From 2e0bf42f8656125e801b176b81b6470dadf8d066 Mon Sep 17 00:00:00 2001 From: abdul Date: Mon, 2 Mar 2020 15:53:52 -0800 Subject: [PATCH] init commit --- CHANGELOG.rst | 1439 ------------- CMakeLists.txt | 187 -- LUCIDCamerasWithROS_v1.1.pdf | Bin 0 -> 578089 bytes README.md | 3 + catkin_ws/inc/image_encodings.h | 216 ++ catkin_ws/src/CMakeLists.txt | 1 + catkin_ws/src/arena_camera/.gitignore | 19 + catkin_ws/src/arena_camera/CMakeLists.txt | 263 +++ LICENSE => catkin_ws/src/arena_camera/LICENSE | 0 .../src/arena_camera/README.rst | 49 +- .../src/arena_camera/config}/default.yaml | 12 +- .../arena_camera/include/arena_camera/Arena.h | 89 + .../include/arena_camera/ArenaApi.h | 50 + .../include/arena_camera/Buffer.h | 95 + .../include/arena_camera/Device.h | 108 + .../arena_camera/include/arena_camera/Image.h | 59 + .../include/arena_camera/System.h | 74 + .../include/arena_camera/arena_camera.h | 494 +++++ .../include/arena_camera/arena_camera.h.gch | Bin 0 -> 8555 bytes .../include/arena_camera/arena_camera_node.h | 393 ++++ .../arena_camera/arena_camera_parameter.h | 335 +++ .../arena_camera}/encoding_conversions.h | 13 +- .../include/arena_camera/internal/Arena.h | 89 + .../include/arena_camera/internal/Arena.rc | 111 + .../arena_camera/internal/Arena.vcxproj | 431 ++++ .../include/arena_camera/internal/ArenaApi.h | 50 + .../include/arena_camera/internal/ArenaDefs.h | 126 ++ .../include/arena_camera/internal/Buffer.h | 95 + .../include/arena_camera/internal/ChunkData.h | 41 + .../include/arena_camera/internal/Device.h | 108 + .../arena_camera/internal/DeviceInfo.h | 566 +++++ .../arena_camera/internal/DeviceInfoType.h | 32 + .../arena_camera/internal/Exceptions.h | 102 + .../arena_camera/internal/FeatureStream.h | 274 +++ .../arena_camera/internal/FeatureStreamType.h | 15 + .../include/arena_camera/internal/GTestAPI.h | 29 + .../arena_camera/internal/GenApiCustom.h | 285 +++ .../include/arena_camera/internal/GenTL.h | 793 +++++++ .../arena_camera/internal/GenTLCustom.h | 67 + .../arena_camera/internal/GetInfoUtility.h | 35 + .../include/arena_camera/internal/IBuffer.h | 574 +++++ .../arena_camera/internal/IChunkData.h | 501 +++++ .../include/arena_camera/internal/IDevice.h | 954 +++++++++ .../include/arena_camera/internal/IImage.h | 857 ++++++++ .../include/arena_camera/internal/ISystem.h | 461 ++++ .../include/arena_camera/internal/Image.h | 59 + .../internal/ImageExtendedChunk.h | 46 + .../arena_camera/internal/ImageFactory.h | 262 +++ .../include/arena_camera/internal/Interface.h | 58 + .../arena_camera/internal/InterfaceInfo.h | 277 +++ .../arena_camera/internal/InterfaceInfoType.h | 20 + .../arena_camera/internal/NodeMapUtility.h | 8 + .../include/arena_camera/internal/PFNC.h | 1081 ++++++++++ .../arena_camera/internal/PFNCCustom.h | 41 + .../include/arena_camera/internal/Port.h | 26 + .../arena_camera/internal/PrivateGlobals.h | 7 + .../include/arena_camera/internal/ReadMe.txt | 40 + .../arena_camera/internal/StreamInfo.h | 35 + .../include/arena_camera/internal/System.h | 74 + .../include/arena_camera/internal/TLBase.h | 30 + .../arena_camera/internal/TLDataStream.h | 27 + .../include/arena_camera/internal/TLDevice.h | 23 + .../include/arena_camera/internal/TLEvent.h | 20 + .../arena_camera/internal/TLInterface.h | 25 + .../include/arena_camera/internal/TLSystem.h | 23 + .../arena_camera/internal/arena_camera.h | 186 ++ .../arena_camera/internal/libarena.version | 4 + .../include/arena_camera/internal/makefile | 84 + .../include/arena_camera/internal/resource.h | 14 + .../include/arena_camera/internal/stdafx.h | 25 + .../include/arena_camera/internal/targetver.h | 10 + .../arena_camera_grab_and_save_as.launch | 6 +- .../launch/arena_camera_node.launch | 6 +- ...na_camera_node_multi_device_example.launch | 48 + catkin_ws/src/arena_camera/package.xml | 90 + .../arena_camera/rosdep/arena_sdk.rdmanifest | 66 + .../src/arena_camera/rosdep/arena_sdk.yaml | 4 + .../src/arena_camera/rosdep}/empty.tar | Bin .../arena_camera/scripts}/file_sequencer.py | 0 .../grab_and_save_image_action_server.py | 6 +- .../scripts}/individual_flash_test | 2 +- .../scripts}/result_bag_to_action.py | 0 .../arena_camera/scripts}/sequence_to_file.py | 0 .../src/arena_camera/scripts}/toggle_camera | 6 +- .../scripts}/triggered_image_topic.py | 4 +- .../src/arena_camera/setup.py | 9 +- .../src/arena_camera/src/arena_camera.cpp | 92 + .../arena_camera/src/arena_camera_node.cpp | 1899 +++++++++++++++++ .../src/arena_camera_parameter.cpp | 367 ++++ .../arena_camera/src/encoding_conversions.cpp | 198 ++ .../src/arena_camera/src}/main.cpp | 32 +- .../src/write_device_user_id_to_camera.cpp | 93 + catkin_ws/src/camera_control_msgs/.gitignore | 13 + .../src/camera_control_msgs/CMakeLists.txt | 15 + catkin_ws/src/camera_control_msgs/LICENSE.rst | 27 + catkin_ws/src/camera_control_msgs/README.rst | 44 + .../action/GrabAndSaveImage.action | 77 + .../action/GrabHDRImage.action | 99 + .../action/GrabImages.action | 93 + catkin_ws/src/camera_control_msgs/package.xml | 31 + .../srv/GetCamProperties.srv | 38 + .../camera_control_msgs/srv/SetBinning.srv | 16 + .../src/camera_control_msgs/srv/SetBool.srv | 6 + .../camera_control_msgs/srv/SetBrightness.srv | 30 + .../camera_control_msgs/srv/SetExposure.srv | 7 + .../src/camera_control_msgs/srv/SetGain.srv | 8 + .../src/camera_control_msgs/srv/SetGamma.srv | 10 + .../src/camera_control_msgs/srv/SetROI.srv | 17 + .../camera_control_msgs/srv/SetSleeping.srv | 7 + cmake/FindPylon.cmake | 30 - include/pylon_camera/binary_exposure_search.h | 131 -- .../internal/impl/pylon_camera_base.hpp | 1158 ---------- .../internal/impl/pylon_camera_dart.hpp | 133 -- .../internal/impl/pylon_camera_gige.hpp | 462 ---- .../internal/impl/pylon_camera_usb.hpp | 344 --- include/pylon_camera/internal/pylon_camera.h | 189 -- include/pylon_camera/pylon_camera.h | 524 ----- include/pylon_camera/pylon_camera_node.h | 411 ---- include/pylon_camera/pylon_camera_parameter.h | 333 --- package.xml | 51 - ros_and_workspace_setup.sh | 105 + rosdep/pylon_sdk.rdmanifest | 67 - rosdep/pylon_sdk.yaml | 4 - src/pylon_camera/binary_exposure_search.cpp | 110 - src/pylon_camera/encoding_conversions.cpp | 160 -- src/pylon_camera/pylon_camera.cpp | 267 --- src/pylon_camera/pylon_camera_node.cpp | 1649 -------------- src/pylon_camera/pylon_camera_parameter.cpp | 368 ---- .../write_device_user_id_to_camera.cpp | 99 - wiki_imgs/logos.png | Bin 20948 -> 0 bytes wiki_imgs/logos_small.png | Bin 21573 -> 0 bytes 131 files changed, 14861 insertions(+), 8200 deletions(-) delete mode 100644 CHANGELOG.rst delete mode 100644 CMakeLists.txt create mode 100644 LUCIDCamerasWithROS_v1.1.pdf create mode 100644 README.md create mode 100644 catkin_ws/inc/image_encodings.h create mode 120000 catkin_ws/src/CMakeLists.txt create mode 100644 catkin_ws/src/arena_camera/.gitignore create mode 100644 catkin_ws/src/arena_camera/CMakeLists.txt rename LICENSE => catkin_ws/src/arena_camera/LICENSE (100%) rename README.rst => catkin_ws/src/arena_camera/README.rst (74%) rename {config => catkin_ws/src/arena_camera/config}/default.yaml (96%) create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/Arena.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/ArenaApi.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/Buffer.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/Device.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/Image.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/System.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/arena_camera.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/arena_camera.h.gch create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/arena_camera_node.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/arena_camera_parameter.h rename {include/pylon_camera => catkin_ws/src/arena_camera/include/arena_camera}/encoding_conversions.h (93%) create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.rc create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.vcxproj create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/ArenaApi.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/ArenaDefs.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/Buffer.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/ChunkData.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/Device.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/DeviceInfo.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/DeviceInfoType.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/Exceptions.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/FeatureStream.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/FeatureStreamType.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/GTestAPI.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/GenApiCustom.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/GenTL.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/GenTLCustom.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/GetInfoUtility.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/IBuffer.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/IChunkData.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/IDevice.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/IImage.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/ISystem.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/Image.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/ImageExtendedChunk.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/ImageFactory.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/Interface.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/InterfaceInfo.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/InterfaceInfoType.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/NodeMapUtility.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/PFNC.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/PFNCCustom.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/Port.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/PrivateGlobals.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/ReadMe.txt create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/StreamInfo.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/System.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/TLBase.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/TLDataStream.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/TLDevice.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/TLEvent.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/TLInterface.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/TLSystem.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/arena_camera.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/libarena.version create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/makefile create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/resource.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/stdafx.h create mode 100644 catkin_ws/src/arena_camera/include/arena_camera/internal/targetver.h rename launch/pylon_camera_grab_and_save_as.launch => catkin_ws/src/arena_camera/launch/arena_camera_grab_and_save_as.launch (61%) rename launch/pylon_camera_node.launch => catkin_ws/src/arena_camera/launch/arena_camera_node.launch (71%) create mode 100644 catkin_ws/src/arena_camera/launch/arena_camera_node_multi_device_example.launch create mode 100644 catkin_ws/src/arena_camera/package.xml create mode 100644 catkin_ws/src/arena_camera/rosdep/arena_sdk.rdmanifest create mode 100644 catkin_ws/src/arena_camera/rosdep/arena_sdk.yaml rename {rosdep => catkin_ws/src/arena_camera/rosdep}/empty.tar (100%) rename {scripts => catkin_ws/src/arena_camera/scripts}/file_sequencer.py (100%) mode change 100755 => 100644 rename {scripts => catkin_ws/src/arena_camera/scripts}/grab_and_save_image_action_server.py (97%) mode change 100755 => 100644 rename {scripts => catkin_ws/src/arena_camera/scripts}/individual_flash_test (97%) mode change 100755 => 100644 rename {scripts => catkin_ws/src/arena_camera/scripts}/result_bag_to_action.py (100%) mode change 100755 => 100644 rename {scripts => catkin_ws/src/arena_camera/scripts}/sequence_to_file.py (100%) mode change 100755 => 100644 rename {scripts => catkin_ws/src/arena_camera/scripts}/toggle_camera (89%) rename {scripts => catkin_ws/src/arena_camera/scripts}/triggered_image_topic.py (95%) mode change 100755 => 100644 rename setup.py => catkin_ws/src/arena_camera/setup.py (69%) mode change 100755 => 100644 create mode 100644 catkin_ws/src/arena_camera/src/arena_camera.cpp create mode 100644 catkin_ws/src/arena_camera/src/arena_camera_node.cpp create mode 100644 catkin_ws/src/arena_camera/src/arena_camera_parameter.cpp create mode 100644 catkin_ws/src/arena_camera/src/encoding_conversions.cpp rename {src/pylon_camera => catkin_ws/src/arena_camera/src}/main.cpp (76%) create mode 100644 catkin_ws/src/arena_camera/src/write_device_user_id_to_camera.cpp create mode 100644 catkin_ws/src/camera_control_msgs/.gitignore create mode 100644 catkin_ws/src/camera_control_msgs/CMakeLists.txt create mode 100644 catkin_ws/src/camera_control_msgs/LICENSE.rst create mode 100644 catkin_ws/src/camera_control_msgs/README.rst create mode 100644 catkin_ws/src/camera_control_msgs/action/GrabAndSaveImage.action create mode 100644 catkin_ws/src/camera_control_msgs/action/GrabHDRImage.action create mode 100644 catkin_ws/src/camera_control_msgs/action/GrabImages.action create mode 100644 catkin_ws/src/camera_control_msgs/package.xml create mode 100644 catkin_ws/src/camera_control_msgs/srv/GetCamProperties.srv create mode 100644 catkin_ws/src/camera_control_msgs/srv/SetBinning.srv create mode 100644 catkin_ws/src/camera_control_msgs/srv/SetBool.srv create mode 100644 catkin_ws/src/camera_control_msgs/srv/SetBrightness.srv create mode 100644 catkin_ws/src/camera_control_msgs/srv/SetExposure.srv create mode 100644 catkin_ws/src/camera_control_msgs/srv/SetGain.srv create mode 100644 catkin_ws/src/camera_control_msgs/srv/SetGamma.srv create mode 100644 catkin_ws/src/camera_control_msgs/srv/SetROI.srv create mode 100644 catkin_ws/src/camera_control_msgs/srv/SetSleeping.srv delete mode 100644 cmake/FindPylon.cmake delete mode 100644 include/pylon_camera/binary_exposure_search.h delete mode 100644 include/pylon_camera/internal/impl/pylon_camera_base.hpp delete mode 100644 include/pylon_camera/internal/impl/pylon_camera_dart.hpp delete mode 100644 include/pylon_camera/internal/impl/pylon_camera_gige.hpp delete mode 100644 include/pylon_camera/internal/impl/pylon_camera_usb.hpp delete mode 100644 include/pylon_camera/internal/pylon_camera.h delete mode 100644 include/pylon_camera/pylon_camera.h delete mode 100644 include/pylon_camera/pylon_camera_node.h delete mode 100644 include/pylon_camera/pylon_camera_parameter.h delete mode 100644 package.xml create mode 100644 ros_and_workspace_setup.sh delete mode 100644 rosdep/pylon_sdk.rdmanifest delete mode 100644 rosdep/pylon_sdk.yaml delete mode 100644 src/pylon_camera/binary_exposure_search.cpp delete mode 100644 src/pylon_camera/encoding_conversions.cpp delete mode 100644 src/pylon_camera/pylon_camera.cpp delete mode 100644 src/pylon_camera/pylon_camera_node.cpp delete mode 100644 src/pylon_camera/pylon_camera_parameter.cpp delete mode 100644 src/pylon_camera/write_device_user_id_to_camera.cpp delete mode 100644 wiki_imgs/logos.png delete mode 100644 wiki_imgs/logos_small.png diff --git a/CHANGELOG.rst b/CHANGELOG.rst deleted file mode 100644 index 2af5e2f3..00000000 --- a/CHANGELOG.rst +++ /dev/null @@ -1,1439 +0,0 @@ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Changelog for package pylon_camera -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -0.15.0 (2019-03-14) -------------------- -* Add support or area of interest selection through service call - -0.14.1 (2019-03-13) -------------------- -* If no device id is given, loop over all available devices until finding a valid one - -0.14.0 (2019-02-07) -------------------- -* added diagnostics - -0.13.0 (2018-12-20) -------------------- -* Added script to collect images with different flashes -* filling auto_flash_line_2\_ (3) parameter from parameter server -* Support for online change of autoflash - Allow activating and deactivating lines as active on exposure - A new service is provided per output line, which allows it to be - configured as autoflash. Internally GiGe Cameras will - default to switch all outputs on if autoflash is set to True - whenever exposure is active. -* Removed maintainer - -0.12.0 (2018-06-19) -------------------- -* Parameter for auto flash control -* Publish camera info when requested, but deactivate images grabbed when not -* Hard coded line_3 as output for gigE cameras -* possible fix for setting device_user_id if it was not set by user -* Propagating the device user id back to ros params server, if not set - -0.11.8 (2018-05-16) -------------------- -* Last change from Marcel as a member of the Magazino-Team. Bye Bye :-) -* Bugfix: CameraPublisherImpl leads to crash -* Added CameraInfo to the GrabImagesResult - -0.11.7 (2018-05-16) -------------------- -* Revied PR, Moved CameraPublisherImpl struct to method -* Workaround for overestimated wrong numbers of subscribers on image_raw - -0.11.6 (2018-05-15) -------------------- -* Added definition in default config and readme description of missed parameter 'shutter_mode' -* fix typos. -* Fixed typo in message in method PylonCameraNode::startGrabbing -* Tried to fix LICENSE-issue - -0.11.5 (2018-05-07) -------------------- -* Updated license year -* Issue found -> wrong year -> renamed back - -0.11.4 (2018-05-07) -------------------- -* Again renamed the LICENSE file - -0.11.3 (2018-05-07) -------------------- -* Renamed LICENSE.rst - -0.11.2 (2018-03-28) -------------------- -* Bugfix for useless rectification - Moved rectification part in the scope of publishing the result. - This resolves #36. - Thanks to @flajolet for your contribution - -0.11.1 (2018-03-26) -------------------- -* Improved re-connection behavior - ...by resetting the set_user_output services - Furthermore the spin method now checks if the camera might have lost connection - and toggles a re-init. - -0.11.0 (2018-03-06) -------------------- -* Removed deprectated msg-flags - There were deprecated flags in camera_control_msgs/GrabImagesAction, - that have been removed, namely - uint8 BRIGHTNESS = 1 - uint8 EXPOSURE = 2 - uint8 target_type - float32[] target_values - -0.10.14 (2018-03-05) --------------------- -* Updated outdated/confusing install instructions - This resolves #21 - -0.10.13 (2018-03-05) --------------------- -* Add aarch64 architecture (`#32 `_) -* Contributors: lalten - -0.10.12 (2018-02-13) --------------------- -* 0.10.11 -* Updated install intructions in README.rst, catkin_lint - -0.10.10 (2018-02-13) --------------------- -* Installation via pylon-debian pkg - This fixes #22, fixes #31 -* Added missing camera_info_manager dependency - -0.10.9 (2018-01-29) -------------------- -* Reviewd bugfix: init() is called within action -> multiple server - Resolves: SW-6342 -* Create action server and data for rectification only once, delete allocated data conditionally - -0.10.8 (2018-01-04) -------------------- -* prevent double free - -0.10.7 (2017-11-20) -------------------- -* Bugfix: lost this because of PylonCameraNode::grabImagesRaw() - -0.10.6 (2017-10-13) -------------------- -* fix Pylon find script (`#27 `_) -* Contributors: tlindbloom - -0.10.5 (2017-09-28) -------------------- -* Empty action goal now leads to undefined returned image -* Updated README -* Corrected install instructions - Basler finally provides a debian package for the pylon-sdk - -0.10.4 (2017-09-11) -------------------- -* Regeneration of brightness indices after binning change - Resolves: https://github.com/magazino/pylon_camera/issues/26 - -0.10.3 (2017-08-31) -------------------- -* Removed all non-ascii symbols from the changelog - -> fixed changelog 2.0 - -0.10.2 (2017-08-31 12:09) -------------------------- -* Fixed CHANGELOG.rst -* Contributors: Marcel Debout - -0.10.1 (2017-08-31 11:48) -------------------------- -* Bugfix: gain of 0.0 was rejected due to wrong empty check -* CMAKE_INSTALL_RPATH_USE_LINK_PATH for pylon deb -* Contributors: Marcel Debout, Markus Grimm - -0.10.0 (2017-07-17) -------------------- -* Updated message dependencies -* Contributors: Magazino Version Daemon - -0.9.2 (2017-06-26) ------------------- -* SW-1177 No longer using node_name as frame_name but keeping frame_name from configuration file -* Contributors: Nikolas Engelhard - -0.9.1 (2017-04-18 17:41) ------------------------- -* removed cv bridge version -* Contributors: Ulrich Klank - -0.9.0 (2017-04-18 09:25) ------------------------- -* Updated message dependencies -* Contributors: Magazino Version Daemon - -0.8.1 (2017-04-18 09:22) ------------------------- -* removed cv bridge version -* Contributors: Ulrich Klank - -0.8.0 (2017-04-12 21:03) ------------------------- -* Updated message dependencies -* Contributors: Magazino Version Daemon - -0.7.7 (2017-04-12 18:46) ------------------------- -* Force recompile (blank line CMakeLists.txt) -* Contributors: Marcel Debout - -0.7.6 (2017-04-12 11:02) ------------------------- -* Tested the auto-exp-upprper-lim and adapted fail output -* Added upper-exp-limit for exp/brightness search - For dark scenes, the exposure search (to reach a desired brightness) will - reach high exposure values ( >1s ). This leads to instabillity and - timeouts. Therefore it's now possible to limit the exposure and fail in - case the brightness can not be reached. - The own binary-exp-search takes above limits as bounds. -* Contributors: Marcel Debout - -0.7.5 (2017-04-06 16:14) ------------------------- -* Improved smart exp search for values < 50 - The own binary search needs an upper bound. This is generated by using - the default PylonAutoExposure function with a value of 50. - So an initial setting of the corresponding exposure to a brightness of - 50 will speed up the search. -* Contributors: Marcel Debout - -0.7.4 (2017-04-06 09:32) ------------------------- -* Updated invalid logo path -* Contributors: Marcel Debout - -0.7.3 (2017-03-01) ------------------- -* Fix: Installation failed du to return code 2 - udevadm control can return failure ($? != 0) - When building docker containers, the or true does the trick -* Contributors: plieningerweb - -0.7.2 (2017-02-23) ------------------- -* Fix: Install udev rules - Udev Rules usually installed with setup-usb.sh of tar.gz - Without, camera will not be recognized in Ubuntu stock install -* Contributors: plieningerweb - -0.7.1 (2017-02-14) ------------------- -* Reviewing beetkeskin PR for GigE gamma - - Formatted the code (deleted whitespaces) - - Agreed to the fact that a non-accessible gamma is not always an error, - so that returning true makes sense - - Enabling gamma before checking if the NodeMap is available might solve - the problem -* 0.7.0 -* fixed type decive->device -* fix gamma handling for GigE cameras - When connecting to a GigE camera (aca1920-50gc), the node crashes with "Error while accessing Gamma in PylonCameraImpl": For some camera types, the Gamma settings are not available to the interface as they are handled automatically by the camera itself. This was already partly fixed for some gamma-related function calls, but not for all of them. This fix adds the missing checks. The behaviour is slightly changed: If the gamma is not set via user, gamma remains in auto mode (i.e. controlled by the camera). Once the user tries to set a gamma value, the gamma mode switches to user. -* Contributors: Magazino Version Daemon, Marcel Debout, Nikolas Engelhard, Stefan Kaiser - -0.6.17 (2016-11-23 14:54) -------------------------- -* Bugfix: Wrong vector size (255 instead of 256) -* Contributors: Marcel Debout - -0.6.16 (2016-11-23 13:38) -------------------------- -* Added upper brightness limit -* Contributors: Marcel Debout - -0.6.15 (2016-11-23 10:04) -------------------------- -* Fixed crash in case of target brightness > 255 -* Contributors: Marcel Debout - -0.6.14 (2016-11-23 09:13) -------------------------- -* Reviewd parametrized timeout for ExposureSearch - Lead to a better RaspPI support -* Refactor exposure time search to meet requ -* Add timeout param for brightness adjustment - Add the optional parameter brightness_timeout to increase - the time for the brightness search. Modified error massage - to report the actual timeout. -* Contributors: Marcel Debout, Maxi Maerz - -0.6.13 (2016-11-14) -------------------- -* Fixed non-working set gamma for GigE cameras - Up to now, the setGamma() did not have an influence for GigE cameras, - because one has to 'EnableGamma' first. Fixed that bug by moving from - base-class to the usb and gige classes -* Contributors: Marcel Debout - -0.6.12 (2016-11-08 17:45) -------------------------- -* Moved setup of exp-search before the first brightness is set -* Contributors: Marcel Debout - -0.6.11 (2016-11-08 16:47) -------------------------- -* Reverted bullshit changes that broke the exp search -* Contributors: Marcel Debout - -0.6.10 (2016-11-08 12:13) -------------------------- - -0.6.9 (2016-11-08 09:13) ------------------------- -* Fixed brightness calculation for color images - For mono cameras, the subset calculation remains, for color images the - brightness is calculated using all pixels and channels -* 0.6.8 -* Fix for non-selectable gamma for some GigE cameras -* 0.6.7 -* Updated changelog and README.rst -* Changed default behavior (no_ros_enc given) - Non-provided encoding is indicated via empty string right-now. - Default values are mono8 and rgb8 which are checked afterwards. - Moved YUV422 support to 'future work'. - Still TODO: - Update documentation - - Fix brightness search that is evaluating various colored - pixels for now - See: https://github.com/magazino/pylon_camera/pull/7 - Resolves: AL-87 -* First working color image version with Bayer Support - - Moved imagePixelDepth() and the setEncoding() Method to the base - implementation. - - Added functionallity to detect and store the available image encodings from - the used camera. - - Added conversion methods to convert between ROS and GenAPI encodings - Still TODO: - Update documentation - - Test code with a camera that supports 'rgb8' and 'bgr8' - - Provide 'bgr8' iamges in case the camera does not support - 'BGR8' but has 'YCbCr422_8' instead - - Test brightness search - See: https://github.com/magazino/pylon_camera/pull/7 - Resolves: AL-87 -* Updated rectify image to support rgb8 encoding. - Updated grabImage function to create "img_raw" variable with correct - format based on current image encoding. -* Updated imageEncoding and imagePixelDepth function - - Modified imageEncoding function to support RGB8 format. - - Modified imagePixelDepth function to return correct pixel size based - on current image encoding. -* Added function to set PixelFormat - Baed on image_encoding\_ paramter, the function set appropriate - PixelFormat depending on USB camera or GigE camera. -* Added image_encoding as parameter - Added image_encoding as one of the parameters defined in yaml file. User - can choose between "MONO8" and "RGB8". -* Contributors: Kazumi Malhan, Magazino Version Daemon, Marcel Debout - -0.6.6 (2016-10-19) ------------------- -* Merged in unstable/super_fast_brightness_search (pull request #2) - Unstable/super fast brightness search -* Further micro-CleanUP -* CleanUp & Comments -* Downsampling is now working, fixed indices error -* Added brighntness exp LUT, to allow smart search - Unstable version with lot's of debug output -> to be tested on the robot -* Continued working on the brightness speedup - Fixed missing starting point offset in index calculation - Added output to compare both methods - Added imwrite to investigate the result -* Added idx vector to select subset of pixels - Idea is that the brightness search does not have to calculate the mean - of the entire image in every step, furthermore on a supset of pixels. - Pixels will be selected like this: - sampled img: point: idx: - s 0 0 0 0 0 0 a) [(e.x-s.x)*0.5, (e.y-s.y)*0.5] a.x*a.y*0.5 - 0 0 0 d 0 0 0 b) [a.x, 1.5*a.y] b.y*a.x+b.x - 0 0 0 0 0 0 0 c) [0.5*a.x, a.y] c.y*a.x+c.x - 0 c 0 a 0 f 0 d) [a.x, 0.5*a.y] d.y*a.x+d.x - 0 0 0 0 0 0 0 f) [1.5*a.x, a.y] f.y*a.x+f.x - 0 0 0 b 0 0 0 - 0 0 0 0 0 0 e - Resolves: TORU-1750 -* Contributors: Marcel Debout - -0.6.5 (2016-08-31) ------------------- -* Added a script that calls the grab image action and publishes the result on on a sensor_msgs/Image topic -* Contributors: Ulrich Klank - -0.6.4 (2016-08-24) ------------------- -* setting image publisher queuesize to 1. If queue is to long and only single images are used (e.g. by waking up camera via set_sleeping, getting an image, setting to sleep again), old images are provided -* Contributors: Nikolas Engelhard - -0.6.3 (2016-08-23) ------------------- -* new script to toggle camera(s) -* Contributors: Nikolas Engelhard - -0.6.2 (2016-08-16 16:12) ------------------------- -* Changed new brightness request do ros_debug as it was creating a lot of output -* Contributors: Carsten Zumsande - -0.6.1 (2016-08-16 15:06) ------------------------- -* Changed new brightness request do ros_debug as it was creating a lot of output -* Contributors: Carsten Zumsande - -0.6.0 (2016-07-28) ------------------- -* Updated message dependencies -* Contributors: Magazino Version Daemon - -0.5.4 (2016-07-26) ------------------- -* Merged in user_output (pull request #1) - User_output -* ros-linted the code, removed tabs -* Made set-user-output working finally! Still have problems, that USB cameras start counting with 1 and GigE-Cameras by 0, but created a workaround -* figured out, that basler enums are of type double, removed num_outputs member and replaced it with a vector containing the UserOutputselectorEnums -> Output '1' can now be set using 'vector.at(1)' -* added function that counts the number of available UserOutputs for the camera, have to test it for other devices -* starting to fix the setDigitalOutput functions for GigE cameras. Added member to the pylon_camera-class where the number of digital user outputs a camera provide will be stored. Still have to think of a way how to get this information, because they are highly dependend the used device and the used enums -* Contributors: Marcel Debout - -0.5.3 (2016-06-28 07:41) ------------------------- -* typo - thank God for jenkins -* Contributors: Marcel Debout - -0.5.2 (2016-06-28 07:21) ------------------------- -* corrected command line output in case that the default image encoding is not mono8 -* Contributors: Marcel Debout - -0.5.1 (2016-06-27) ------------------- -* Fixed: Node claims to not have reached the desired brightness, but in fact it reached the brightness. Therefore trust in the pylon auto brightness search function and wait till it claims to be finished, instead of running into the timeout -* Contributors: Marcel Debout - -0.5.0 (2016-06-23) ------------------- -* Fixed a two bugs reported by andermi: Node crashes in case that the camera does not support binning. (fixed by previously checking if this feature is available) and setting the mono8 image encoding before the startGrabbing(), because afterwards it's assumend to be fix. -* Contributors: Marcel Debout - -0.4.2 (2016-05-20 12:02) ------------------------- -* minor fix: changed from global namespace to the one of the node -* Contributors: Marcel Debout - -0.4.1 (2016-05-20 08:12) ------------------------- -* Bugfix: filled empty 'encoding' field for images comming via the 'grab_images_rect'-action -* Contributors: Marcel Debout - -0.4.0 (2016-05-12 15:24) ------------------------- -* improved error handling for the grab_and_save action server -* Contributors: Marcel Debout - -0.3.2 (2016-05-12 14:31) ------------------------- -* added launch file for grab_and_save_image_as and print error instead of warning, in case no grab_image_raw as is found -* Contributors: Marcel Debout - -0.3.1 (2016-05-12 14:11) ------------------------- -* fixed copy-paste typo and added loginfo output -* Contributors: Marcel Debout - -0.3.0 (2016-05-12 13:43) ------------------------- -* Updated message dependencies -* Contributors: Magazino Version Daemon - -0.2.9 (2016-05-12 13:41) ------------------------- -* added action server which wraps the GrabImagesAction to be able to store the grabbed at desired location on the filesystem -* Contributors: Marcel Debout - -0.2.8 (2016-05-11) ------------------- -* Node dies no longer, if no device is available. Instead it keeps retrying to find a camera -* Contributors: Marcel Debout - -0.2.7 (2016-05-10 18:37) ------------------------- -* fixed wrong uri in rdmanifest file -* Contributors: Marcel Debout - -0.2.6 (2016-05-10 17:09) ------------------------- -* README.rst edited online with Bitbucket -* fixed wrong link name -* Contributors: Marcel Debout - -0.2.5 (2016-05-10 15:32) ------------------------- -* renamed empty tar -* Contributors: Markus Grimm - -0.2.4 (2016-05-10 13:57) ------------------------- -* Added required-empty.tar archive for rosdep -* Contributors: Markus Grimm - -0.2.3 (2016-05-09 18:07) ------------------------- -* README.rst edited online with Bitbucket -* Contributors: Marcel Debout - -0.2.2 (2016-05-09 17:32) ------------------------- -* Updated readme -* Contributors: Markus Grimm - -0.2.1 (2016-05-09 16:17) ------------------------- -* updated rosdep definitions for github -* Contributors: Markus Grimm - -0.2.0 (2016-05-09 15:44) ------------------------- -* Updated message dependencies -* Contributors: Magazino Version Daemon - -0.1.1 (2016-05-09 15:40) ------------------------- -* Updated message dependencies -* Added rdmanifest script to download pylon sdk -* Contributors: Magazino Version Daemon, Markus Grimm - -0.1.0 (2016-05-09 09:08) ------------------------- -* Updated message dependencies -* Contributors: Magazino Version Daemon - -0.0.72 (2016-05-04) -------------------- -* basler-feedback: usage of the https:// origin for git clone to be able to use it without ssh key -* Contributors: Marcel Debout - -0.0.71 (2016-05-03) -------------------- -* added loslaunch dependency to be able to check the launch files at build time -* Contributors: Marcel Debout - -0.0.70 (2016-05-02 18:41) -------------------------- -* continued linting to reduce cpp-check errors -* Contributors: Marcel Debout - -0.0.69 (2016-05-02 18:21) -------------------------- -* linting -* Contributors: Marcel Debout - -0.0.68 (2016-04-29) -------------------- -* TORU-319: cleaned up cmake -* Contributors: Markus Grimm - -0.0.67 (2016-04-26) -------------------- -* ROBEE-336: linting for result bag to action -* Contributors: zumsande - -0.0.66 (2016-04-25 18:52) -------------------------- -* ROBEE-336 -* Contributors: Ulrich Klank - -0.0.65 (2016-04-25 16:42) -------------------------- -* Basler-Feedback: 'pylon' should be lower-case -* Contributors: Marcel Debout - -0.0.64 (2016-04-19) -------------------- -* added missing camera_info_url description to the default config file -* Contributors: Marcel Debout - -0.0.63 (2016-04-18) -------------------- -* README.rst edited online with Bitbucket, - Added 'questions' section -* Contributors: Marcel Debout - -0.0.62 (2016-04-14 18:01) -------------------------- -* fixed unhandled std::runtime_error in constructor: init() is now void, if something fails (no camera present) ros::shutdown() will be called. Furthermore added handling if grabImage() fails -* Contributors: Marcel Debout - -0.0.61 (2016-04-14 17:01) -------------------------- -* write out namespace instead of assuming default -* Contributors: Marcel Debout - -0.0.60 (2016-04-13 16:21) -------------------------- -* fixed launch file bug: tf frame should not contain '/', setting frame_id in case that the camera_info is parsed from the camera info manager -* Contributors: Marcel Debout - -0.0.59 (2016-04-13 08:41) -------------------------- -* changed size of logos for the wiki.ros.org page -* Contributors: Marcel Debout - -0.0.58 (2016-04-12 18:53) -------------------------- -* edited logo size for ros-wiki -* Contributors: Marcel Debout - -0.0.57 (2016-04-12 18:31) -------------------------- -* added small logo for wiki.ros.org -* Contributors: Marcel Debout - -0.0.56 (2016-04-12 17:31) -------------------------- -* README.rst edited online with Bitbucket -* README.rst edited online with Bitbucket -* Contributors: Marcel Debout - -0.0.55 (2016-04-12 17:04) -------------------------- -* README.rst edited online with Bitbucket -* Contributors: Marcel Debout - -0.0.54 (2016-04-12 16:51) -------------------------- -* README.rst edited online with Bitbucket -* Contributors: Marcel Debout - -0.0.53 (2016-04-12 16:31) -------------------------- -* Added rosdep yaml -* Contributors: Markus Grimm - -0.0.52 (2016-04-12 13:21) -------------------------- -* README.rst edited online with Bitbucket -* Contributors: Marcel Debout - -0.0.51 (2016-04-12 12:21) -------------------------- -* magazino_id is now the device_user_id as in the pylon API -* Contributors: Marcel Debout - -0.0.50 (2016-04-12 12:01) -------------------------- -* added CHANGELOG.rst, generated by catkin_generate_changelog -* Contributors: Marcel Debout - -0.0.49 (2016-04-12 10:31) -------------------------- -* Updated readme -* Contributors: Markus Grimm - -0.0.48 (2016-04-11 13:41) -------------------------- -* removed deprecated 'SetBrightnessSrv', 'SetExposureSrv' and 'SetSleepingSrv'. Please adapt to the new interface -* ROS_WARN instead of ROS_ERR if the desired brightness could not be reached -* Contributors: Marcel Debout - -0.0.47 (2016-04-11 10:12) -------------------------- -* Code review -* Contributors: Markus Grimm - -0.0.46 (2016-04-08 16:52) -------------------------- -* Changed dependencies for pylon to the new debian package -* Contributors: Markus Grimm - -0.0.45 (2016-04-08 15:42) -------------------------- -* fixed premature commit - TORU-623 -* Handle constructor failures differently - TORU-623 -* Contributors: Ulrich Klank - -0.0.44 (2016-04-07 18:06) -------------------------- -* init size_t with 0 instead of -1 -* Contributors: Marcel Debout - -0.0.43 (2016-04-07 17:42) -------------------------- -* readded HEader after rectification -* Contributors: Ulrich Klank - -0.0.42 (2016-04-07 17:11) -------------------------- -* formatting & coding style -* Contributors: Marcel Debout - -0.0.41 (2016-04-07 16:32) -------------------------- -* added parameter for inter-pkg-delay for RaspberryPI usage -* Contributors: Marcel Debout - -0.0.40 (2016-04-07 15:32) -------------------------- -* linting -* Contributors: Marcel Debout - -0.0.39 (2016-04-07 13:12) -------------------------- -* removed dublicated dependency -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera into opencv_rect -* finally added rectification support using the image_geometry::pinhole_model and the CameraInfoManager -* pulled intrinsic calib-reading from opencv_class -* first implementation with the CameraInfoManager -* fixed strange overriding behaviour in case that one requests brightness with auto_exposure and auto_gain set to false -* 0.0.36 -* fixed console output of the timeout duration in brightness search -* 0.0.35 -* removed unused member, found shorter name for the grabbing action server -* 0.0.34 -* finally added rectification support using the image_geometry::pinhole_model and the CameraInfoManager -* pulled intrinsic calib-reading from opencv_class -* first implementation with the CameraInfoManager -* started to integrate rectification -* Contributors: Magazino Version Daemon, Marcel Debout - -0.0.38 (2016-04-04) -------------------- -* removed double output in case that the intensity settig fails -* Contributors: Marcel Debout - -0.0.37 (2016-03-31 15:56) -------------------------- -* fixed strange overriding behaviour in case that one requests brightness with auto_exposure and auto_gain set to false -* Contributors: Marcel Debout - -0.0.36 (2016-03-31 15:31) -------------------------- -* fixed console output of the timeout duration in brightness search -* Contributors: Marcel Debout - -0.0.35 (2016-03-31 09:53) -------------------------- -* removed unused member, found shorter name for the grabbing action server -* Contributors: Marcel Debout - -0.0.34 (2016-03-30 16:11) -------------------------- -* renamed ActionServer to GrabImagesAS -* Contributors: Marcel Debout - -0.0.33 (2016-03-30 15:51) -------------------------- -* added missing 'All rights reserved' tag, added LICENSE.rst file -* Contributors: Marcel Debout - -0.0.32 (2016-03-30 15:11) -------------------------- -* README.rst edited online with Bitbucket -* Contributors: Marcel Debout - -0.0.31 (2016-03-30 15:01) -------------------------- -* README.rst edited online with Bitbucket -* Contributors: Marcel Debout - -0.0.30 (2016-03-30 14:44) -------------------------- -* moved all logos into one file -* Contributors: Marcel Debout - -0.0.29 (2016-03-30 13:41) -------------------------- -* added missing wiki_images -* Contributors: Marcel Debout - -0.0.28 (2016-03-30 13:31) -------------------------- -* new logos for the documentation -* README.rst edited online with Bitbucket -* Contributors: Marcel Debout - -0.0.27 (2016-03-30 11:31) -------------------------- -* edited README, added license text to all files -* Contributors: Marcel Debout - -0.0.26 (2016-03-30 10:22) -------------------------- -* moved README to .rst and merged package.xml -* README.md edited online with Bitbucket -* README.md edited online with Bitbucket -* Contributors: Marcel Debout - -0.0.25 (2016-03-29) -------------------- -* implemented setBinning -> be careful: CamerInfo now changes binning_x & binning_y entry while the image height and width keeps static -* Contributors: Marcel Debout - -0.0.24 (2016-03-17 14:21) -------------------------- -* size of provided data through GrabImagesAction should only be checked, if the corresponding 'is_given' flag is true -* Contributors: Marcel Debout - -0.0.23 (2016-03-17 12:41) -------------------------- -* fixed mapping in GrabImagesAction from deprecated to new interface, fixed error in case that values are not provided and the resulting vector size is NOT 0, but 1 -* Contributors: Marcel Debout - -0.0.22 (2016-03-16) -------------------- -* smarter behaviour, if the goal values of the GrabImagesAction doesn't make sense -* Contributors: Marcel Debout - -0.0.21 (2016-03-15 12:52) -------------------------- -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* warnings are not errors -* Contributors: Marcel Debout - -0.0.20 (2016-03-15 11:02) -------------------------- -* compiles without warnings (no return value) -* merged the two branches -* adapted device removal behaviour -* 'is deprecated' error is now a 'is deprecated' warning' -* added deprecated handling of 'set_brightness_srv', 'set_exposure_srv' and 'set_sleeping_srv', which now can be found under 'set_brightness', 'set_exposure' and 'set_sleeping'. Furthermore the usage of 'SetBrightnessSrv.srv', 'SetExposureSrv.srv' and 'SetSleepingSrv.srv' is deprecated and should be switched to 'SetBrightness.srv', SetExposure.srv' and 'SetSleeping.srv' -* implemented setBinning as runtime parameter, but finally realized that the camera does not support it. Hence the camera has to be closed and reopened to be able to set the binning. This will be a future feature -* realized new fast opening behaviour, Basler-Feedback was: Sfnc is outdated, so I replaced it using the DeviceClass and the ModelName. Futhermore its possible to detect the desired camera without opening it twice -* increased fail_safe_ctr for dart cameras -> manual: up to 50 frames needed to reach target for dart cameras -* splitted grabImagesRawActionExecuteCB() in two methods, so that it can also be called from the derived PylonCameraOpenCV class -* moved output to #if DEBUG -* did lots of changes but finally I found a logic behaviour! -* linting & formatting -* added setGamma functionallity -* finally found out that the best is to keep default camera settings as long as possible. Added lots of commands to the default config file, hopefully one can verify my thoughts ;-) -* removed outdated scripts from CMakeLists.txt -* making roslint happy -* removed outdated scripts, brightness tests are coveraged in magazino_tests, exp_caller depends maru stuff -* removed test depend, all tests are done in magazino_tests/pylon_camera_tests -* finally got a state, where brightness tests for usb & gigE are running successfull, have still problems with dart cameras -* 0.0.17 -* README.md wurden online mit Bitbucket bearbeitet -* removed has_auto_exposure\_ member, because this happens already in GenAPI::isAvailable(cam\_->ExposureAuto), added getter for cam\_->AutoGainUpper & Lower limit, added throwing of std::runtime_errors -* searching for autoBrightnessFunction stuck for dart cameras -* clean up dart -* disabled gainselector setting, because each gige cam has its differen naming -* removed senseless getCurrentExp, Gain... functions, correctly implemented setGain -* removed comments -* calling the grabImagesAction with differen exp-times will no longer affect the continiously published images -* further cleaning -* rows & cols are now size_t, removed unused checkForPylonAutoFunctionRunning() -* cleaning & renaming -* cleaned up the extended brightness search, works now very well! -* setExposure() on the pylon_camera-Object (not on PylonCameraNode) has now target and reached exposure -* enabled output -* fixed GainType-bug -* moved exp_search_params, continued working on brightness fix, still problems with dart -* CMakeLists.txt formatted -* dart camera starts with the same settings like the usb camera -* not all usb cameras have GainSelector_AnalogAll -* formatting -* seperated registerConfig, openCamera and applyStartupSettings -* added output regarding gain and exposure time, facing to problems in difference of usb and dart cams -* gain setting started, checking if gain db range gige equals usb -* check if auto function running not necessary any more -* brightness search now in a seperate thread, added lots of comments (and outpouts which i will remove when the gain stuff is working) -* removed auto-functions parameter limits for gige cameras -* gain for dart cameras not hard coded any more, one can set it in initializeing process using the ros-params -* changed order of setting target brightness value & setting the auto-funktion mode -* try to get rid of all these checkForAutoFuncitonRunning() functions using only one PylonCamera::isBrightnessFunctionRunning() method -* - output to check if auto-function still running -* - added const max allowed delta (tolerance) for the brightness search - - switched from int-mean to float mean to decrease rounding errors - - added comments / better readability -* further comments for brightness search -* 0.0.16 -* Basler-Feedback: Prevent that the image will be copied twice: - " - Es handelt sich um ein Missverstndnis. Bei dem Ausdruck image = std::vector(pImageBuffer, pImageBuffer + img_size_byte\_); passiert folgendes: - 1. Konstruktor von std::vector(pImageBuffer, pImageBuffer + img_size_byte\_) aufrufen (1. Kopie der Bildaten) - 2. Zuweisungsoperator von image aufrufen (2. Kopie der Bildaten) - 3. Destruktor von std::vector() aufrufen (1. Kopie wird verworfen) - Der Compiler hat unter Umstnden die Mglichkeit hier zu optimieren, wenn die verwendete STL und der Compiler C++11 untersttzt. Da ab C++11 der Move Assignment operator (In der Mail stand Move Constructor) verfgbar ist (class_name & class_name :: operator= ( class_name && ) und der Compiler wei das der R-Value std::vector() nicht weiter referenziert wird, kann er einen Kopierschritt vermeiden. - Vorschlag, einfach folgenden Ausdruck: - image.assign(pImageBuffer, pImageBuffer + img_size_byte\_); - statt: - image = std::vector(pImageBuffer, pImageBuffer + img_size_byte\_); - verwenden und das Problem ist erledigt. - " -* removed brightnessValidation() because it's a one-liner -* activated new waitForCamera() function -* added waitForCamera(), which waits for pylon_camera\_->isReady() observing a given timeout -* comment on isReady() -* Basler-Email: cam\_->GetNodeMap().InvalidateNodes() should never be necessary, so I removed it -* resorted methods -* added comments -* Contributors: Magazino Version Daemon, Marcel Debout, Nikolas Engelhard - -0.0.19 (2016-02-29) -------------------- -* new device removal behaviour -* Contributors: Marcel Debout - -0.0.18 (2016-02-25) -------------------- -* try to catch the logical error exception in grabImagesRawExecuteCB() -* Contributors: Marcel Debout - -0.0.17 (2016-02-19) -------------------- -* README.md wurden online mit Bitbucket bearbeitet -* Contributors: Nikolas Engelhard - -0.0.16 (2016-02-02) -------------------- -* Basler-Feedback: Prevent that the image will be copied twice: - " - Es handelt sich um ein Missverstndnis. Bei dem Ausdruck image = std::vector(pImageBuffer, pImageBuffer + img_size_byte\_); passiert folgendes: - 1. Konstruktor von std::vector(pImageBuffer, pImageBuffer + img_size_byte\_) aufrufen (1. Kopie der Bildaten) - 2. Zuweisungsoperator von image aufrufen (2. Kopie der Bildaten) - 3. Destruktor von std::vector() aufrufen (1. Kopie wird verworfen) - Der Compiler hat unter Umstnden die Mglichkeit hier zu optimieren, wenn die verwendete STL und der Compiler C++11 untersttzt. Da ab C++11 der Move Assignment operator (In der Mail stand Move Constructor) verfgbar ist (class_name & class_name :: operator= ( class_name && ) und der Compiler wei das der R-Value std::vector() nicht weiter referenziert wird, kann er einen Kopierschritt vermeiden. - Vorschlag, einfach folgenden Ausdruck: - image.assign(pImageBuffer, pImageBuffer + img_size_byte\_); - statt: - image = std::vector(pImageBuffer, pImageBuffer + img_size_byte\_); - verwenden und das Problem ist erledigt. - " -* Contributors: Marcel Debout - -0.0.15 (2016-02-01 15:33) -------------------------- -* added comment -* moved cam-info setup into new method -* Contributors: Marcel Debout - -0.0.14 (2016-02-01 08:22) -------------------------- -* fixed brightness assertion bug: spinOnce() does not result in a new image in case that no subscriber listens to the image topic -* assertion before accumulating -* Contributors: Marcel Debout - -0.0.13 (2016-01-25 17:03) -------------------------- -* set gain implemented for gige -* Contributors: Marcel Debout - -0.0.12 (2016-01-25 13:32) -------------------------- -* added lots of comments, initialized the camera_info_msg with zero-values -* Contributors: Marcel Debout - -0.0.11 (2016-01-21 18:02) -------------------------- -* removed roslint -* Contributors: Markus Grimm - -0.0.10 (2016-01-21 15:22) -------------------------- -* SetUserOutput is now a service -* Contributors: Markus Grimm - -0.0.9 (2016-01-21 11:51) ------------------------- -* README.md edited online with Bitbucket -* Contributors: Nikolas Engelhard - -0.0.8 (2016-01-19 18:54) ------------------------- -* fixed segfault if no camera-present-bug -* undo set gain for gige -* Contributors: Marcel Debout - -0.0.7 (2016-01-19 18:23) ------------------------- -* gain to 100 for gige hotfix -* Contributors: Marcel Debout - -0.0.6 (2016-01-18 11:02) ------------------------- -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* catkin_lint fix -* Contributors: Marcel Debout - -0.0.5 (2016-01-18 10:36) ------------------------- -* removed all tests, they are now in the new package: pylon_camera_tests to resolve can-dependency-problem -* Contributors: Marcel Debout - -0.0.4 (2016-01-15 18:41) ------------------------- -* Reviewed ROBEE-212: Found the missing part in order to use the trait -* Removed compaibilty_exposure_action.py as it is outdated (it used the old pylon_camera_msgs package) -* Contributors: Markus Grimm - -0.0.3 (2016-01-15 17:12) ------------------------- -* Robee-212: Support for setting the digital output pin of USB (non-Dart) and GigE cameras. So far, the std_msgs/Bool topic output_1 can be used to set the pin. Only tested on USB3-Ace camera " -* Contributors: Nikolas Engelhard - -0.0.2 (2016-01-13) ------------------- -* formatted cmakelist -* check if env: ON_JENKINS_TESTRIG=true before running the tests. if not, tests will have state: 'SUCCESS', but the number of test remains 0 -* removed useless error-msg if no camera is present -* Contributors: Marcel Debout - -0.0.1 (2016-01-11) ------------------- -* Deleted maru_frame_rate_tester.py -* Merge branch 'feature/pylon5' of bitbucket.org:Magazino/pylon_camera into feature/pylon5 -* re-enabled tests -* Finally we have a find script for pylon. jeah! -* lint -* own Sfnc-Header no longer needed -* Pylon::autoInitTerm was gone, is now replaced by Pylon::PylonInitialize() and Pylon::PylonTerminate() -* compiles with pylon5 -* made single_acquisition_test.py executable -* added further tests and all 3 types of cameras to the jenkins -* fixed duplicated output -* making roslint happy, removed not working 'build/include_what_you_use filter' -* reset version information -* fixed open_wrong_cam bug -* format -* check if shutter-mode is available for the cam -* improved script for bag to action -* node to convert a bag to a action server again -* support for shutter mode added. So far only tested with Pylon that somehow only supports rolling shutter (although global reset is working in PylonViewer) -* fixed format string -* package.xml, moved rostest from set() to find_pacakage() -* fixed ROS_ERROR with wrong arguments -* Make catkin_lint happy again -* CMakeLists corrected -* writing binning-value into camera_info_msg -* fixed typo (fist/first) -* float is not a valid type for ros params, double is -* requesting lower framerate -* using device_user_id instead of magazino_cam_id -* longer timeout for camera test -* no more empty frame in grabImagesRawActionExecuteCB() -* added header_frame to action based rect images -* removed / for gige namespace -* comments, moved mtu param to /gige/ namespace -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* removed default_camera launch file which was outdated -* README.md edited online with Bitbucket -* README.md edited online with Bitbucket -* README.md edited online with Bitbucket -* README.md edited online with Bitbucket -* README.md edited online with Bitbucket -* README.md edited online with Bitbucket -* merge -* merge -* removed hard coding -* pull from master & review -* new calibration-yaml (so far not used) -* test case now opens dedicated test camera (basler dart), attached to test server -* test script now executable -* corrected catkin lint issues -* Added binning feature -* master merge -* removing __init -* new folder for test scripts -* resettes changes on magazino_cam_id -* added missing suffix in CMakeLists: -* added dependency for rostest -* renaming magazino_cam_id to device_user_id -* renamed program to write cameraname so that it corresponds better to the official naming of pylon ( 'DeviceUserID'), removed magazino-specific check of naming convention -* renamed program to write cameraname so that it corresponds better to the official naming of pylon ( 'DeviceUserID'), removed magazino-specific check of naming convention -* started work on ros tests. First test opens random camera and verifies that an image and camera_info is sent -* Removed grabSequence - Fixed an issue in the setExposure function - Removed the desired_exp_times parameter which is now part of the opencv node -* Renamed is_sleeping - updated readme and default config file -* fixed pixel depth error -* Make catkin lint happy -* make roslint happy -* Added doxygen comments - Code cleanup -* Updated launch file to use a separate yaml file for parameters -* removed wrong comment, check for valid initial grab result -* undo raspi specific configuration -* fixed trigger <-> result confusion -* retrieving result success -* removed .idea folder -* README.md edited online with Bitbucket -* README.md edited online with Bitbucket -* moved spin() to the top, added output -* removed GrabSequenceAction which is now in GrabImages, renamed params\_ into pylon_camera_parameter_set\_, moved init() into constructor -* moved init() into constructor, cleaned code -* README edited -* README v0.01 -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera into action_trigger -* added test -* realized optional action based grabbing -* moved parameter reading to the parameter class -* removed sensless auto_brightness = -2 and auto_exp = -2 value -* added comments and return false, if registerconfig fails -* mtu size now in launch file, default is 3000, inter-package-delay increased, but sitll hard coded -* removed MaxRetryCountRead & MaxRetryCountWrite Value -> keep default -* set fix grab timeout of 5s and removed fuzzy cam-specific timeout-funcitons -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* first basler-debug-day results -* camera now also opens if no camera_name was written into it -* correcte usage of cmake source directory -* Sigint Handler disabled -* ctrl-c handler -* shorter return code in brightness search method -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* fixed getCurrenCurrentExposure() typo, wrote return value shorter -* lint -* pylon includes are now marked as SYSTEM includes so that no warnings are printed for them -* renaming: pylon_camera_msgs to camera_control_msgs -* Updated SequenceExposureTimes publisher to new message -* Updated action server message field name -* Splitted the package into pylon_camera, pylon_camera_opencv and hdr_image_utils -* some fixes for sequencer -* fo -* new script to request an image sequence and write it to a folder -* new script that answers image_sequence-actions with files from a folder (work in progress) -* support partial names? not completely working -* param tuning -* Changed to pkgconfig for pylon4 -* hdr parameter tuning -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* parameter tuning for toru_0003_sol -* Tuned exp. times -* new calib for cam with filter, new exp times, removed sequencer imwrite -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* new calib for teststand with filter -* reduced log level -* changed parameters -* adapted toru_sol_camera.launch to new load_calib script, camera name is now a parameter -* new calibration -* Fixed brightness service using locks -* testastand calib with acA2000-50gm -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* Pylon camera now compiles with opencv2 again. - Bugfix: brightness and exposure servers were not working - Bugfix: pylon camera now compiles without opencv support if opencv could not be found -* added script to simplify loading of intrinsic calibration from db in launch file -* new intrinsic calib for SOL-test env -* Delete old wide angle camera calibration file -* Set start exposure for spectral dart -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* added std:: in the header, reduced start exposure for default camera (intrinsic calib) -* updated toru camera -* Fixed merge mertens algorithm. Matrices need to be manually locked. -* new launch files and new calib for sol-teststand with caA200-50gm & 6mm Lensation -* be quiet cmake -* added another exposure time to hdr -* Updated exposure times -* Possible fix for sequencer images -* Merge -* TORU-148: Rewrote pylon_camera backend. Thank you Basler for all these interface incompatibilities. -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* default launch file for intrinsic calibration -* catkin_lint fixes - * move mistyped message out of include_directories command - * don't modify CMAKE_BUILD_TYPE and CMAKE_CXX_FLAGS - * add build_depends on image_transport and cv_bridge -* added rand as runtime dependency -* Copied the merge mertens algorithm from opencv, optimized the code and parallelized the computation steps. - Removed using cv/std etc. from header files. - Removed OpenCV3 stuff from CMakeLists.txt as we do not need OpenCV 3 anymore in this package ;) -* Added missing dependencies to package.xml. Added pylon4 system dependency which is now installable via rosdep -* hdr parameter tuning -* added additional throttle topic -* (Commiting for somebody else) - Changed framerate and added throttle for HDR image -* Removed ros_info statement - Added link to exposure fusion paper -* Implemented a basic HDR algorithm to speed up the HDR generation - Added some const and & where it may make sense - Added some if statements to rectify images only if somebody subscribes to the topic -* now using hdr -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* launch files renamed (commit by marcel without rsa_key ;-) -* launch files renamed (commit by marcel without rsa_key ;-) -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* added new calib for new acA1920-40gm with 6mm Lensation Lens -* Added cv_bridge dependency if pylon node is built with opencv -* added launch file for stand-alone sol teststand -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* new launch file for sol standalone test case -* Bug fix in naming -* Delete dart_wide.launch, replaced -* Rename dart_wide to toru_spectral_dart launch file -* Set magazino cam id for wide angle camera -* Deactivate auto gain for DART cameras -* Fixed dependency issues. -* Added new intrinsic calibration file -* launch and calib file for dart camera for galvo laser tests -* new launch -* better gitigonre -* correctly edited sequence launch file -* correct opencv version check output -* cmake now searches for OpenCV 3 first, if fails for opencv 2 -> before: although OpenCV 3 installed, find_package(OpenCV) only detects OpenCV 2 which comes with ROS -* SERSOL-11: Implemented HDR for GIGE cameras. -* added named for nav eval camera, added respawn to sheet of light camera -* New calibration for nav_eval_dart_cam. -* added name to sol camera launch -* Added new calibration files for sol camera -* Launch file for wide angle dart -* Add missing image size to calib file -* Calibration of wide angle dart -* trying to solve django setup problem in cmd line -* enabled compiling on i686 architectures, fix compiling without openCV -* added calibrated transform to toru launch -* new launch file which uses the right marker -* added camera id -* calibrated lamp dart as TORU_0002_temp, added to calib result to dart launch -* removed debug imwrite in hdr generator -* Merge branch 'nav_eval' -* Merge branch 'nav_eval' of bitbucket.org:Magazino/pylon_camera -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera into nav_eval -* services in opencv case -* TORU-5: Added calibration- and launch-file for dartcam which should be used for MIRA evaluation. -* brightness & exp server only available if in non-sequencer mode -* bugfix: pylon_interface is ready after the first grab(cv::Mat) call -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* usb sequencer working -* added seq exp times parameter, fixed bug: set exp in sequencermode -* added python setup for connection test script, adapted launch fiel to try to support older djangoe version -* comment on max retry counter -* frame rate tester will be launched with crane_camera.launch -* frame rate tester writes result to file -* fixed bug: brightness service has to wait until at least one img is grabbed -* start exp in launch file edited -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* start exposure (ros-param) will be initially, including range check for all cam types -* removed django setup, maybe reuired?, fix -* reactivated comptibility node, minor changes in launch files, error message -* added frame rate tester for maru -* removed skipping warnings 2 -* removed skipping warnings -* moved SfncVersion to local pylon include and added warning -* moved SfncVersion to local include -* arm fix -* also arm not only for 'write_magazino_id_to_cam' -* arm adaption -* fixed formatting merge conflicts, fixed intrinsic_calib_loader init order -* remove formatting -* code cleaning -* commit to try on maru -* mean without opencv -* mean without opencv -* removed brightness parameter -> control brightness & exposure only using the service -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* Launch file to start dart camera -* Add calibration file for dart camera -* removed some warnings -* fixed uninitialized pylon_interface\_* bug -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* small script to compute brightness of image to e.g. show in rqt_plot -* launch file renamed -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* added architecture check -> other pylon library path for arm -* added support for toru camera -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* checking if exposure mode on camera was set -* Correctly set WITH_OPENCV option to OFF, if no OpenCV is installed -* warnings eliminated -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* gige max retry counter set to 6, retrieve result timeout changed from max val to current val, own_brighntss_search param added -* added ReadMe explaining manually copy of pylon-header -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* script to check brightness service -* CMake file cleaned -* If OpenCV Version < 3, will not compile HDR -* transport-layer retry sending/receiving 5 times (before 2) to prevent lost frames -* prevent 'isdeprecated' warnings -* merge with ulis fixes -* change from init to setupSequencer -* several minor bug fixes -* keep compatibility node for python scrips -* added hdr for usb-cam -* launch files -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* brightness service wont send true until target really reached -* revert -* publishing camera device name as parameter -* second fix for 'The image buffer was incompletly grabbed' bug' -* fixed 'The image buffer was incompletly grabbed' bug' -* tried to fix 'CreateFirstDevice' Bug -* edited sol_cam.launch -* kais changes where gone -> build pylon_camera_msgs before pylon_camera -* service in new thread -> brightness search response will be send when target reached -* workaround: no new images will be send while own auto brightness function running -* code formatted -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* auto brightness working -* return values now set for compat node -* hopefully fixed merge problems -* improved interface for exp_caller (and no default camera anymore) -* removed old trigger srv -* Added sleeping service: set_sleeping = true -> pause grabbing images -* brightness as reference -* workaround not working brightness srv -* fixed usage of wrong service -* updates -* works now with yaml file entries 'rows & cols' and 'width & height'. ULI -> pleese adapt if uncorrect -* works now without opencv support. PROBLEM: WITH_OPENCV: wrong 'setupExtendedExposure()' function call -> extended auto brightness function not working -* fixed dart segfault -* working on WITHOUT_OPENCV support -* brightness service working -* service still not working -* fixed double corruption bug -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera into review_marcel -* pylon_interface = pointer, undo ulis time out change, new problem: low framerate -* works now with yaml file entries 'rows & cols' and 'width & height'. ULI -> pleese adapt if uncorrect -* added dependencies to make sure messages are built first -* works now without opencv support. PROBLEM: WITH_OPENCV: wrong 'setupExtendedExposure()' function call -> extended auto brightness function not working -* fixed dart segfault -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera into review_marcel -* working on WITHOUT_OPENCV support -* added compatibility node, improved exp caller, removed cyclic output -* brightness service working -* service still not working -* fixed double corruption bug -* added pylon update file, minor changes -* bgr not yet implemented -* renamed file, move to src -* made launch files user independant -* bugfix: if no intrinsic yaml data in opencv case -> publish only image raw -* CMake adapted: if could not find opencv, will automaticly build without opencv support -* Version 0.1: Usb-Cameras working -* working on extended auto exposure and software design -* sequencer working the first time -* ROS Style Guide -* ready for review -* tmp -* rectification working -* set exposure in mu_s working -* Desired Cam using magazino_device_id, support for USB and Dart Cameras -* bugfix -> before: crash if no intrinsic calib loaded (out of mem) -* added cam-matrix to initUndistortRectifyMap -> same result as undistort (without shift) -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera into sol_demo -* runtime -90ms, fast undistortion by replacing cv::indistort with cv::initUndistortRectifyMap and cv::remap -* Pylon Node now working with Basler USB3.0 Camera -* Pylon Node now working with Basler USB3.0 Camera -* initialize camera pointer -* last commit just before bille move -* small fix for failed exposure -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* better errormsg if exposure failed -* added ocr cam launch file -* small fix -* added condition for second camera open -* more output when opening camera, no camera is opened if identifier is not unique -* less debug info -* small fixes -* using old method for usb -* new launch file for defaul camera -* work on native exposure calibration, Problems with USB -* towards better auto exposure -* intrinsic calibration yaml now also supports a comment-string. This can be used to easier find wrong connections (crane camera loads calibration of insertion cam) -* crane camera now also only looks for ip -* new launch files for maru2 (won't live long) -* allow to use IP only as camera identifier -* removed second entry for camera_name -* demo tag -* IFDEF DB for calibexposure action -* adapted to book_gripper -* added smoke test -* no default parameter for cam id, new launch file for kado camera -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* new script to test exposure client -* if in launch write_calib_to_db is set, exp/brightness pairs are written in table crane_exposure (should only be set for crane camera -* should compile for Maru usage -* has_auto_exposure will be asked after opening the camera -* working with cam acA1920-155um -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera into book_gripper -* working without DB -* merge -* fo -* new launch for laser camera -* added missing opening command -* removed SensorID' -* now also working with cameras that do not provide auto_exposure -* bugfix. new max_exposure was always set to 1sec after search converged -* removed default value for param_file -* changed respawn times and node names -* maybe speedup -* new max exposure of 915000 -* new max exposure of 915000 -* merge -* less debug -* lesse debug -* no exposure via msg, only via action -* more exp -* exposure action -* new launch files -* towards exp action -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* small fix for better nodehandle -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* towards exposure action -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* new camera id for crane camera -* higher timeout to enable longer exposure, ErroR msg if requested exposure is invalid (current max is at 916000) -* exposure calibration works -* now with functionality to calib exposure -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* new exposure is checked every frame and update on the camera on change -* changed name of pylon camera node and some parameters -* small bugfix: handling missing yaml-file -* launch files (again) with respawn_delay of 30s, node now works with usb and gige camera (so far, only exposure can be set) -* towards usb -* now with cmake-option for QT-sql -* merged -* added 'add_definitions(-DWITH_QT_DB)' to CMakeLists.txt -> db-libs were only linked if they were needed -* pulled from master, saved merge conflicts -* interface now working for usb and gige camera, exposure can be set again. new define WITH_QT_DB in PylonCameraInterface.h that decides if sql connection is used. TODO: move define into CMakeLists.txt and only link to db-libs if needed -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* Merge branch 'cinstantcamera' of bitbucket.org:Magazino/pylon_camera into cinstantcamera -* switched to CInstantCamera -* corrected crane launch for camera -* including usb cameras -* better names for camera launch files -* speed up, now only publishing if someone is listening -* added respawn and default exposure fpr pylon -* new param for intrinsic camera id (as given in db) -* less debug outout, exposure default to 500 mu s -* exposure-param is read every 10 frames -* removed some old debug, all other msgs are now ROS\_*, exposure in mu s, params in launch-file are now listed before node -* now with new param: pylon_exposure_mu_s to set exposure. A negative values enables auto-exposure -* removed debug -* added check if camera_frame is in tf-tree -* added camera-frame parameter to launch file -* Merge branch 'master' of bitbucket.org:Magazino/pylon_camera -* merged, now with frame as parameter -* added fixed exposure -* added fixed exposure -* using new reference frame name -* added option to set exposure to fixed value -* generalized camera selection for a distinct camera - Conflicts: - CMakeLists.txt -* Added CATKIN_IGNORE to .gitignore -* fixing install targets -* added install target for launch file -* small adaptions for new sqlconnection -* now also publishes camera_info and undistorted image so that camera can be visualized in rviz with projections -* now with correct timestamp (using software trigger) -* more output -* time of last img now written to DB -* corrected link error to sqlconnection -* now with launch file -* node now sends image via ros, connection to cam is closed if node is terminated -* initial commit of ROS pylon interface to basler camera -* Contributors: Carsten Zumsande, Kai Franke, Marcel Debout, Markus Grimm, Maru2, Mehdi, Nikolas Engelhard, Nils Berg, Philipp Schmutz, Roman Mansilla, SCITOS Demo User, Tobias Wohlfarth, Ulrich Klank, zumsande diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index e0726870..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,187 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) -project(pylon_camera) - -add_definitions("-std=gnu++11") -#set(CMAKE_CXX_FLAGS "-g -Wall -Wno-unknown-pragmas -Wno-delete-non-virtual-dtor -Wno-unused-variable") -set( - CATKIN_COMPONENTS - actionlib - camera_control_msgs - camera_info_manager - cv_bridge - diagnostic_updater - image_geometry - image_transport - roscpp - roslaunch - sensor_msgs -) - -find_package(Pylon QUIET) -if (NOT ${Pylon_FOUND}) - include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindPylon.cmake") -endif() -find_package( - catkin REQUIRED - COMPONENTS - ${CATKIN_COMPONENTS} - #std_srvs - roslint -) - -catkin_package( - INCLUDE_DIRS - include - LIBRARIES - ${PROJECT_NAME} - CATKIN_DEPENDS - ${CATKIN_COMPONENTS} -) - -set( - ROSLINT_CPP_OPTS - "--extensions=cpp,h,hpp" "--filter=-runtime/references,-readability/todo,-build/include_what_you_use" -) - -roslint_cpp( - src/${PROJECT_NAME}/binary_exposure_search.cpp - src/${PROJECT_NAME}/encoding_conversions.cpp - src/${PROJECT_NAME}/main.cpp - src/${PROJECT_NAME}/${PROJECT_NAME}_node.cpp - src/${PROJECT_NAME}/${PROJECT_NAME}_parameter.cpp - src/${PROJECT_NAME}/${PROJECT_NAME}.cpp - src/${PROJECT_NAME}/write_device_user_id_to_camera.cpp - include/${PROJECT_NAME}/binary_exposure_search.h - include/${PROJECT_NAME}/encoding_conversions.h - include/${PROJECT_NAME}/${PROJECT_NAME}_node.h - include/${PROJECT_NAME}/${PROJECT_NAME}_parameter.h - include/${PROJECT_NAME}/${PROJECT_NAME}.h - include/${PROJECT_NAME}/internal/${PROJECT_NAME}.h - include/${PROJECT_NAME}/internal/impl/${PROJECT_NAME}_base.hpp - include/${PROJECT_NAME}/internal/impl/${PROJECT_NAME}_dart.hpp - include/${PROJECT_NAME}/internal/impl/${PROJECT_NAME}_gige.hpp - include/${PROJECT_NAME}/internal/impl/${PROJECT_NAME}_usb.hpp -) - -roslaunch_add_file_check(launch) - -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${catkin_INCLUDE_DIRS} - ${Pylon_INCLUDE_DIRS} -) - -# Add library -add_library( - ${PROJECT_NAME} - src/${PROJECT_NAME}/binary_exposure_search.cpp - src/${PROJECT_NAME}/encoding_conversions.cpp - src/${PROJECT_NAME}/${PROJECT_NAME}.cpp - src/${PROJECT_NAME}/${PROJECT_NAME}_node.cpp - src/${PROJECT_NAME}/${PROJECT_NAME}_parameter.cpp -) - -target_link_libraries( - ${PROJECT_NAME} - ${catkin_LIBRARIES} - ${Pylon_LIBRARIES} -) - -add_dependencies( - ${PROJECT_NAME} - ${catkin_EXPORTED_TARGETS} -) - -# Add pylon_camera_node -add_executable( - ${PROJECT_NAME}_node - src/${PROJECT_NAME}/main.cpp -) - -target_link_libraries( - ${PROJECT_NAME}_node - ${PROJECT_NAME} -) - -add_executable( - write_device_user_id_to_camera - src/${PROJECT_NAME}/write_device_user_id_to_camera.cpp -) - -target_link_libraries( - write_device_user_id_to_camera - ${Pylon_LIBRARIES} -) - -add_dependencies( - ${PROJECT_NAME}_node - ${catkin_EXPORTED_TARGETS} -) - -catkin_python_setup() - -install( - DIRECTORY - launch/ - DESTINATION - ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch - FILES_MATCHING PATTERN "*.launch" -) - -install( - DIRECTORY - config/ - DESTINATION - ${CATKIN_PACKAGE_SHARE_DESTINATION}/config - FILES_MATCHING PATTERN "*.yaml" -) - -install( - PROGRAMS - scripts/file_sequencer.py - scripts/grab_and_save_image_action_server.py - scripts/result_bag_to_action.py - scripts/sequence_to_file.py - scripts/toggle_camera - DESTINATION - ${CATKIN_PACKAGE_SHARE_DESTINATION} -) - -install( - TARGETS - ${PROJECT_NAME} - ${PROJECT_NAME}_node - write_device_user_id_to_camera - LIBRARY DESTINATION - ${CATKIN_PACKAGE_LIB_DESTINATION} - RUNTIME DESTINATION - ${CATKIN_PACKAGE_BIN_DESTINATION} -) - -install( - DIRECTORY - include/${PROJECT_NAME}/ - DESTINATION - ${CATKIN_PACKAGE_INCLUDE_DESTINATION} - FILES_MATCHING PATTERN - "*.h" - PATTERN "internal" EXCLUDE -) - -## Testing ## -# All Jenkins-Tests are now in the pylon_camera_tests-pkg -############ - -############### -## QtCreator ## -############### -# entry for QtCreator to show all files -file(GLOB children ${CMAKE_CURRENT_SOURCE_DIR}/*) -foreach(child ${children}) - if(IS_DIRECTORY ${child}) - file(GLOB_RECURSE dir_files "${child}/*") - list(APPEND ${PROJECT_NAME}_extra_files ${dir_files}) - endif() -endforeach() -add_custom_target(dummy_${PROJECT_NAME} SOURCES ${${PROJECT_NAME}_extra_files}) - diff --git a/LUCIDCamerasWithROS_v1.1.pdf b/LUCIDCamerasWithROS_v1.1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..07ef72efb7e14ed430e0e3c4f3df1e377bf3437d GIT binary patch literal 578089 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 z zlA$_3jq`U^jqAUDMLCs{Sj!?*E?tfM3ZV9jcyusPn*Hf&qX|;$DH4}pCl11iqt4FY z;)Y9h9+U>hlj$|XP_QOrrNhT$*7`%2{TTzw1wY-C z!i6+NHa{nYLZ+h2`CN)Eo2ZJBI}bZmSo5-&RwFEzud);NFiWZXSFG5Fd}#OT(W%5q z%T6M^4{~Gh5VU&@w#|MN(T=MqyO|8%AC)@>$w)ptG#oOI3zKn8eexy)912xBfs37J zxc2Db4%a2l1(4&TJt3EEakX5@j{>^K}P}W_1v>J!Wqqk}%V?H@k>frSCQNtR`L2-n}N`CBgUn@XX zS66?vyNLxWP?8auob^2e)x0zbZ_p_7x$@Pqg?9Va>b|j(MMMvz)Ry^r@Q2%tnm}k* za6cbU;xY06Q5jox@53nz+obmRq0|2KLlN{CVwO1={!<#ExRPvjxflZ_e8U>l0qWoln-~K=%!{M6t?WUb5T8kBDo$cQ@^udC^ zeit-p9Y`^y4yB5Npx>c+kEa?U|uuRU*W{m5Ob(NdYrCaY5GF2Pi0p)Gej4*IS3E?k73guZNBz(9PCk;iN@9n)_ zePbM5)1$N?H>C#-^)EAekgep)`$dfRC&Tnq5i9&}12$UPZ-{n#IZyb!iEk8@4#AGz zd+cmQlFq{^)ZyN%$MK>B@dy^x;o*->lJi}IxwQPilL<{-M*{*IBmjIag`rG{(gP64BdSib;&q78ajvinWvPW z;AS3fEkniQY1mcbCci&}U^2rxH{MBwY9xZfAvj=!Y{~0PL=T>heR3n$k^iU z538vmOqPa@J-2O&k&p5=9)lcU*?mM8%d#vn>T@|u^~GjGTxT5Ac}VtP7u#LS5l?;| z;y{i3o85Grl`nL*s|ErTma}eEl{q?Rz*{r}Y~sgFgqoMcdh=!-KsV!xnxbbmmqoA$ zU6b}#zNoT(;x_(+`p5m4$R5MoJtRav7+`?2YsI=fxUGAs)y2*EYXwj~+%MENIMSm& zT76xq)-@0x{(GWSn0!o-%)t9LGE`UQ^lHC37nI%OG$rKo-)TJEt7+B*(Gq&k4oj8( z`~em0C|$Mafc#U~6ECp&gKkcoYOIYGoe{9RqSDkk&uDrv>EK(76>mve4~aR!_FnFg zd$96+tK%`t9}q8u+UpQ_KKkz25pBV=DL>_3=humvH$n7E*H5#^^`?Z{d5Y3}$b2-_ zxns-rMDgGIn*NS~r#W#Hm23`my`$7En$c+KihU~9M01ZxwM=qN<71s6_2WfL+(oVCYpj|gQXiDHu+4=%ML+=iHIMgKe= z|7?&RZ$_k-lh)hfc3^TeJyJipACK?@O>OeKciWZhY9Ge`AW76TPsYz{Iyet}`0a>A zTlQs%vyF{oDCp_wPtPTXoJCX+^Im9u!A3E9n9N9=6qYKBj`E5HKu*Sb9|3jg%g#l% z06b)Z_5_s=DRdR^;F9&G=!HVggP; z_b-z{%pS(GJr&-8D9S!lHf*yAY%f;Au$&QAW)PO#Y*ymobG~eg>1%lnFPAZfUG63b*ypn)oUW` z*@vm3(##T6D<=-k-r_Yo59MnR$cme?t(!}(8dX-^sMd}jCqHc96TRd*Y%|%4OWbOs z^a2|(aLN<_33cODvHY*oaF|4U^je8AEvYIZGnm*JN4npY25Gt0Js8o&)lrB*Ax52ESX5`D<$zZkSyz zP3wE(Xt$H`xTT>knsLb2=Pb*8?oY=jKp&3n?IQb!+X+03H<)b`ZD#PB&eZ>j8ufWV z{rv{dnzK$a5&QCjmu-^Wn3WEI4hq-QOrLAvdessd=Mn1jt~wlk<%gn7U_ZYvDt@k` z`^^Qf1U(~qZ`4btWULqJFh%LDBNa!~H?H%f9`j^ZbF#CE!e!vAG%?dX+}{f;*ya{% zf(Y$*0U)EuzrI#cR#6mSgAO}>ZU1P&c#sj6NZyhUz5O8Y>- z^R{403177GSEwRE%N2QVaI2->B;2yFC!UC{!xVN3b-HDy1X7r#m=$N+Qmp|L#Z9Zh z7b{QnPXlMa(CXvH94W4Ss(ty&D0oYb=f)K89T2^d;v0H<;{OD}HCf0li71(;JtD10 zKYh`bbL>+;OxECrzIK!qAzjCyjuxooi;!W#OEO8KW88k94d01opS{sgmrKd*B6D;Y zdU}vUtSf)WGq4uagw>(0)|ztDg@E|US3MV9gX2+! z^1=Tp|8Y-3bJR*ACrQvNq<&(@8;Kq6tW&14*s*w(%_PAW?N(>MesS7wdjiJ4yblsI zSVyfiC53#Ab2sD)0BcBaW@`6n`2?cp6{+w;nCA0KDgbappA^jcln8;(4+1!EPV za-KNVt}U7ao{dw}RcD?0d}KoX#G#}VETx=hL;S(UtOnC13w0jYL-ii+xY;Z~P#?EI znOSh?nRpLMsfmvQV!V{X;AIC)&n^(6`|f5a^EGHHd~9vYUwD)D#e8-s^Lqz&XS8=1 z78%|P5B>_x!_7_mvv>tP)KISULuSX^(?XqbU03Q$7X++-0jJt@$sN|~WNHlpPTl@`rpxzLTXK7>~eiknK~+Q*(hf?^}G0+hOm zW4 z%SV3X=Gn;554fSAW#8_N?)L}qy#KQk#|eP+S8K&BD0Xx~!;N{0?4<%Y<^{X6^x) zK4RlzsJz%5H~`@SkTFL0aQ{!#`wi+R_|{sXSEys*A%8eZVn_dc6jRc5eJu7Ys-7*3 zK3=h?gJbS%b@n}bmM7|P&0?Xp%&s4Zo#g|?wLIwV@x>M|bAkn(oP0)W={`1*yz$gB z>iyy(OZjq;9HdPYO*RBE2;>oI>`Q-b*kYuiWDwj>*Gl_!^$JNwSpWwsa}R`NJFWEd zKOnKsK5*!GnGh21Yn+_OvV#Ux3X5U_Wue8LZLyN%Kn^k5$=9v^g2Ni*@6!hc!{y>N zxkv{;nVW!VRc2v`R*(UvUSvc?VBg!Xtia^pHPd)i>+6`VEy4vYDDUl}SZ?F~!Q58t zNtE8$F*RHI!-PDU@EhI_?5ciEVZ{4&%reT?wmVP!)dm{R&X|%I0pu<=khoJQ=H_Hf zbe3_kzrSBQ0lke1}~r3XK> zz@c+rr2HA!>DrWd(|*RVJR&D2r)WWVgSv}={hArea*1SW(0jQl)XxR5VYw;8BIDB( zQ^I2qIwLeu@(Q=wZ7++~J*$dTOk~2QwIt$XXLLPk0$ko;=QG~zKHp=s`+EWtzd5|X z1W`^xLIRg$@GO}go6Z^|RCvBxN3C(bZY}eJQ-K5%E`Rb|>O9GMis?V|P9o8J6ZC`+IMHg= z?QKn|^mE$dK9QH_AjS|ht00qJh}=~C)?#u08;JccE;YVF=stk`;Jg%t8Wm;y&^>71 z4+ASth`wyh1~rt^D7^%!p$K}5gHC9ebAqb19-3#U=oXAEnI`}SD(UQ2u0A znRuF-ieem6{&0M`?m?9a6DA$@yT$v8zkCO3{FP#0Fp|Zlrz=^eBe=rbyaZPR-Qv*f zjXJjNKLTz+S8W?qazayp5IWV~x7-kY6uK9$sBNW&2=8R%Rtr;FHv1T_oX*X0Tx}Fx zX@ZZTGd+0)I(n!W$HpM|E7B(2@FqHh@w$=kE^^9H1AA2`g)de8V9g?|pnU)EXbv^B zBF+TuZ-kVAlAp!LL8o5%DIdA87bWI_CW?1Rg(tWDyn!0hp*2i0m+(1~My!p&6#!u` zDJj{@rj;>=%U&?~q8m*< z)jFVA5%^#~R82*W{gg~t6aV+Rg_cRY?99|&JLtdT*RB0;H90Sq)95AwR0i2PIcaivkaF>r zU&rC2pGdMi%oov1g#K+K0F+! z+m{V>esU7A3N#E*U6N5VI!>0^VVkpH0Z1gYI3huoQqR579nDJLb_J+Q{J_!tnE%?o z2*4zRG00cket%(n3`jEoWYYc(%}CX89#rSWr>i=l3D)41CwjpnFlHeu#$;&ivjXL> zYOWuJKp)L;6c5l4D!f!`3PQ%giz*%AP8=9?3G%Ux;zxrr!dLomZf$Nx=h*OGEqmgY zDxjIMX8uPP8`L)`;-lu-V2S*=m&yKAf-J1#?Mq#Z)y-@=UU>)*vBz(VNiuz{EdR%c1l2!lbIn zFOaGzfM-gzele*({v(4p_j0?F{zC*7PEnZ-r(8~Uq3>;-xlX+6(HS7xna zy*q#Rs&drV)}~Y$p}@C}Xa0Kyv*SKm`%yr;!@5t~ly|IeXsul)8r^3kl3XcB9?W4K z?eo(@;@AG*+<^bx6W)WUBpk(EVQo#K8Db}VnM>Mnz!@1OuR9)mvk1k;lGdiPP z3NJaBrhuleGwBIH#OQX)k61w8!>~>A9thA!SOsC*54<`+(K4;_xw23@gzi~6-5;Mr zWf4&t8t9=a>3u$Ao@|`@OQv*J=R)r-w-=FT(E;IE?Xmaw2+DDvH)B9@;?H&>Wtz#q zZa+duO!@n4tWk)AP6~F`5+L-IwekTDogNhgNP=o9sw|Zh)u%7Clr)nsZm>!p_p8ju zLF$1PR+$O!3ne@rYavGO%uLG_IWYowOgf{(ATU_M-&j1OBec4LG@`|>QsJcC&|gmG zwY4pRUh6cFy!47a0kt!Yl5jWIxHlW%ktkzcFI_rUm_&Ae>2^T!D z{m>FpZN6XZ>|?=nY5lHii#P$u>c0v40v7!xe{tqLTL3v0v)6Lh#s#lg@>6FVoH4b z8=(bm03;#*q<6_4j2)f$uld}*!uHrE+I_y9^^wKc5O)ew++*E-XN~zhT+7AG z6qu;7{fcDJHqmSa^6@Ih?|EAu`I?paU=aLhpimz!tRR=NWbs{V;F;z?bos<~yeUop zDz|Grm2RJm+|y?ry2>*h;8=HR*0-oR5!p2CA@y1^$S-8;o&_bKdHsdHe>0E5PIp%; z(T-GU^|A;<&2QEYEl!$c+ZL;dDie@qU%K^V`zkNo+vcCd%;3nt_&rg=ij%ud{5B{^ zn61%=A$9rgKqeVYqoHC;vBKg(0_KrnuPfBZeK!^r<16!ZaF;pf$9+l`QXX=`WSlxz z#P_e3AAJcp&1Ir$5fBpe{qT(m<#mVl_|KWD(p#YnfZ(|5u*~8H6w(ty_oHm;ZvpOC zdk39|lsxzcnw1J>Mt^(IA&w#!z?O(rLP9cE{>`}av$IFvbu387Efe+Ld5F>^l&xT` z#JW?kojIK80TBwSRQ%#LZb>{&n#Zwl6~2|WA((dIQ}0^90PfXS?T(dFih7F;AnG_6 zRf3~!VuY^)1$H;PWYwZ(8omdRS);`*?u>qa)X@91y)_FW`6BT^C&_Jiq7kBJ;y9XjGs;`DV zGm*~l^>R)Nm(vm9>`!cBNFnpSnfn^Y&>v1mU&p5i15lNd-gPZ7!lHglONVw~;a=Ob zfVIZEJUjAwlMW_u=2S}gCYwXc2M41Jbo^5p1P2y@5Dug_}H8|Jw1IrHtyfdd|OUTOxzEV^b#ge zd01)=@DC~Q*Q9=%+?_+2^p`SJhCBq%CNG?lchCcf3L;EG>Ig=yzk@-+CH3hW;8j_l zfa0nI>;hLKLCIOo@xOcST>1GB*i>r5Y5sZoY_VRm&k-EPMsOI39tW%9=#f~nUUv=n z^vF+oQ$D8JQ?i_>mgh}j*-~reizpI;O=?F9xvgd@J=SW9q`Gr)52AMY;MNBR<@ry* zs765YYZTD#di5TfAmvb5{ftHr{8UC@<36&Emj~;_1SFdl?^Wg^a_=mF4NLRvTn#<| z5KSj>2M}rFKskfx8wK0OA&=V$skJsnPl>Pm^6zqO>t@&+&#q*zrpXo{-ardDzyMG< z3V`4H8U45H%Ns{bThaeg)Q=A6HC|SuF3;pA=q>l|KS}7K(?aNGoJ8?W)|xy(x~#<> zq;-}rhdk(pI(oP*ZxrD7Do?9U=N$GOmbjrOw- zfi<}nOF^D)TSHqIfuZu{++%;B4Oy$2>Y+9Bqke(O=3}LOqMN>j1@rg!J(Vp5Y5D@{bl3ZZ*J^a9vS!IrCCM_c9;>cFAY3`v9Ww!1 z?Xw&PyyeG!`<5u9>7YFTNdL7gw8_Hg^Xue^MC_J{shR6cMHvow*I89Jj7)p)D0qDuL!S_8~SE>drT%)POg6a~%Ag~n?Q#kH5N_|#osi1It4nb9d4 zqVTg!g;)@P54}JEO|cU=l$__U_*rjP&TLDsInVZ zulyxpmbz4N)GyP|#0y>eDa%qBk;wyzoNTf`hE)(MB!pM!F9-$!4_amq5g zb*HgmGUxj3f?Mk)(C6Xfjx!*1N+`lCYLaIo=qL@4`tFABF)U0DaF>>3b~P|GiIxBZ z{`AikIXUnHj}7dr{*qfT{DHj-SIaO@`h{u0&s#~56sRtE_d@n(Tu&{-CJJq$39mTW zw5fevK!lseEgxNT9sq*imkft@EghfISifCa3>tB#Cx_hGF^GQRyzOpuP7yP-pJq#k zO{$>0HcWeHCDCN`i*g@9TE?^UzhQiX!^`^FnAu2(K|Cf2YR<4<+z<tZVf-$nm1&30a4@>x+Vz*`R0SS*RC)~G%|Wmn_fj)&u$mPO^+i; zG!~2(GTzsg0l5;)=B;G1O0hOF3{U<#C4eak|NDK=`2C46^WnGI$3N>a5CX_g4WREp zJos}dXXuI@ucLW@jF9zR03-ZZUOt~Nu9Z&XR;Z=9Vn*2tMxw+M)6uqzGMkW@W zZeqJo^gy+++`?o-cH{8RTj#X~oaBP?H^F%YE(p)ZpI3KI-W@oL{YnQXeW(;KtcY!v z560p6Hq$fU=KpY3`tJh8ou`V-@MTd{5B>o-p)Rpti5UZp9P!h>GB+1| zt*4u>!Ae4?ErFC^RHvc6}cgsy#Ql{_BeyFUTL$!$eD%|#@YhrbN@O-lug1L zJG>~-tiT$>kE#lmx1{@|_v-xgR6NVqpw^ZYMcB?{me@Bk9u=}0cOkBcka zWXP*kqo6k#3BZ3cX)f8giJ`$@xSS3v1IC5u){LRz|79^bQ@cwl*b^z12U4~h4Dc|n zHKiP+**tw9wV4ipWdAD2FDCv79!nTzl8i`day~IoN%XJ6?C9fIh5H)$k0s2x;Ec z6+N?$q{9K2ws3Eahu7KUIVtM7+x~VFMap^t^fQNWFsHp@=#T}DjL6#C3#(Ay-dqTptjXVJ+XN?r z=$9lS*-6guURV4!d~t+|QynIS{Ten`wiy8w@DW15bl`JZF4spQ~LyKGHU zqNl-SVPiDF<&Z*DVYuSIbZZ=m`f>Nv8`uRNjVku^37W$Dg1H>$>vpT&Amr0-q1) zjI1y7NYHEBxae#d;23*13BaUDWS5)o3Sr6L99SQ{ck=2<5WFpTrbvZ^BLstMVuE=bKP ziKck@9=wHBM!Uj^1HV-S_*GYspm!JUK}w*$UxPOtF%ry2+OA?dO5oFimW5Tm#d^4t^jX|9-W?zFIGKjt1_uP7ZPH6YjRDr{vnznLKKlsR5kZv$W}cRBSR1Ed;RN0n zH}vBb5_f^8DdFjbGF?Ix{d^`|v&_gY6)w}-8wPv>goM7!ebYag4D+tPlW7>Q=K@o< z{rURN5fV4H<-XUCnVrU|z6N$YAjAVH?UN-~rWUf_ZfhT|HVP?U z+1G$BJ3Yt11@Fq^pKuIZxayFneZ@hmah0Cj0~RT6{PrTrZoeaeuLnIx!KaLW?Ucv zvhK|t3_+$Hhgd$E33-Z#-uX@iYAKNG?Slr4*x`W^T9>r5BVTcu(sHlon%!{SzmW*U zY)lixt6pe}(zQws5>Sup^AS++%%?Z_tf$QwOS^4H@Y~G2lbams9kLp*0>|%O>@8Si}+b(x;(QC3cyeGop zrrE(*IVr68N&%R&kk#Iy%iQg|PxBDCfW#ZZ7~_V6!||MLi=k^>RRH{ek2nwu2CQxY zLhwGI_sY@m@2rsTY4GO&*D@d>%+d*bazJAzcDGevj1`{6An&wH7@S|0=Ez{k_&6g# zLPl7bdiT_SXg^!=567s49sGSx96(Q$DmUdxIqOT9PSyEx<7W({uei35rW@Cv@MeKD zfu6*`Ye?v8IGS=*or8DX_tE}Kq27!IJ~e>Uq_>l!!+%u0oD0s_67T%T{q{`9445Xa zKG+c8eJAY0*{-AiM4N7oZA@K{7Y}hPH9Xa407IsQ0wc#)Dr($o?!t0L*2jP1{8fN5 z^1+;OMrY?Wus>rCS`!bN)si|MI9T75Dx+!wqk#KH_9hlA0uE}^a#SqH3p@S9kp!zCJvwgTe+$`-9NDn#x+jhPvS%4=j70$sK8xbbxzYUV%jDj}LA_0* zoN60!L6~5h^(e$l7RYU5DTx*oS7K5;T!sbn^uTDaT15vOVR|AQ7z@|^{gU}nx+0Kn z_>&)cU-ikjE4u~hu-e7_1v$-|X0WZ^R+{*&eOskfZnY0jZo-1hIMEC5OfFVeL(eeA z6w;e`Ul;t|{5yJhuDf6XoXqwCVW-ID@Xb zu!OH|%+ESm!eaWdY z2SCHSAcl5Ych(lX-8obZCW;SPTv#@@`IutwAC5y&Exd#Q5 z1yB?Cu7~iY~uJTIKsn32p~>Bd$0dM zp@J1jbDl(9xl8Q!Cm$f#D(9$8gXxhCd2+G_TPMaw$@nspzY~0xW#$VuARZ7QJKZZs zP3zvW6EzdCHRSuChb9^uojCg4wZu_t7Hb9N0{5!boJ85#8ds(na$4;VW^|tNbkhiI z*bXo+mqP}NYfYqJ%QQC6gzV@(Ej%q$gnYEs)1|K^FdLlDGe-yb``@}zUQx8rOmeeg zU;o;33J<=JMzuj|7*vG1+jozIV@}7ADEfoJTQH!crXpA{EC^_mz2M%T_X*uC{ySN) zd#->mn#VYKK|o#_*ICq9O_bN|Fl&482o7H{0}^lIo%pN&=@YfmoOTK!HG(a3e`+bpg!U`66#f}CZ% zq!_4fLoScqwB0JODz;&?}2O zJ2ZV)Izhnj1X>y1xj0fQQf2nAC9Q-rwzBkFI1mR^Rk$kF15UO9C9qe&II4+vv@H#E zzE(H)}R6^VXrjmGj@r%-|@53*pBm{`leUC^_m|HA? zKdf=Z3b0%PuCT6~iPD$AJ`2qw9dQ+kYDlysLWcbmoeB(01OwDIGUU6B>yaYz740KdkgW0t#;eqsD;7 zJou3W@(oCOe`LSia!9cNSgw*eY}tWnH@-wmt1f@L|3+Ug|6w&4P6KgSu=$9p{+e|8 zqb{@h7gsxj&FJvkm8NaWh>fv45}#tWGCU+A9MW`6fXS)-&ebsPG*K5|smGi3fZJr) zpE?i{m=Ym*q!TL<2?|Gh{a(>;U(&ApEEl1w94M%COKo{>_9wE1{u9tqnkLP2o&xG* zsZ9>Ao$ypvtK!Sxzvh<@ytuz!QNlowXI8$xo!BY9TF^&Gp0l2>_%S0kEwNii>&bQ3Jeqoii6Ozf*z3&&Vh76qFuKU(xJqP1EPf^`OTV+XxX78m4N_R+{nj90 zP%x@ZcNDx5Ew=J>zrRFo=yqnsOR$0FWNRx}jIQVlAWrJ}#+gDFr}@FuS2@y2bMzM8 zG#ak({pE)hRY~DQRlbbSc-GWq_VPWGZo&Pmvf2J!^52p0zh)Ts7*N{#%CWv>27t!R zz|9a4)Pq3v!<#EIn{@ThL_Y<2`FMXc*iCdxHoF#}aIgaywAB;Nun6QWwDM5{Fu3HX z5+A_l@s_Xh4+YXU#lDAsCDfL-G7DM*TTaM8cBqL57Df!M$7pD%-XOaV#k`>5Qfd;w zKICswh37~@3%%zc07)ga)Eexgs`685gY+8$?xVFK18NhYp{?_jGTB~Z&5}yTjS)18~@89}dCTil?YmmcDp`eTxw- zO3v1(p8FuElh%clo>fx!>vAj~<2NnfU8CdT76uM@SAe~A@jKu%xieN@_ery&ft(a> z2Mj+#^*ON5(veI1b111|R$M4q`?0)Z(EpN?ryi9>fdcKU@hV5z&meif04ZxxJzT4NvZ zs78k_Vo!GYfYIw3a0};;SLX)>1Jky$F+|%f8UPgF>=ina0v5C319IS=r4@J(#X;HI zndq)FU$k`VY!#~;b}vmzo8{-w_Qq)7MM=j{;Rz-nZQ9DMi2q~F-4PDh4Qm~`88bC= z9mR^DyBGQK%+Uis0e7Dx)*qn@pB;xKHGQvTkHt#gJnHgq%#G*;&UIi8@#s1%Cu^fT zvBj1n_-}tfq-tN>xOGh!AeupKyqQIGs!R2G#lsY@w@EjVi_je;j2nlk>->iq1ds?v zRLHj)80yeM+k0Zb`;7d%H|kEMkIH;#N_oLQIN}&ndQcwms)B8jzzos~+?Swno!Y`i zcL_G&g@CcueZ5h@V5nQDKdl3RkpD!@oy_Ga-VE&ivR!frlE&UtCE1r6--8~~u%kP^^{ z3OCr$r-J-V6sF3Gf>5XAZIZoZu4KEhr$yR90?e_+r){&uK7xPklE9iYJXizPjgEd( zON$Yow^qD%xHm&tHPccl6cVZFkSNLW(-H~Rk9HYWWpS^Yc`_HEe30*&UVQ-MdCR=D zH#Aa3i9m`qamD5&*l+wdmuk(bH-38Nj{~UKK8z8~eBL6MsUGl_hfNZf)2V{$TZqBAG?UcuY#=;*Kft*?i;iCQX!=7FKIUX z4{}1Z3+x`hf#)aAc5_%U(9@`O{yf;aUjGaE57)}HMbO%`P-q@|E>KfJ6W(IK#Ct3O zD+YXQ4g#!fBEYj0dhp|Z-nyoDnheIHzX0W5VH8n%ctgUxukLQo^B(mVx1I=_iU!9$ zx%=?|Se@nN+qm{1U0*|Wt zpj;K7uXhEimWHwwRs?^}_jfp~6r31irlm>msXaP^GI%`K#{Hej@ju7aKQg|SE2kp5 zI)(#!p&Z}VIPfhUeEO8^(EZ`{8ME5tM0zw1tTd6H)>%EE#fY9?!-{sL?B)09Lsce% z>hJZCasvy3Ly#6oLFDAu05xqZdQHE zY{Syo#WROHWv0ck-%fRja-3Ljs>N^eWpSOqjY@oX-}&Cir_KBl#>ZuA(|#-E?C8)n zY{+k3HvxT-X?6vj*vHXpdqI1B1srdpo5{1XwIL+OV#4f~D~k&#V$_v+Bt$p!HP<>M zs^)2voTCSVf=H7BzLs@M2#9PC(4W3IqkxxUv zpWAm^r0Z!ZpIBq2K3qRqu%5pO&(gMkTa)%SG(054GfV23+x)7}=XI(I4+`ZU!AGG3 z=YKGrBT0%E^V1V^nX%?J1Rrp5t&X>>@bGv%d0^+-^v!Mly?WXH8>@l~r1tX(1u|p= z4AMU`BB{iOOUD^L`)~DI+c>{XCAqh`+UsFF7a0 z&g8tBGq;fG`7}+&`_3SDPo#|NY@~j{zB%Fai<^`CZ~6i=W7h74kb>*I(3JW;NF#-N zVUi;X9&g}I#lf_L!STlNAG`1{w*3L6XQ8^SJK>WD?m~`A(-S8(3uDfvOG8}cm(6}= zguP@QJT@!*wZDGtIcU5cG(LIO_(MvD&j&c~K!vKwsShn~J1Ucs#3cLj49oH!$3zrh zJ~#tc1h+$9^$l(Ego`^ND|h1bpG~o$6eejyelYG7@M-yx7izw#xY6BTFyfbKYyIhg z-a)JC#XR1gr?!4JxeHCx?|*bO^*MhY|3L)$-B2cd=;DrC{~VIpeF3y}eP2G%t)Hbd z`n+WQbZtY0n!SFYn@wVO`*>qpi?q;?G1ale5dGnL!GeF0SQ1@a2|LqpbRefHx$jW& z+fe$UFE@V*CCKeQ-4 z5ZW2*sPQm6u4JLrc7=coYm)D>27w9mN9Dzo!Gj8q*&~bVu`!m8(&vte&M`AOGx=ab zYu&Vn5_QO!AC;6elyS~FhJL#AJXh(rxPq^)a!=&fn#t!vZ0ZzcL=_%M46CcSrdIJ9 zaJQRv@ezEyVVSsdU`cs8VJnx&Ec8Q;jYRK$KB3CIc21G6UC6VcgXKpsvxeDEyE|KJ zWDng849COg86;R`E$;PCnrmn0t}8@ z<~I|5SbXm|ch1I|d*<_p7M5m+iNT2s!MTT$Ns@5)3;X2fU^<3|bAb-um`bG>Jo>%+ zVXkI#jN3ULr=;i3?MJ-hV;E~;OVs!zmeA9i%P~QB?%eCI4;sCK)Fvg*fgi<`fO5Ch z&tY=(VwTt`LKB1GnDgh(>0&Dly08FE5ZdXV7yd=?+uZZZ|M+ez{F|cw-@T?v!`nI# z|3)xh`@e7h#lQdS|LV0>{~s?26A=}X`uEp_t!J)#uQpITrQr@Cqm@yVMYri$Jv?sh zs76idL|dxe;UjSSnCN5F#3|V`NTY$w=Q^Akv7{5B$qI+$y`cYvE|kMT{ZqH{livJ1 zxgVGq_(a#4XU*+;n*8A0c01Qp?v=(krDLz(A>ZV2(7Oh(z_gC0j>0OGiZpGgkCfEa zC~5@VfNp(lKlarxPxA6ZJ^pC3O@bN^BL9=~t@^&Oz(_Tm=RH`t3&V-R?by@3e8-_v z0fyOQ!6Zhgf}lC9zI9nJK=ZmEqAXU00V)`^o2BqYGKK=G5YVYNH7z^S^TWXVi^{o2 z+T|;PC!WQ~?Ypepz3NsueULw&?C#3cTHaHU;HN+m%Xw`679R?TN9S$p!LWTNWvyT6 zvAPgkhqsb4wom4d-<-A!Myyo|oj{d2Si>F(z%71v%Ho$D0UL5YOQva86!18I<`kyr!W?wu2=!`awRCtaNteNPY= zv23E|z+-ky|Bt4t$jUy{N@23d)O8g(3TXFu4mIMzR$MYBL@W@6@cv_E$%G`BO zgR7>6xymzei$2@<6^2cZu=pW3c!|Ke-EArSt|r$1K9qAM#Jp~qzC}FtlVWfo%nyae z8Eh9|^OC%6$$2^KiHCkAqbZ@UFef5W90&^w9KB}p_FIfYj>~j*MFgxJuBM4iSNha^ zhdv@_Phe6LhWHaMuXIOwB{HB(cRXj<9EL)$_6b686(O#M<=2x>khL(8Up>JZq8leu zomrH@lb{Q4Fr-Irb7U_oWT6>+ zi*8XsTYnt>47!AP;8lde#PLO7nlc?@)?}FjK$p z0$gxts~!{@Ki?d~%@B&-2n>*?B0O$mD+d$saaJI>lUK5>%@G2OLk{CPrn%}^N!MpQ zP^OEI7G{jO?CnUn^7XsD8V)$^Lwm)lS3%oUFQZ~GC%T&{W_n%5^_ixc?9kGkF9O;z zXyVFn1O_~D)YOr$b5QL0#H=>aGIcENt8KWZ`cF^o#pIEhI}`k;9zU)$ZCM3xndro# zr4YZpV1bq3!Z)Z~4d4uhqc9Ts`a~7Yck>piSPq1FJes5-L&oj+{dOu$`kjOOZ=BX` z>@?xcY#%enZ|MvBwFXFZ?^@SEqb`bJx4TcDmKf#zebib=iQrP4nroHDz(xy<+(mA7 zOw>c@G<>qwWYk4OJ!?yCz82<>P#*fVnB?sYW=<0szVSaC(4qBKGIPq<7if?D5EAH) zbFK!qjv9v5Wa-MSz{pL7-G6YP<}!T5(kQel)#ORyI1^X`!*Yc>Ge8V#v>-`nrqYB+ zk{bw_$muHQBaB$-IscdZT0_4W1?XlD=W+Z84JS&7tb#YE?(9(Q$$T_AN&$w2T3>RK z)WAMjKbYQ7ya1Q3T%|@>45o2FIl=tMlo`C)i>j=VFyyjdPmR-2&uoC)Sra~fgHgSs z9GG;c{F|!XaXl*?6soatB4~5B=!*!=5D|N6a^6iGX;FN8TM z`4%b442i&m)UM(e>4t_TR&=A592s#p5+zFEkhu(7aWDRksbWH@Vfv3xGcluJCK)Za zQxT=0H5!8jB%q%J^8_w@P)|l9!R_j>6gKC)lB7s1_z_p%5kq;4k6q$^UrbZCiK zoefeRtlK5$BXvUPY)^dV6;*ac>}NJSLm$s;YSR4LaxT;^1D!xsDIrQ;nlgU`RF7+E7Q_e}Qao!BoEk{*+~QBZ{<2K$y% zNbPPr;)rAYo$AOKe4iiEt=tByLa#Teayw3g2iIE?3#&hl#B{`7NI;uQ#LOWsz?~m8 z+hGWitLM*_pYN*FRmqDNjFpB4*+LQHNk&~*=Ax@gY!|+Y8F1N;WK_jb^U5twRXfhy z9P7CTyZorXxZUaz5!DMzsmHSykb_b*sYbJHYQLscy|vsM1G-kssDnDv;ekQHKOy!v zptF7X&(Nudi%3vwr?Q1s!J>WEJTY?9o$kK))lC9zZN3jp5-B4@RU*2trb;2Q5R?2# z!yeD>=Ls}GYSS@~4J(Iv%C`-7m!Z&_}mZs=zglJxHoEa(UgVjIN#Wpy~G2o)gnz>Cc-n|`|I&L}sLE`bm ziRmazTD?9^3LztizkL(GD^T6=B(&7~G)^3&KPgudx}dB!4xPHAh@T&2jxWuds&qNR z-PpJI)7;}^vP?{sR1gOgmm6d#XyIvg3m?zTKS`<2z7_5t^k%N82@#(~NYOV{(ti5` z*QI+%g5!L}Omwn$^q2h(uDpXbd0j9`A5P-ZY)4^fw?B%Or=jO78@jo1v4*MqX({1} zXsKZoCvIvE{SkiXKE{@fP8HM!iQv7p*J^o3g;?^o>+u~P`%vW;MnB%dtAri547nh_ z&4o+j==8WBhy3JRkbM0@7k(S6Iu2slwvCo+1BBNuY5ryq{v~%Wea=Sl_|B9lMP;54 zW=2X<$rr);`iPPp2R4$Sn>UXeF2hL=x10HuclNtO+S)L zF+XkISi^&EddBWW!}cm&_ky2`y|I{`PF6$=zHW#^vu(jw0=uGO+cpzOa}Vwi9*gXd zk7lAdhU&zJxxqXUPKTQjj5*9qgM}3M2P~uv5!PgM)VOu_E6fa|-<3<-{P#`Usm(6H zO^Rv8w5}m)DD|GBDsL^`ijyJwaII#!5jF;cZpXE9HJ_Z8=nlJLf^I;~%{^XBJ`%g9 zAOme~n2X&0^daeIT;6`*D#MM1l*dSKN;o$3n6L{>_c1Lyt%3sum}YJeiHfG|_1t7{ z_b~_vYZILIcEYKy2^|=)fTKvYG>`4oGSPd*!jh{24p&6oQ(fjz1bVVsH#K3Jnp6;j zdjnQ{TDz<;%|e~Ay@IEGlY5UFFH+xKV(%S4RyI#^ZkR;otAkeHoKXKkZK_PPS*ZXA z&i^<$2(W(0L<6fysC89r}eqcg5MR)D^tmsQRGgb;aP%`Ec@v z8ypH?MJeiF|9F(FLf+!De?YG)a{J~jC^0Tm-#*z9b+$kP6%j*HE(!@C{@t$mV>gc~hHdXUW#kitI)@$)`eEoy}sd$aD@clkZW_9syvKr5`n+g7n@YhLezVpuC^p=P#DN zRN%V3g^GUSUyNXWlp zDw#os*M0Cb@+~Kh50Wd1@$2kF)2p`SG0NLx?8}~>sCyVC;v4CCc|_N90A^N4ze(BD z#eGSrv0SNued0@Rn4`V)jF)ON2IK2T+tT+8HZ8{cDbH}Wpv=5aaWpPNh2Rdx_fM9o z=BAT<>5k*V3U`)Q44S*N(eW8KBCCY(6&N+o)yEM+P=!rptZfJ)z1(Up{@g~_SFwOp z?ikEt*-Lj34TP6QtdS@Xf$(Tk49;L&1(Rl%TO@<7-`Da*2)sUfC^+P*3+AyugSn## zZv^J`s9Khnf6HuA2o1uh_@(Lb#|t!Qu1M`-o&57^>wy7p8KKD^yXMC}DPrYfr=xO$ z;OroIL-4UeI#E}1QOc`(1>-x^z6$aAUYPwrm`ZwaAs5A&f1Cwklw&DHc~rU=sHMm; zalD|_Vg=C47@^k@V(_UJDpPiRqy-R||9;Rvv5rn^*@qjU^<;eBNxKK?nRl-!QZ3;V zqKU)dw%mfjuHJyCyH1`Xz_w93EZj9YuzG*GUaQ)sh>onSN~?+Cae8MQL-sGCRUIs? zT8R(FH)|^c<^3rP&I-fUgHorGm+HVD=FFUu6_E~KsZjzLtgA!|mL_Yk2V*cE)g)^( zn0ej0{IKyN2gE+qrHRRJHRZ9Wdoc`q`g-5;wkKNeilJD;1CCM42XZ6{)Vx7M2QlOU zemtftN?u_$F=z{O4}j8mChdYTN)KZn(AHzCU}?J_3qX1P`TR%z`|Qw5h(Zv#Cw{}C z(w6^r=>ys9(u|1;oPGTYkXfwj!B=AaI5Q(c`mbVLZ`3pU10@~eC#TT*h2B2tS zLxlQOaYS2$9T>4Pe|Bi_K$cgo#h}i=fpcb?%{nv7j@qSaCG8x+K&`Jf;Y{krWMxbIxKzyH}N)R>%EFxa^0D+o|L1di(kCiI%@0h~)CgkS+m{ zxpoMFACG;^i6=8ua(yj`7_~iy&+mbU!5DO47e7?jTz=68QTDpwG5U~an;jvrt_Fjp z2@-~BevquDj+D|#Wi1=wSRNNljZmX_u?iu)G)D_}ZppMD1w%F+HK7wxAL^eA@Z1It zVVBd_mesoA#2(f)MleWJx}Aiz-WaM=xnpP+ixwHG6aMb5>WdG0x<=-zny2E4$n@V7 z`y8qmP!$W0!J-LXN+SfkYVlQ7#i|GunK)Q?0B!K6Ehr+0yot;g+#~KW!N?w6H=bY` ztOF6#e6Bzu%S-T{`YJ;z;|-rx4gy0WHzJxx&hzb`8G0B;-69iouy5pP zBdiKFu|;Qf<)$qTcBppx>gFq0^fHMCwmDPbBiuRFDff{_H>hOJimXgt9mRAJ|q4RT;Jt66gT(PN6T1{;Q|$N z>Evmkg>anrp_cF6w+gXFBe<5|pS}f7qeN!><4q;l32Fqe(-;&xNzcz_Bkl zW*?M8T%ys|mGUWQwmLI*09!Zif?0b0k+B-0t;vZ%#92RVUy{w=VTh;mp7+SOVl~6E zg@77S37Lzfz!-c*o|7(6(`WT*)RU(=ftUsmORogUJ8|P$wl8;Z=a;VWbK-(S5J6d9 z$ycZ*Ugre?vaQqtQHA-wHYrt(CZ3LM)QLxXx^*D8C#%b9U!ZT|i&GP@^g8cVO0QZ_ z5aEY5aKm)M>^7Ft=LS12TowbO2ViY{R|(n_)y{EaSWCj9cl2!9(2Xd zY$ohb_TQ0yalr9){@&a56mbyibJ!BDEOtj%uWwj+PW=|4TLXdOE9$Oft}lsv&Af zb(8~9ASuSc5oQuQt;c2W=7N#+Y5ATKa)%4@g`wN8nHV>9b@{MiZh~&**5dfE;)unK z;wi8N$LkV-8rJJK4v`5Um)MwAk62FF;-Sw1!;Kuz@k{c~8SuaWt!Vd95-!{@2)>65 z*=Uuu?FT5h{329H&k^y}JcJoK^cQFaw>(2@;A^@2vA)?}o_$Ju2pK;t+fb7Cv&KCL z8m)yN+tZa0PbV0BnllsaCOKY7A zYgmZ*j`3we*_Q{lf5+^~Eq1E7DLQ<+xE}k;1Gyn8@T8RXv|J}jyID`!9jTl)V}ckw z>#zNgk>C}iNcKfAl|a_McU`3dQ#IcKY8$&THTFp-lt|?$ABT97ESqD66}{j%$13Kv z0w=bEX_WJ6ouP4-nzF;pTGZw{weY>XxAc6w4kHuVS_fVqEdn-gT0WL`yKu@a^v?IL z`o0k7Q^1w-QaL`lvkPDOy`5^f_2I(AURKWjSEedPXz&e}L{3~+14(Xj}7PgKuXoEMBDeq|tr>JFbg*eV5UT^)zGlKQ!qULbb5`t`@S&yldb4b1I=9%Nh6 z_9r#XZf?jTLm`Y|R+Qp&bt4ReK}Z~~dRv$MOZxATOS z5TFIK;?Mxf{jIlV52Ye6y46iptBt}x?XVZcU0w8LSS&HHSFY1ilD0sly77rEXBQ36`Fv91vc zJ@}0=7yN*E`B?>!ZQFos`!LD|W`4B@+ZL?;?HMo2cNNZH0+MEC;#a4P%a_N<_{FJ$ z!$QTI#a2iAm1B2zVhbFb4yHvki<|`Kj7r{r%?gfvr7E#lhHb)BYwbpU7sYe4q|rVc>9+$IV$@EP$hyWu|HelVWT)PZyj1yUWq%*7Hx7 ztFxa1L_d{Vr)vQ!3oqPfOKW^SLXm*i4M&WMvfdrtKGiy!z#=T5lG&=I@7x?e7lrb%tWa`h^oVdZV zFc4)uhEkKZD-A&beTNgm3`;@Fe)a$aG$Bf4mI=?+U4-9AqMo%Uw>ChW5$hiaX`$U8 z3IPbb!_L>h~D9q@WcyaS`Eq6L37qtejhC`8l@$vFFs+$xG*G| z7>ryuPySY;vF`LA%+E&3SK;0f^Uo}RJFvtf#j!(P5H3tH z{Z>^J8rXL%z25;J>USO*o6Vq#9r5Sjf)rU$E1pr^t08xFpK;)DW-oN1Aa>~XO!yQF zod2+*258mGyiecXc!d?cRi1lr7eC(=-pE*pe6%mkORn5t*laF~eddc5rC^+oaGW_g zceIJJ7q`&E$f#iRd}unj?Oi0f0L2HWE`9Bn?4b5vI!N|bzE(8l>3(9HJYo=;f)t_j z90%mg+0zMxm>^7t>TW>?EGwhzU*B%$k2ci}RYRn7UcE2E#X0uwK2L6gW7EFY%u_q& z+lt6a5htQXdkmu?Zw5qv;<(cn0{~E1=L2tghyl%AskIl!i(@e7%7);lP8_&;Yzkcl zTHsB@51jV{1;p1?2=piJx%w~%L`S0sITF|fhVwq2cc|uuE&`2%VMs~~E5DI~bTQbH z`vMEB^3eSVua{URg++6W5x__`-H!C{YM;1h3=p9ny)W)dxm_lJo-KF{BA#H{4*~Ka z2Ye6A)Z)=AOn2k>;DD6*#z4;qDB&#qRDm>`xpm^)n zr_bLB;42ma_LzHJJoyKG8n`Huk{_f$KM9J?bIih}RgtUA(AizUK>(qQBfr(|=r^V# zJ4*@?2GBIXd#4-vG1G~!4YO^#z!dh5tYH0qrk;h^)ua=GeLtMFC@Cl4&=Kykhw56=p_pXco z>rx92Q;~Ii@qGc_8`vd{$TAD=$_JX&+nad;dvR+>>TegGnCa;e%Ho9m0TEYwJ&hO9 z%V+W9>C!I@GvGw!-c7r0Q1MXugc~L~AE6ZrKwnzUD+tK*b;ek&&&B5@Z(HFD{M}sq zfbL&Dkb9m+Z#djnJqnwOELM<040_^AJ`A&|ABeXF@PCL)6kLFV^aoSUb<6_@BQ-pH zwpBnta79v(ULB0l_tIzgRHJBUxfc^jf?b7Zi4^)z_=51R9IR1YTDdw`4&DQceqKBp^@yF=P1G>K{~FH@%1VaEO-4FVv=Ks(~yTVyD)Ufh@~JTK@FSyyL8xmQg0b50o2Fev*K}cq^-e3#y2*Hbu9@8 z;89OzIQC*rw#cCVp}A(3pg^kuaNlV8lm+n>=9hxDo#Cg-a|RUcy}KZE?o|2zMGc77q{|@>Da?e#cX*5Duy(CgB;u$FoPu9^7hyABXACmGY;w`){6-FckxW z;fZ0l7IzGKFy}ad2o>B#oPA6h*nX-&UKKuYR?t!oI6p;4k$eQrrd5ekku{=F+bA_2 z`=N%WOf-Y(#JCcKE2aBxntnZU+Jq7hX3p6x>R={ZApypupVajq_mk0s!TJU%EY%FN52RSO;$b zlf!f@SS92>F2ZPcawDu7qo z@a@wx5)x0gWC!5+ExU&=V9Av|xkjMm*VbHSNJVTm(?>;yV!{+L!ww7>U9!ng1#yiWY> z17UbP(CRZG`M*^@!lkS60`OEFU#7ajE09mzq`~XNWpt8&T374mLvpLQu!g`iuNih`R*6gpXNR z75AuDDhyWTtMp&8rf`(6mS~(!4`Q86M!Lxsz^Jwc!COuBA9XW1v0aGu<$>@adXd|5 zAAoL9Bq2@*C@5^7Vlvw6Oc=+PLxCHMU^+wcNi7~u?7%R5HXp<8e~=XUNMQK#A)|m# zi}^_<(PjK>Pso=sTt8pE{3k#hkpgL#Q(1U;U%%$4-R%zzK0*0rc`-m8{qSHYUZsKDmB0DS0Ou0z(GQ$~>2`28?5Ib&yjSF&Hstr{cx(9Up zbi!962n&khe>w~1X1Y-$`+2&WJE=duSb2_PK7~YVYpRTfb@*`LtL2+J7*Hoc1vf2D~sQp6(aM75~Fy z@P`%ZrQ?@?7Ep2myd|FJcIo2(n}bb?+jW^KHL*TxseSJabR7K4gWD$TqCYFp!bwPJ z;=faR9!||4iJwMD=&?g3jWR{3)bv*R?5$QePM;p$n6JFJ5jRj28PUTasT!;Vbo1A8 zi(tz7;L8?7SSL^ZCc{vxh|F=}LNFGVVB2j++8bGv;Sna$i(-*m8Fx&1z{t8sjfF1V|&`OYqXw8M;h z4h(Bo6~X#5#fH{2dkCh#hw{5x`TozCBBI62j{!;#^ie}WhE}>T|0Hyd=jX_Mk_y;C zn~6{gC>dn&<0K3<_e}auGgXLJXc?hj6e}?&9r$pC=bwUNrwW1B=6UXO7(%x$ zB_GKy+$6)wK0x^5h1{l(0{UF`B3oK;H%Ha?d#`urj29OUqZkO_N=edw&O%B9qGdXo z2??yMqUUQQM}Yh{1M)8D>EI zZ-V;344-#HZh*Xd(*;6wg*X5qGgJpsR_oGik^UH~)iDBzE~v|;hm0;UobKLk0{XjQE`MPO zPOwy{ah3wvDA&KioNM2z^WY}&%rXN$G&|u529P~EXDhc=U@NOZq%f0fYH#DN)YTp@ z5!a>bHF*Nsg-RfeF@}^WmdqNjI%pNlt$Mx^)wZPyaX2I0nS2SLq}j@b5?m6{RHIN{ zA-$GDL2ygRRUD>ZE$BFFYwFUqqTn%Iq~i?ATidq}OL-kPo>1}(9hI%ms2>mD_9E;M zFLG&OGw-A(i5zsVodNEV;>GnBw9rf?1>4u!@wj7GN{VfAba z00+aB-Wv$MQWQD{G_8I*1b4}wbZJ`A22ks-@FcsAJYvou>=JbLMsvHhHQc%T`U8eM zy9WYeYW8sw>6qJ3V^i)ZYZ;Gq+; z|5mMpz(DGC7fT!^vp8b^qlW5JwBe)S?-;pB6=1kH;Dq29JfI378{mBx8S6X}&;cBr zv_< zkJBCjupgURHc!=w;afBN`e6WIeHq?=5YPbtk7<W;Pz;B_8uA!07}m4>#f%}NAe8KP)z}Dx@CoNFVGsL0;OxSjZA=jZr(=F zskWwczQ-G7zIu{AMQhM2(r2J)O8;=>mlWl{jQSJ>0dD}CtG#Z2>&-4eR%L~^4>*WN zQz)_Wj$|%yPf>)3=$a{zwJrejh^acsbJiU>`RS_ZCu?qfZvX&UHp<)vJ)PIVUi^|s zybfwsz?zO}F)r@}D*2Kw31FH6{IbFnprQtW@eupvvzEf-Cx|Xzx4#FJn!R+5fG#tv z2M^@pg~?l=_~N5WQ}x&uMn+_v)J!KDWuaa|=gA+L?x8Y}2)&vky(L~z;)zxB%Z~AS zHB}CXtWsBJ5QGnok*Mvs$bB0j@OX^)TzGU5VaYp`yz#|qns=W*g@BOs`x%B$f&N9f zMXCQy;Y%W{ZMqh=R2Je@%e7*FG<5)l2UOvQpzA65)^tv7Jw&p_u}Kz6G8CW{h}v$9 z+fdhB@V7)5YG0;*VTz`zBGk}q#DwS}zWvS!H!rF#zu;G2X274TiLi8C~&n}Q08a* zWi?Glsm8+P2N)pFlrZR~J(|8J1UN0-GL|n*j!h28L83>W-3~irL$$+Dm3L$)wM5jP zWWBI9Y22iFUaj&Lu}kz zeF1L4-d6$9CTsOrZn2ylArI^>@VC;42J&8AgoNgSbu^y~hKpm20Wp2NCJaxrT%P?3 z%?ECUV#k?lJz<=UhwfeXuT%TAKtMQJc4Mhm3*KO`G@KggcTRq);0Z~TUCf0YSYNsP ze1@W`HF6a)8wFAM6Oe#rYP=Mt2i&^$M?M(l%kqo)5eW~t*pktQd!u|KQ;L!)-I-o@ zmK@qSE<{*1b6MR*B*`*rkX&8oTqfZ|w>A>xUB2!50sW@s(rQpCNyyS0>*L@J^EGY>TU>5S`LJ1y?=px#Y3x_isfko@KykCu!rQ(!thO5Z}W>otE6 z%`!j!U@}yft~eB+k`}kk=2*&V#h#`os#WkO)}!}k{c**@{>nTjWWO8e%ho|)8p*vs zv_LNV(>NnF9g7j}4mmtfHUvhiz|?$GL5R=3S7?X|9a#%Y{Km@^Q<~~a|6O1SYjg@h z?B6+I*3eWk_uvP!_c}xkEOOxQvK+DwycUshd>4`R`e;DWQvteW8IW-AJ41Hr`h1I2 z#wpdQh?mfZWb{GG6=jGDw)q&f&72Z-<<|GGIxcmOc;pYb4e)ealqU%_@fuqoc1(mIR?9V=-tfH!> zrgl@s+SS>{+gVV}-PXg-+1(Kw)$Ok2?&JI)|Hgk<)!xU}+u76C!<$_gfATs0&~0xY zUlk{7Z+1};!Q0mV_+1#B?|pXEw-39782&tQ#J`>ue)iRW+lBG(*9IrGyZfH?^S|^> z-PPLB2j39f7xdOv$=%V_o?S>#$;TFdKDeZ~5I8>k@9+E~;2$d1p8q&x9RFWR4;=rV zJ}xdI3^r{9{NpHcNios0_Kg1?M=mBLBzX59-@;!*-`C#Fh+SOd>@$B)h36L+5@p9X zz%DEzCWLPmf9yNxGT7B&F9L2De=7XH->~q19h?8@NOcvZe?L-P7=NVve|t>-+lY$% z+lZ?F=ZNwvd$`*D-w&%OzVrX)`*Ru@#m0 z*Rhon{@1aU{GZ46@A&_7Y=x!&Ikpm_|HIgx9X0=NlNmqS|901Rb!}YiZU1xW!QHGK z?f*VEb`fdee;#}f`u6|--24B11Cf}Bg!q5mKV;&mKf%yVqVoQ6uZj>{My%_iGEtBI z5vp6RDigDE+s5Vsya4`?=t8tfI%*fPa(hR4MMWl=%(sBc+W#R{MZCXB4WHSzz~_ME zrbY5{l<}LC(N^DhzBP*#*36s7*zLn1hJk9|q564~u;t^y`iYSeL)9S1(=dPE`=@_? zb6pMG?&Z-|=TrzbZ`{HOUbgrX9I-6E@IJLgZy_@SB7gG3sC2JZuvyjSeFBT@rTbO> zn--OWCWy|?&gQz{g9*#icjdK`Gq*J7-tpvARo|a*pRZY;d~16Zhtnw(ow?9@w5?^r z_`z~*qFm$Ze)I4B@S*I@mB57u^@Q3GKDYdb-*l(sEJ-Phq?~R++^0}kDRf3`kj=oG zcb%CeT%{iDe(&f|y?are!0FF4EnE0*53;64qNVsTq=U|hJ8Zw z$0?tzD$s4dCDbn9MTL59j*^DLpBHjZ<8+2|#!8K|rMxcWwK?)ycTHZ<7V;J2e|YnS zhV3g(-Q}T7&evB+oPMHAnG?62lfT$C0hVvM#3-ZN}tL_Vy+ zu8m$IjdK__3N$(X({^#tppEbC#q{Xx<~JVOJ%=us#O9TrdY28|FvK|J);mEWCl5iI zVT%wiN$dDaN}p@|Zdym&p-ttVTXtF&H`LSQVU5>fSJ|LWTQY;w zt7?==_p@NJhY~C*IZE@P$NXQ?$f|~ISmRZ1e#%pV!{Lt|MTV7QBh+)Rl1`6zsJ4lM zFLf$%evG;3HrwF$*s<$u90q%%3SGk3;j6E>!yYgwnZ0uuh+gbgPzW_G+ueNUNOGTx z(~0=jiKp;t%l!!fWr9mr^>fm0pZrMw&}CQ*FGU>u%Evs}>}Lrt99nWry;4iLJSfU_6nV2ik!IT|{gpcFJ-FQNH78VuBF&iQ1-Ha=mJ`eKjyZR3 ze$5H(RW$R4&ya0BO$erIc=tZXD_V)ODe>ERw<|UNbSk|b$T{$0Uw)UXB#9|vY{C}w zaZG6%$y{_YF}yX3q~AWGPLw;ivIH}Nc^KuVCq=fL_X2A-Pg&#J#|~Pv9R5V6@h<^l;6l$L=%AgfPOkCXyhPI@V4a0>UA4~X zy+M>-ltSQy$|6JFs8=%MzAskOEXYk{>eNflxZ}iOlcT1IXvMyF@Y~-xJ#~{H*MQ# zS!y5f`T2eCiWB|e6oV6&&8xg38&a`|Hv|tQH1yjpoK~|#R7*8%C0}=TD8`~Ok#y$_ zDyr;q-fC2+b}E_$sgYGZX4hH&C_AnNE@1K<#sT_mMCEiHG5R!tOiWl92y@Xn(z0fS ziEIZLn(k$akd+RSQ-$?TwUZyN@#^@G+pvm7qdw;e53AyvxaRax5d85Pm^sQC)ZmX` z^xrzN*i#c|8wg)C{Shcel>XwH6B6+wqRahmY)nMWF6!6oJXPVuzZWB|!hQL8%_%oV ze)z%q%wg@&Ei>@irQMZxjziz<9JNV)FB=P8c~tXYeR?rFjL|DYH&gDg)G-?(Y$Y7F z#9@EMMK$Y>-#0Srcss)N$b)&yxQ0klWrAFj&$VuiAuH5hh8SobaW_s5NksGRht)t6 zemQ0=Q5UQY>1K0V$479df~Gl6uPZZL951`q!Z-bmOst-k$Z0!NI%=nL- zk&zMmY&>VJ6Fa2we?;L-$jZnBSv!EIX=y~@g-0_?4*5{)IPV&o1phv$pKD-h<#|*J z3_K+XYr&A4Y@05q)xGns=c*Ofk7oC0s{y_C>!0(g0}{1?5y)w~;XAUs6c>ZvOd#|$ zsHz)ieH~9GJ8VT1tK+td+5lfis3b~*-|OpGI@^d< zQ3$K$!sN`15IZ%VqYLl#D3g6UH7$5r4&P#Nw&zwmQ{Wc_-UmMG{5}$BOvk&?PpxAp zQ@XO5_MDjky^O;Uak7>k*>s?)Aulv&;oXg-`IiXYZ<#Aap&_8m<+D#~9_Qb!N^2&s zd^}HC>KCQhNjcO<_t@551+ABb`}klci=@-NUp67W6;n%*Zh!j#F;}fi*Br?&41yUD z>&Z<5hLpB)M~jGg7jH0l@QUCLERM$qrts(L2M3Cto-dod!J3+yf(s{(<4Gm6cwRU1 z`uc?CZJfhGUBMgnt;53&!Sb;*nsXKEMEPT;1M9@3xSXzX`BMR?4B(Is`DG;sKY8CC z=YfjQB;r3^X^pQT^oWlaht_Jg#vw4pKn>UCk_W)AG&xz=+h098?AKJlH=7g-Go$XL zyns&WqYoL^2P%Wfzy}j$lm}bT@Xq^p1Vjr4s}pf0x)E3=QP#T!O}du%3)suk8PUdp zoU&r6M~azCOk>icBDV*;8tVV2e{rH{<XLz}f3flP3Q!+bk4l1TyM#q2f5`syOrO3-zo6Vkf zY-w@`A4*76;2;zv$PzR-IEb>f0pB$#)N$dr zIpyxveiO$aa0G`#eWb3MPx*s;R&j&9H2kJR&$&^~bfypm3LssGLeWk1p_aur%lUTFNrwyPbqIqb+H% zy30Rgn z-PIpnv9A&vwMh4D%C)Sp!c3X zb>4q`0--6To(8;3CWeZ?`{_YL#4D|yrl@cmE)Z$Z!6d>G@e{23gW@I~C~xI-^R}X( z3!U)ycW^wy5SSeZS4p!hfVr);)U?_e=H|xF2l?c1Ijc$UFv;83m%bCsEv@cfc(hEh zGZa!+3nQ;8Cyl32cr`;bG;q@|bFpJz#6mQzq=1}m4q>Fh+>TMwCaaTYMi3aMV zWH89eqz6ZC=fD~tr!A913t1FXu#~C~XZaD(4)h*xgve`q`J^>Dc-1YPd_@bqHsJPj zB-4e{)vKQxMZk>~fx*-Q2WMTEp5$4kKw=)XpJznDxF9NmdM(C?FKiQWv<3)Q>OGK^ zr5PibinV}lp}j@#+?uKq!eO>mm*l}mLizZ;;$Sj~Q$&7%+n(M`$$p}b*reTLrhpa- z2@%i9sR*)ZLgDiAFaFAIW_k+k+MRFmZobkQ`qqbyfEbKf2@QvUy@mq#T$K{G9QPHsP< zxG{=3Idq=@R3@Y2lu6BU-cectgq-$=v9Y?}Nmuh(6>8{uXFV zH#zrJ-WbJ7L2mDgjpS+I zS|)3K7N%#A6`3%&A&K2_-C0F;R$)V_O)(nCYig~m9Z>7>1Ok^I&0&jH#y(hV zxN5rt0J&y7I);NFXO~b!JujcK0~X2Xc!G81+Q7{a9W+fJW~!I94+ zGX&a+lyuHMi}n9ZvK?{Zy)ykgnPYsu6P+7HGCG#B;B?}~-a`Y*KHp2iOdW(x$iwOL z4I)y}Koh-6x%fOJX1CXt59tI1wucLhq~v+wVTTSlx1SW0R8p8T_5ac6dU3BS_E92C z)S*qQl_YU3w3&B-L!~`)x%w_RP^*h&=|wn`cGPvP(s(uUBySg&GY?K4>1^Cv-Uvql zAdKVU%Lk^KtC>6MmxKkWG4U1giE5#8(>7529kp*bq_>q=XV(Wb# z-gC`NTIrd|al@n+4ZK(|`M~*p`@-0H>a;^&@&QZYBY!VO$sKLJoY?)>m^VT@^Cc~X zY2Vf-XK#jEWIO+y*3NrbhU7dhL#=#2B+%2>P<>tr&H0GAW3;85cSMJQOUJ2TYB52Y z6$s3e-xgU}3=U7cLZ;Hw)afx_rPaJt>9mLiNby?Lh#a(aRo1bs%?JWGgcX+$S`!tg z@jIE#Cw)J-^gfBG0A$1q%gR2BYWq0d=nZ3e{(_u?W^mc0A3z_GhM!9cI@)E@olHSh(Y zHf7Vn%|#fjQTZ`d8%)Gn#4>yZ=++xnF6?Dka2d)OZXy1d%k2dxY#=2;bF@;q`0!nf zlE4=n%^4AS3q8KEc}Y_KTTqjM<-ejUzks$sF)%kj=;)Xzc(9F?| zM(m#ep&)&Vc%fY0z7oupH;QMNV%0-r`e`0h6X_BV*<*Ezo-9!-3P->{c9{%>i2 zANzge4*=+|sOn!uQFPLG1jboF4UO!9 z@Gkiux1qqNQ!p|yb8xhGBj-1?(KiAfP;|1jwKTH&1*+2jO%eYVWByZ&zae~o9sd7I z;NJi5z%Ao1koynY{%Nj1toiH6U)=9kA1fP6X{G-o$7B432`l`~@qXLzuloLd&OaTG z@fR8lY^MLGSpH0U{Pt#mzd7CiLfkO^ zBjf#h;s(gA{K4C>{C2p%S@mCekemC@2Ai@w5VV)`d{!_52_qtF0<{mU-&_>92RiqFQ#u8se-!T)BHeEq$v{XcjRQ8%t0-1r7@0$A|9@x)JwuF<=1) zAkvQvz1*Y_N1_mqh5Ys14WwQYsFuRZ&}6~GMT;7}JO~ETuP+XexD)fgML@Y0rjUR5 zbqz$Ga7K20LrXD7L;8PwmJtj2OuB@Knk$Nr2nm`u2!${!9CS?7`=ulV5(akiX{1mf zHYON~eFGW$803Yh#}Tm<7fA?su8<8GHSs&w_mm(`(6uqT*fuM?L_e4OSK1|u{3pDa z7{j(AJ`(VX6$e0u1-v{wEadf=g`*|;+-!Pm)R)lXWgu~h*2IGz`!kz2axX0?gWaLf zNeKc)@W-Di0h=aCH&v>$O>F2Q{<+q!?lUU=Y;ae?E1?)N(Q39K^>frQrc8On61q(J zwO%rOT~q1v+bYMdQK8Ybv7f9h?07;Fn%_(n>It#Clb)KZr~BEBnOp1?`IoI$7-C2$ zC}?N}QOsFwg(b;3M4+1Jk@Y8o0uMOzrYU<8<1FeG&QS)}Q;{sbY(12d|pFixIAcm&N40m&K=s`2Ss zta4~GPt4!Hx9>Go6N}FeB1r_w&!t9@9)FS?t#q3-+3R)n?8$+zUEt z0$Qm6nqJ~&TLqV0oIQVgU8PFv<(mgglGoGY+BWIEQm4Cn3b6o{{bi7EUctv)-;u_I zKGp9GatTH#AU_i&B5blCBz&#G8x_hS$Xq@7m1_K*5s@JKMnr65prw+R2om+UL@a!; zil1mk@NDVDi#DE5L*so7E}){mePV>#SKSi(fziQ|a7NHieZy0zEci}u=+oO@T7Tm;ZL!Bu#I0Ullv zH!o&q9_4orP8s&PG7V&d{yq zQggT9x2-8@VCX3(|1p&FHC`|oOV%>m8ZxWy6(rVby~zo+{rDUIYSGkdP~ODudocV2rV@uPKS+ra)K0CjX^54>Uc3? zy&wJCm(0-A%wj}I!}L;!KyW#V1;o!40QrOBt79onimwNY^i4J>FV#E~ak-*Ddj$ZG z?C7iAF>#FI*L@AJ;{AQ8aU4;72ZHf54SF63LQ|eI*P=%JfTjVhwc#Xa>ZXRfa%PI) zG5G>7&$uD=Ms|?xDM_ReBg-sqgHw4zGg+OOnnoBzTQ{xu0|;}60O%nF){LrdPe=X; zpFYt+zAQm1#o z&(n=z<)TS!q5F84Ui-t+h;Yq8f^G#8(DUUYC^dc<6&feLtnNo>N86XIqZ(EHcCVS0 z2a&xj;8nTLpEWJ+Nk3f+Ei*cv#>`-$?@XFgVyYoJM68%aP07FUW#|Ld9MdT%K?Cca zf;g2XrKOc*zeu7Md6cH4l)_PSbo}K>e4M0VTE|!s1mCVaVOLI~Qi}+r`8+5_DGDUo zl@P#a5c7CKrB9rh!{h7s>2k%s9IRcH>ON#)y6VdA7eV#*9_=!MQ&hQO2Jw%ZR#>vu zV{xHzDwzW3U)+)Zr%PU?vj? znfiy(C(V9-aU6CQ>+>H&Iw>a#(uu?YdHCEX`VFg#Y~*1?4eSAQa050K^vV-!92^+& z`3hTP5fd1^v^elX@p0IqB#@qR?JvIvtXIdd3^n3xxGNSbMQvdqDQsm~$`@zaScv^x z@KR_md`C)nc;IhZ%eNsbf%C#Hf>G56;v`YYnj+z+7eR*mI8{3Ev<)NJzqHaxTAe_= z*LWwP75lv#o178a4Mf#StUV7Fnu0Xyd2|b{p8~aScqb4}-QGLlI~AC3H6Lf81~U0a z03+0TiJ}^~BuSEmAdHF%j#@HafOw>t110Occ1gGE$+fzCom^pza|JNh!3g?_z!yoL z$~}aWm9t9|d8Os?ypOy*`LZogRcQN-;g6B;lr7+QE7OyUaamhx?4`)t@@3`l!6oL5 z74oM0BL-~6qB;cxax*W7*tu3(Qt0BFHwN^76S!iFIhMJOz*wg(bsoqdLnN~z&MM1T zi7h5TD|hXoZw!)nuV01Im{U~SXCLiMn!m5B`T?I5&LZ>`lv!O#BaJRWE+_)thc`pmnTY%7DlO@q^;o*HM>%iom6R< z1^hvCK$0Tv`P%J;zELgrM+iN^d_(?3PLC-BrXOF2nCVR2yGkF39en~9pM}Y6A|CH3 z+c%EkYx5V|t*@YV*t_D`qV6X!x2OQ_$|7Mo;vEvQ$RROV8Cl6ZkOESYC`5fv%5M~k zbzA}jVo;(a>3`y zetYdcuP;|()ClW`h~K~yOtJZxTazpkq+5zuX46rO8$nfW)BA>IckcwbN%v;K5CuN2 zC}2bv3RK3Kvs^{_eh-oF<3>GmBY9a@?RlL5-+Tiv{U$&-tbSje#n>>p!N==}p;*jPzF7C2Ypqe?VN_(IX}qV3wYXoZH9D~>3c z!v#UPYDt15Q=!{^P*!M3P^d&sk@@t}ijPG)o#JgW45NbNMXqWJ3UYle+^SV2AIcvV zNA52K=wD~^2<=QLhB3h-|fhni@^<4m|vp$WVnK>3I$vqIHSDkCn`V-6OstvTr#{z zN4EV+#tv@c%jLlHZ7mQso*b+$rXuxR^=v`WXYS=?cP6%)S1bnfdn->RE|0x%daFWB z#Pl{L0b*Qr#<5!1d#E#GPBu-=nD_--bK2 zlSUlZkC}e}mvKX88gG^icXHTg;@-tQlPc&?)Eh#!$XZj&erTIM*6+;4#mU5cWJ4J2 z6JDx5-%wS(2x`&Lo+uSjo!uR-;f9&m-0SA;jzDgj*vJie+M6$SiI!2aZ*TJ;unnH* zj^JIhxpBzk&pjq5yBoMhKM2b|LFpZr8#75}vkNnJuRBRT8j*{_Wo=DE%y5WEW%(Aq z-h5BK9!b9rnblaf_e3;pbaS%i1MkUhAA?+ueAM(3s_W^~(k!^uJ3Y7hjm`_>v20}a z*3;ZkU7J?grztBx8YNPF-EdmR?XWoG{rrYca?oXaiw8i?qc95#B`wNc-^P*OkCuIS zJk`1c-Fc~o>MF%ZIzxROUNUntT}M1bR^~Pb$>q~-I|7^W(vs%Gw&L**dQ{r$J zAsWLOcK$VMeKUJ8y7nay%Sz~I=b~Abz0qA|^Lk*6>z^1Op-t|J*DyGEF?SN};uO1l zN*a9V2Cv5qC1Pg|0Wd%Lw6 zLzt+RXFAoO$2QfP4&YsjBRo3UJz`bDYjqvGeB-7ct9^ay1W5XaSh5Uy9Nj0sT9OuD z@Z#t&DHwNELqxAQquboO6bf0q3F)BRtl~XPjI>m$C_TY2g*9h(U!tJgK4@4OiNs8f zMYWYA2G0mDR&_1($wslQdRj`j+5Wr;QeX(5O(k`y2)|Y7Yd=1=i9uc<^LeSW;ospq zj(Oat=DxV*5xB$}?52&cA9I3ZR$|^{OBRKcdTU~8s_-G{Xf8XtDbz%GC0zFOG64-1 zU&c~eg_%c;qVWLJz00i_nW7524FcSmklB13bH5llEFx>_h>PU+yCIm(adZ;p@b=-L zPndA%b$%mX1}u!w)xq4=i?^pGTc;K96*5kwNx8F>Z?Kznrq+HI()LWWaVBAUOE}zt zGPW+%l_^{=AQ3G1fDgtfBrYXqQ}#8J>?YN=Zwr1L4hCcAX}B4M{Tbh3iT}r9Ez>RR z)l+iRWx9zqeEEjQM#sW*v1^l>(g>su?c9RX?oZWUBg%N~ zUY_oDjVKHl;YwQ%ne08s?&%zAoCO3l(g>+_xbD7Gc5hF}jALz?)p>W70hVGvLEvuA z&jlX$s=WtLSH4==wBt213|hW?=x!nIY0KLhz`ng?d3pU#H&9e$svwz^lWB>5SdXl!Sdx2>9xT5^JC$770^N!YQt%o4|u^zq3e{>?k;V?D*RP*zG4|tiNZfB2tPbV|i^SJJ~G+zX_+cP`fqc6AbfV|Fc(ih!E zY*WGdNUl9XtTB^Kw&nF)QJ45UnJB|ftknKghMCy9@0LLPc3da=2=DZ5$*Ao;o{c!^ zvC&T=uX*j4yEKf^aiA$iB6-Zt6<>Z_$=i51moz_6M`dSLY{Uq@8E+-6Uvg#le`I6t zRBcl~Jhyn0>uF2Y5Ol}4;9`wGI668(?f=Bt{@zE0S7x|wRz3#Cw+sRBMWC4515xn` zO%vMHfxTLGS&2*K;-sTH%Y?k$8*D;6U&>}-EZSJja-Hybg+Cm}J1|#MIOVeEC5eKM z430~%J62I=zx?w0JH#jF#}EiNQry~gs@saIQ)ovMgD#S!P+aybGtN0@eL;T+)r-rA zSu)Z_gYO=%AI7jYQmaQ4n37L9Zpmuj+3A}Nr!!+|w^RtLUhL%)EwCzboDoWHn3B&aV_7U%1P5i;#StKac;bo z@UE_LxMSNk*#o~1E`47a?sqA1_ZGH2;my^$nXm6O!mNVQ@bj~`6Ulm7Dvw!qA#HHS z#{#yTRmoBUp3Fv7M<=Jdu#bl>+@_87<4LM@Uhm~@2`uGI6YfV=u7-Zp#V%D>Wq`5TK`66=M_$qHMEr|MY-q^=nB&)DUw_{^)?dxvzpvs zV4#-DS5gCg+^})tkd@jVq&DSx+K~l9gLK|z)9B@J3PUr<%hz2VC+e-v!OqgM0-xBd zFRY06Qo{PAc70_K3q80wZ8){!n@u9aw<7V*R74vzEDdxix1%<_)1!?O;C&}(G?aIYbji>`OXcC&MWP}g`3^WzfbvJQpoEb5DMpj(u2wQAlO+|pSiP+2#3 zlwAfN3#w#1Na_&4K6^KDLNk)fRSPkYGY@+Vv)7vOLmm41*7!Q(nAYcGxBYUg%)P`m z_XOShQT(jL8nOWT6vW=M0Xg(^m278tr2G6>e=KKV3i-w!)BY79HD$iOV@0uHqie0yr3f)+?vyqRN)>u-mMwTDw zECN1fDnvZa9h|@Q`DCF^J^?;0w;#Yp0bAuMS}`6F)l^)o3OfE@WrYDkn1n=Q97aKawV!4B@lsK`6acB^6;y= z6HE!2r)yalM#$C?Ukmq&FZvWU}S8eo^t&$(I#;w z!xQx|_e?O+^(M;j-zsp+>f3R^$)R~G)H2f22pRgswy)7=xO>h|L$`id>EFX=J$!ih ze0@BQY9xM`8YI6c4o2cma5;OdyehiSf15r`Xt3P}_+C3w-Oy>FcIL**y6xgZn}(Lg zO$!*vKxFXLG4>}2U-*gGeM3-w%2xRKBqDL%?&U0cWN$~5*hEB%d@LL3o()`EduCYh z$iE?c<-Ri9Gq=aI`d8O}Ck}9yvnK?C&mTm6f3NCw31|uID>o7Y;*ZbtX_cK;w zDc|*%+wk<_C_KE(Zud9sm+H1a?}CKvL@&9@WD!NGn6$MEN#l4CS+4%a>(a|G2*uP*=0xAc1~nh)8P`jM2RtB-z;nN`7} zey+-6pvo97n{IVjLD^jplcWnJFERDWx%)kC?sY0j@~D3xRuHAm7u2CEf-G{>iMx-k zu4FcbsTm;cCH5uydUSGYM~TL`^k&|EiJAK(2PQ1)XFufDYc*-Odg7E zj9$||**tAEA_nW@7xTC0Dp{>&>dW4qr+s_kfVj`jkZq4aeO?Xweboh~*QX=FFtj{Bh$o7E7_w6hiR@D?Oo%#Nd~CemC=m(QvQvd!D~_|_RGl?WF>fw?#? z`Zt3=U|8pMJxt3^rpDTaDbBKPoa8AHO2gZ<3sl>jUVn_4^cBU1L#PN#EUgtNnaj=0 zqGPq|tqV^N80CE}chW2t8L9ZfD97M-xmkYCKRmte^Wxl)7F&7+rFZ*r|ac;zKc(41xpR8!#u@X7_tX&WF_Phz_2yLPr zLKQ9?D6b1O^6+jGwyySez1Et2_8h?qE}>&p!&88R?!2P6bV|s-Pwo{w2YqidOUSr>RH`+Xi?dq$WwHT_)>SUpP znoZTJ-;U_)4-U2|1nzne{!>EZr>D~#2whV>)#=9=Rr6v^%UOe$A0LgAe`vcC;*Dv6drnF_`S`bMR-n`?c!g$qXli-71>8EOlewpzcv~*}r@-OM>x=Zug?-_^QLpR zvdC<8?oC5%DS3_)@on-BuH=Z&@Hf9^U{k=l6`tjDed$`%PIjPuSG-E0Exa$-&m+14 z?2V4NDW$_%4GIOZcOnKDqH@Ao{d6<-+NPsWy`xvcJ_xe!R2~#k^#P4FoU#M>4FAPG zW)(7j{n=ZCk5NE?cax+9Pp4P%1C7IV<}}xuqPjO5Tqro0xPPB1Q7%k|3DF(4^2Y|+ z;REezIS73VgM^NT%k{Od=Qxqd35xa+MN?FIz%S0bfVQ73bi-=^@v zfZOKI;Ev>-b=sHa!Q7tNc1ZojZc$}k9)iQWW9Gr_kZI=Z4{Zn!B!@~jTz7HWf^~Ic z#a`*N**x#Qi$=@tmD1Q?_KAw6q_NQSq0-FmQzz8sW( zUPDGO5jgfYgFJK-ng@YO2ZN#$UXybvo8L2zjvo4H$aUFPgDTL*^RYmwcfYVUpD`bL zF5A_A{gL*V<_bn3xV;>6$ z!spl*W)RUR;q8$dSA6e0NMbLa2gi!n=Lb=&Is$pQ+lJW9Fa3hYSOVL;C)@Bb1(pI2 z6z~D{dYgruT^f(gtko(_(wY=q1zX(@nKVmDs`5_mV9~T9>8D@aRPZl1I7z4Gjf$j! zBUIz8`)Y;!;|k&J@@C0J{PixN(7~Jlv5Vy4-Dia*JC}@34ZS zFgejcCf--y8~~K(u+HjQw#V+93gGT$qIhOsmeHYb*4*@Mv>Q$Z9Fbyb)N3vj908`5 z)8jSm``O>JaiXn5;chZ?N(~}d$(wrpSpbL~f{J^@9ca~|loHgoJHTObZr0I7zp}ZJjVQ}QH z#=evAoxi~YQH|uS#38EHDg2kffNEkKyNbuPriO30M-lO3No;l;3g}kth}xjUmT1%+ zO2JrV+LqMI4Onw;=?XS3C>&g!aoXdt0h}UO!X@lSZ6*0G{02uzV65{t3-(VG-scwr zn-@uBx-(#5{w|;U(kr5APH^au;f0(X1Z;;p#G&ca};B%-5*Ah6( zfd_>D@P{RM#Oun0%4W-f0e^8PY!xwP@?~*Hh9&&m3VenoOEUf=f1z{#fWmqda$Ib! zu4DMujR*Zr`g(&r+L#eg>#9egqJr(Pawfud4l?$%u?wpj5Q_Y2g?n(%gv=@0Whefv zdwy%e_QI3S&>R3L0sR|VebMD&_HuDR7#q6C=9~rkztSa1m-;|rbMq4vcuGR?T@h{5c zg-?leF8eWJV;FrbpjEq}J>s*h^w@6nb@MpiZg!ctxXYuc$?mxMiu`h!8i~v_*e|8t zao3{=?4dIS^aiLXV=hlDVo=;h%$PObs5;~&VP$lum(L5*LPWEV>5yn5F-z<Q?8Kaw&5mH1Z@t)Ml%p)KRHJayl$e1KEE zG;x9<(#@xcxs65&XVa!%yoH#b(D4Z4tzd*L#-f8DYUdzf$DGZP3;z10w!O-PhcpFwe{ z@3chqbiPNmS(=7)eC)x^K+KP1{7&L?69x=loVQyCsMV~OwatSS~OhG?Ie1q&vqYlEQ;}a2L=S^>p|bs{B|1wu%d1meQ*O}u~QZY zBQX{oM1xPJ>qT#^g(FktiRU6Qssi|kok9FM+gR+o(xdF_v@~;LnG+WY9xYt=W@6PU zudTn4RE@O?0hlWZ;GTr}jlV*l&RZlGUY~}idV#ovze5y5J*n%Z**gCz=dcUWD4V}Y zWm&7tE922(=NtBeE%E9d_f8^khe-7|j%m2t9vR^J2)Di>l<9aGP|D;vO$BxNy%d*u zi#Ff$ese%d(*wrG(_GtKzk{5b!%#upx-97|(RYXnIH-C38W$O4KxaPB*KXLK$r~w( z^lOGrV?mirS*jKTRMafvc5}8vt3_$g`BP*s0dVkNw-D&5qapk=-#PYsT z?gXcbc^r@VuF77|dpj`nIE7yw5ub7OQ?T%-hu@Wu-$+WJQwAg92ama~&F=tQ%j5Q< zRSf;!$qJeZ4ns3^wN~LQEelHRp`JTf<|?xuAk1+%v*z?`gaH8ZI+ zn^_!4;xM-b{h=~UR{1O zRPfct%UT%2lmwUeOlZeP5IVcV;=-+y`vFHpnDBxFpj-H}r+-!S&?`Iew{j zhv&_25AI@g-l#Nn(xTugS)<*5S-g zGPeqDl$19144RE#B8O`~1^M(%*KXLti=`(wP&&gc+uaVL;Z}mS(i5V(@0O;?l`veD zvDEO~)$Kn+9RjEqQoPct4mn1I)d4_WBz&HSDa3^21d0Hbw6CU}VWz)mwm4I;@26?O zMv%6=L;{mZBirN27oMR8OJQ|;iDl$Q(* zdf#0hbO#xDE8rQrD`LhrI7}3jG7CEgaGS{z+(le{qb;zR65#La_o4nB99z3Uv5+{r zHEpcPSw2ihQLw8jkf7igv})rkdf;jNodh@q3(5@xvIupaA!HK$^_`%&Yu~ZdNsGVI zmSLA+EWji*yBDTHNTuoO8ST44J*i7LI3x!>MXj1Ie)3M$t}zc;c!D7+J8)I%sPbvP z8XXfg&c1qB3LT5@A@(vY_?;F*#;1ajE@*45le+r`@vjry-9o&$joyq~F1UO&rsl?+ zU=AV$f6-Tl|88Xj^so?glsyaB)CH=c103vwLR(RX!m9Vba`CSXcYou${@bU#=Igij z+v@$a|MrmyrvJ8Q{XcGvWM=(iYa|0Ba0l-H7keWam>C)WzBlqD(@jfpzUkqu%e3+S zuFK>3`L?U8i!e`RAb{YS4}6c`7%^ltgnT1_h@AY3J{5ckc}f4Gc_yknBB+oUBohIn zX?bJkoSU!jn#)J<4;l5ckbR)5ch8T^I~kqxOBUyybBoUz8N!YC#S72V*e|2v)U?s( zn`Z~dr?9`&JJj;k7PdyO{nF~uyNj0p{nQ z9b6d=)!*AF@kq|BowLs1fi;e-*1@T!cDD{k`=qfF$`VXSdaq@PaO4xPgnBHrEnM$| z2GQuWGXKat-LPb%oo#DhY-7LDTGgY%$Ae@;|6G>=l!!B!DqRMiLr!yzE7NVr9vryY z6Pvbz;C0b^6qU2ybTkDSt`UdEL?NTn3R{WhVDD?r4@)pA#;XaPvZ~S~R^{3CHctXx zCWg^i{-VWpsR$LmhQM&-iLrh(5I<8mGSWmbe8ft`;LsN{*=D}inu2=m%*3Ddg`RMJ z75Es`q9%G2ZkgD$kwSHKatQ<)8nGDIQRR7%k+lcKMVzfZ&H~WLA%;QmOf8g}=$e$P zBzJsv${yu%S}EN=n(li=5hoqJ{j-ES>BIR{9iLFR_((V>q0u!l7yV7ei@4Y=+N*iR zGjLj~#yx?0#KHbF!=HUDp4Y?8ov-{!Oh7N-ddE$ZC~+|ryT z@9zE%YG^;s*r4j7-4=XevpB7db93F?qKcZPn^o5j(nLH7=Q`tMiskIyd`%(?5=jfj z@Lp&89oteBV$4JXVoNn;>oSS7<93 zmjlUEB3c|7N;4a_u9XP@u-kOlMmT&yBc99E_i<{M*ux^z3M_Hh5l9$iyXbxS8ui`L z7K6D9E_pb7$yq{4SKW()%*zjJdWn?wer30VB+Id_a}HuaG~f#k);|Q+lg-y_6@?c5 zKCLm`%FM|1YO*8sJ@AG@B}K=}lmdEo*dqQ#qU(0jlQFP@I=p{S(N~Jl&6+xKM#b`y za6+6mZf2E2%$`W`px_(1fzfH2V`pL0mWK`M?UVpj39P6lc)b5hHo)d>?JA0u*!X6R zQ`|~ZGSV}b`1BdSiU;wVK$xkQo~O{lavjbo$1gK4{Ghl=LH?;TpC*F4Wkvyfad42t z77f>W1pANc(yAntvP6L-PzQyHk)PL_rqH2M09tMemwppAPE7_kYzU924}9Q*tl3tBg$(3ZCX3V+;;f!l2%*vNnz+cntxNN5 zOU@MFVU17Qpsk>+>6YImzp{3|1Ho-&VI?^Rms_IIRXeTW#xNDyqqmWKv}}R7m0d~= zZL%0T>|CI9O#=`%6O?bmOC9a22oo~y&$6sY%+TqnFMw=9Z)%(iqdOa3N^__bSs|e4 zZm@X{T;Ad$N&iqX2dixr*r@zwo&IvxosasZ{1*3ZXT#ys?-XhZhnqQYqgMMY)LQZs zRRFHenShQxrnx6!AeWlJQGSOF##VlwYOivcbs^cnnz`kwzbZRwm%^#%|c~ zTv1rqbZU6UkR#SAFkU2#VH9A>Y{JJ$pQb3VvzpqUr@ssW7OvEQ{+Dix z_X;CKwZo}v7c!2osiCem6pa{)rlO(*o*AqeCP^~VU7u68TWnX7G=vBGF>zgWI7lRs zx08y_z)ZO5y(+L7H&0v@>r9g}&La7S+K3OHINxi1O+(DvK`dWG18J?LV<##%+TxPG z9zeKu)4V;OSGZkioYHI4richRdbI^=PdiCVkSeUHNdm1){=!gmRzs<>FE(10Zo1JveBvkhx zKAK>)((=$O-F*msCeL*coU>l~xPN1}bkPetgus155F-m$|Egx}3chB@>k6i)Wkg`cggF=StFo{ZK+ zb5NF9kuRjSQQPz1yr6#n#Buy$$xJ5yvk`WzpO>Cn3K18r<5lc1Gc2PI((h#gQPyNH zmT$3cPAc$+!_d#{OXFL28CDd*JUIkjXy};V}XbqSLl|@D zn@5hqA9*vaUdQk%L*BU;`gxgKo^?ON1ZkX}ldmpj^@44X8&OW-i}J9Ao5>a}ZeQ&; ztx5}u3ZH%)p7&}!q3A8pF4T4BG&Gd9ifE{MQuEv0hXJr^8Hz7dj4T|Ky%vE@_nC#; zg=i90f%h$xPMl44Pyo$eH7f`@p^i%`fME1>j{-g{(arv>X16$2L~5t0DBOQIcC1p2 z)2XGlj&E7o;{-sEW&4W#^F>mNFFwEFK&bxVX(o)4ZT-1XWCWw* z-3!lrfz$qdbvoWBq+0vw*D#R^a-z0cn$h*a)%n5I#NsV*cphx^Rwa|cg&UvV&=msn zt~gXNoHeP4qD5Mc$(e<2-M*V;HAq9qE)qC zc)()(=sjF$WC|7r24efAp0dMZ?84`HG6cM>_+x|K4?ToLg4#%x2mGn~nn!dv5}{GB zsMJ~?k&naR@U8B>kI+v*}**e$rNZ-1G z7_HX>8K#Y93-_+ROqEGD$JPaK?bnM9Ic*(+1Ng7LevlVD8-a~x%wJ`Qr{Z9%Y$$w@ zqt?76bJ%=UF#G17{aU_te`~-JxtU;_Te~$$NSY~PC(SuUU1dr+0&7HQUOHkG`dUYdf5T=lZy5)Vc#wq2AJYX`%5xz<6~ zhS6anL8U?q=pN~7K$C+Uxn@)pQo+I)QaoZOOnX>$hrvv<>1Rh>zsE#X{PTPwU}G31 zR=^52u}jYph)TX0|FU5n&4+QB3l_mBG^z8h54Svl7rhx`yvL7~4eatHIgA$q_69-$ zdHy|0H_wmU;X(@USjy~iAUzLS$t7CjJsvf}wYv6mH&eJr*`60U)DpX}-t&fUjXTPbcIJo+mI zU+;q~aw48?0>LddTfeKjZwexby*8LGiMjIqt|*cbb3iPQZ(txdGxTGxj5ndb6ym$* z*H`DP0aVdY*sAG`MvGhPazKRBRxLy@FqbI^x=XLs;-LrK_qR#yHxyO@gF=KcDvIK~ zXQvuB*08Ox+)G{#nPo!?n37Z%xu(UrXB67Re@^k(AFRzQPST{rLIjZ{9@lr3)Yj2^ z+AIBRx8-+|sfFMgGg8?R!QiBOP)fM^otg&|Y=4yp!^NsP>*c1L(~IHAI=V#k(U0Yy zOVflnbldgyhvb(hrEdT30W69EAq=V%&B)gM7-vhhJeXxs(ptN$m<2A!U_@{lvFn_d zCk^auAc9f!ImI|LNn^ot+aJNMMnk#Lq38Ic#zXeX6Ex9vq9$YLPD^**iG_?w%!$XD zHee^tq3qbpV{w_X0AG@(p6Re?J#mC?sWxyd1z<7+EVEwd29!W0QNZG<6xVdv>P#MN zjy*k+@wccOAN~TaTYx-RySaoyu=$AHI5`3fPV{#9!Yjf^agc=kkom5N{P?~!G|z7s zOfJH9{uR4>HxDf-HMx2J-+Lgwyxz!l1!%LVffTsW$_S`OBZV`E)~&y0)!ZBut(?^K z2K=3iMa*13kJt8xDK8TbtwRxo2dQ;Pw;~U6DJ*^^Q_Tji(oCW;dQ%)tf-p;W9g@bg zs%|Q2EiTHzVxL|0;rsmW$n2Qg*UAfO(Q#sNmHZR~vI~3|c8o$_5TX!kf z5YbYOc-rs_KR8b`!ek9WZQ6^hxIKSA=>ydlh>O@Ud`I8KHKvcspJ{|CLQDiNNDt6D z#$c)_`npfkQfuaWyad)5RW;5!hT%AR z6_C(xZY1u8KYFjuW&^rHKL%&RzC`_ zwqcYI5UG9;*9*^Ud0%fKc^*88VZiXPV0=xS-ip7BWh8`mG;#%0LIHU?bwQUanw-eG zw2Fqe!7#4=z;_h-t0at-1zz_xg#S~|kkmy0`Ib5=y64{3{X0K3bvt)Ri!koBW2Pq3Djn;FiiTQ7 zXZ0hyb>tWCbGm%Biw#1FWNMn$=Wd?j%%T#O-7?Sy4rf$1Mgk>|NIB;yT7qYPvJ7eE zMnBx>GVhmQj8D#9A|^1wibdl|-Q6AY)}!e#$hHct$r+>8&&D!4?~qsH>e{7z*lFLq zW;9=V7*Bs5QIAJGd%Oi7K}eambG5HHnuA%Ou-Qpl#PV?SeqM5tSz1{{W>9CD)M0F{ zEk+%6zq-Juf6kZ&>3Tk2obTcZ(F`wl@YClcR#^cjl*fxjcj)B)f2&}bcO1XORpbtQ!eS$ z$=;-X9P$~uku-LxTZM&{Q3QE;5MeWuHYqWhdIf0o_NPyL1l1|>F zmyJW1TZJ4YDbg1JJMg$s0O3oR_Z*1S{~ndq1)yy9R*eCj0Ws>%R6%x+VUhlbVbJzF z^pislc3sZrCwLgNOEb6ktj9#ZU|}Vz5#_?YQ&vPh?)??h-tXefj+M97xvK1orD>le zWIE?F=;0w0 zedfutdZZu&I26_GpKtdW#pesADIT**@j<2B{HP}FcN0Xjw#*yPj}cWH*-J5($=PcY z%_oUSDn%ojy0O>tmTCzHphJ7#xJxTJP(J+P2y2)!g+9aaNqa zlR{M|AdjlON^Y4yd@oMxr1|`e$D8u2`Xqs2j!R z(K_~5C00J)MIT=54<}b%dC29#3KaoZ2Tl^NSrcDwC8Mga#{(C1=yuspJO>&)ZUb*k zP7550J3X=t5CN~sb1=X*3Gq#<=vcK?%(6XFQfKsWrBZTChSD4Hr`fww{W3E>#+~%D z#bNrg)maq;!=d6=)4p?14}xIV{5doiO;E2@3>?V|I%9pC3E^}n)f_X8CuPnnZ`-CaRe3H80= z4z6cYj7?SiR9MU&W>!i}AAE}5Wqmw)ztHtbw>-5Pb7tLV_hQoA$C3rpF;`fw4(a>g ze42Fi!NStvLBi2Nz~;Z#Xt>!~XOor4D$6!Rw$Lb*QfVby++h1{d_Cpp z+OE^QXKzxzwij1d1a8&2dRs%x)ia%P#<)c5Wt`aKJbP(?sA>9NC^bpQ(G|ypK*=@!)|m zEbp2MS+QS?s2#QBJcfqYv?U^(lYc>v7Q6qbnE6OxlU74&|FUH!u<6uS6`Ns^P0L{a z;gG&WA^4&#xZ^&DV&I;5%_v7OW~TZ4!?B2g!_viCi|Q%ITw-o4a61kwNgFXC?(%%k z{GCmR(9rUCTjx~0=@<%WMcc~9!Fqc(SyT|;5ZFp-U3}Kby`ksjZF%`q%k6$d=&h_V z%XnG3tbZ5u61Q{C#_oV zq9Vx{H}AexiH9oCWJDQ4+PdVm@$h!bS)ed!Mq z;g{LO$SYZ*of=v><6)`BXO`qD>`xpf*hj|beB72s{kQ#zAXg>3%du#WEf;r*}gK_=ybt-!kE=UHn6fjwYUfrSTd13YNu?+4~%H<#yY^SJoE>5g&ihIZ@UqD@x z)5C`gn^k#|9JmJTcZVA04wJ=a0Fh+_(b(GYk)gU6uJ@K#19<>10R5k|Vec1%oASA> zHbc(8TJpNS#HKS;2(ymz7y$@;VC z77u0=5=4IyQMAOO3 z$7Wn?WZYB+^D2is%?c2s2V+mOeT&N*9qturO4Q{-J-U~M0eM|&UeA(P##md78ZQ4*uPGPGA~*b^(N|Rvv3);|;~PBd^6ZPLRug{QWghp3NCZ{D)kH1P z?$3|>p=;tf-QT~hC}81dbk7iUv>n%SX?Aa0?SY$QmGzk9qnq$4Y;P8VEFYYsOyt?E zTWOHk+XPnSF3&Om?xdTV80ie5xo9S)q$~G(8oP!-B2nNHnLn}j>?>*aqoNnLPhGtb z+^!+Ma4h&`x%26|hQ|^tWM8MRyH47h%s^l3&88m!HVel9abZHLj{ZQJzuWF{YM~u= zBQMJ$@-ylEh?L~uM$k3d<`}%b>c}*40UP@2yQQcUG()FR)1+=v9t{7VYg^CATx#aR za=*)jl9&(A+MKuX^H!FCQ_;(w>jQ6ROD3Dc-0|&|<4B(?PPcAh>Blgx6MPydDqn-s zr_Y-Gz#?pF>Bcioou?*GlI=6b=Vf>XopC%fT19FoE%Z9zHXd`o`{(Hv?2^Gd~dL`#!m^sXr1Ca#;U+fdaZ_sV+&`3u_ z!>QTJWs>XbODeUsA)DXTdk};2M0MNK6|v>|8Gb6xuQ2qpAhVVPD_HtO6;g>=>R8|1 zAheV^RHxFi=sFKNS@YQM8ETwHNT=w2T}{L%j)(_Bu*j>b?-(SvX~3nPMNebPE|3;C z>9u_Zt-LGab~go3;`5~D_A=z>v~DKmE|C&wDc4f~OLDQs9xFnq2PoZK*#TM;eo7_z0)?PQ0}m3|s4ce`h`^@o{B>527GEfnT_~?lFYe zP`nwtY!wx;eQ|zW3#lEUA)KLPLnR{_l`yZ4>S~g}M}Qh65sf!^$v5%PzMW$yr7vCO z9rjM+N|Xbiks3BwE_R@VIHHP#)M|B*>p<^JJ2`ce!!;`@nH99mtU0)-I~6!e9mJ^= zVolCHMc8zmJy#Nwu=*cj9dwGVRSG5ossrok_yxD@+IjRWdUj3^KIitvWnYhGa~A^Y zZ6z(P+?HLtDXAqM?!fXk6_t$uMUs=SBqGzNH!H8aALv{M7eb%CBM2?Hz=tuSX93j^ z{8YS31pi?|&T_bf-lo8EYf+yPV(_L5c9B+Oi4}UBH83xFyKpEi=#8@BR@a+Josfib zKrup?7ZvNYV5GzUY1sKw!7X~l?1s6Wg|vJiumMh_H^*ev8?@|~+fW5b5e@ zrLoCX)|`yC?fb6!bwwG+NC!_j)O%t_rOia@y84uLF-MCys~>nVHXRVQzKx{cT$J)aIb=VbiYn zA(r1#FAc4NV1u3cdDGn!nk&2O_}IfiHjzRO7hp*D*`}S``=+h9-WP9F!yH#Tnh|ws_~R(~f*@*jI&D}zxlR5+ zs0licutpId4{W#lM3K7q*2?tvDn=a)Usz)#923_|Pq#7bDmTmXMh3&E0@Ik+h>L&{ zhzr#JN|<wWklz$Lb4&1zx{Oj%2v`r>6e>H| zf>dq5WQ{8GmxWh@8Os>JMkniqxgQun!N#1O3NN}bFE$=yZ{AoPGZVm&W6_pbiHX06 z)lg?2WaZHCcrmImKDv0gJXJECwn+uUR+kUwd;Jy`#LwT_Hf~SMGO!E@Qm=d}_XZPl)vdd_@B7=1mJ z_ayKQ5r`6`^oKRiyclQ&>qJhhKGbXL~JH1@jfU8m5>WPPNIoDz=GImR$R7yrf=&KG2U%M z`lmXAy&)3en!p3P>b9Y!G$1Af<*oCG}?8< z>8c{bwq8Xi)>Y{gbGzrI;hq5hnqUcF4OmGE%VHH`-4`(SLfl8SpqSJ?Z){Uy`8Fj9 z@1d(wG|AeNCTtkWh>i(_L~XE&@RPelQ2C|=PV8eNvg*XDYc($`K6|NeT}B7;b+!F+ zZz2mQQx|}RKRLt?pr8b}&;jdO<6e4SNT)n{>X!m3n0Rx0y95#VDc*ZDh7Par9dS|s zfCF7LcOac5z`m5bA9AcWO191p%7f!!y(cL289m#NBueHF6ZcVQb;r8S<&qc4b?Wf+ z@4}%_Pdw+T7J8b%Yvbt_OW)Pqu7SLfO0~v}R#wKUF6zuAGsT&M$$`@kO5FDVSqBtx zc>u#t#PyP5#nP5iQa;`_9hbhz>gY;8g>cKS+rzPn*8>~*48rCbts8Ie1lab^sWyvZORO8&m{%*kgpeCk(XuaZLa0KUtEOYF1G z?!%1>g^XR&6JDEA()prl`Bf6*1khl^}#gUx7aR{M~%eWT&x5Mz6!@0^tXfMs5&^V?8IBjrvZ0R55+90Cu2?fC4Kxp+z8moMj~iEBt(CDi0irx{B>{rVn2eNC$}tfn z0syOQt7L`>WM5tc9NGBlE5OpP0E@#&ZwL%8w040lEW$wRL@tnE4kzVNTetI-R0*zp{{oC%lIq-b1uHU;#(ehvC4k2$I|59z+ZC~odf>R)r7&n z(D|7AMCgA3F;$`e{<-1U-7gCR(RTINg%*|aN1AOc;I?h%$&LE#Ut{q2 zz6YPr%fF|UIWhi>#@PESGT(%nO|n}|YKj?qusZEOgN=c&MU3n6Iy_xlwC~$(dyj0k zoA+bcpEK&oShp-wyJLS)*jUKm;Qt;b+5y~sJu1||Uep$?ZDYV9@FTHao zkP^{#9H>pew!p1zu=BRhT&);**B&@p)R=^TKdcsmhjcKTvx%>dp1`&o4jS+(`edpSA=WzC#>tSPY>AI}Qmyw*_om}> za+Ytu#Y)!|NeToMmW7-#!0-5c9>dA-p@we0Vp84XalSbTK9z4VZ)j5Qd8Jv$ame;c zh5LRh$2_{|7Alf_^50sE?j$d#obv#IhvyM3nZ{aQ+cr}d6n7=3jIDCKj!9B%m!C~7 zo9?ySlB4i0Y{G_`&UiLFr!#XhzBhz5Zs5g2#;CU zCC*D1yrTD51cg>>qE)!mPXv&jcU`+c*`?YrGZZth&GCJTeglcHV8;c=Hvr z9E(I}e&g~2qKg>O4V=4jh4BZZG8n*r|FLlWZSG>C<-aCX|0f4Wxj&|Ua{3N`lyU!3 zO8$~jcl=WSH8ghoQb+zb%JtVPX=5jS7W}_N+3Egr0%T(P(kA{}{h986+CYBoD5l7Y z|F<-=m?8timu~dG?ej+<{%f-+1ODF{@1l%fzLS5p(TTF)|D_uJ^@>yXFxWNL#HQqC$_Apx??Za4vlrAr-ebU z?7|3LUlKCYVxy;RiG=q3>lxi)v4Bv~r;zf{Z?D;sqr@|j3GCZOakzNzucnM#yb>j0 zEd+>f#>_}@q`(-8RU>{(79&r`Pi9XPlcM37lcTo7Vta4j_&M%6v-#SS-aR?fE0REQ zpi#cbQIyG_?P12!q3i!lCSGbD7NcH@AIS9QAWjaal&%u!4_*6l6+$&s0Ze$a{d5$A zd;BS0^=a?r1n2rP*;a@`T>S$%ELTejANuEJKj7$h`C|}`U=aVf7;XN!5B@t7@8$N> zVV;}m55Iu8$VZv$*O>PoXIb~^^F&wb_oChlo)XtLKyRiA+*>z>)##&=FTZT9z?Og?LWg)WwlF!-S6 zk(Owrmtab!nFjHsw3*Rh3%dhKEp`W~Hnu`-F<9yB!7!&_qlOMV!CRN;?~SM{NAUZj zJV_LcSpaOp`VEk(_(>vK1&vb??1&U57JBN$_UJRj*>bs|iBFi0BF;K}-d>pNg@RUS z_C`+_jjv8G*O9`2MZQStswcvVt?hxwJxwAnSP8xM1e$KQ%qe1m(5CVu# z`qTwa2B=?P)S00}DLD3JcEJdKAXnAE{S5J$mGtdNmtid7w5}qWTV}-K;oV8GV`;x^ zbD1~q=aBN*pFKc=UNbG>oyB+t8avv6Rq;`&wBpq9=iCDvk3-&g?_txlNYP!ErPA!< zcbCE~mV9F)I<;(mc!lAkQ=u;#hsmNRfH&7YY*us0q-~^P4q2qN;m~$LT88ur$CtEt z9gA2R#4*7Ou&hCCN>1=!hAAMIl2ST(INZ_kbr*4bZ~pd;3hA36kO}8ZY&C%&@vsgG zY~bQO=+J}hGihmK-{gCdxZt-=CNgL{tWMnKq`q|drpn_XjNZGM*nLYRnl+Hyv&}L^ zV_7vdnYWtp1tS^J>4m!nGPkpaR$7sB=;Vf)JA!us(FEJv^$GoWiG5(az^1f_0#NGT z9a3nUv!Kt%AKM%?Q2r69lpR=bOB%lxCM}XHkK8`)riA%|aG}!ZUml2Z3Ae?x3wCXD z?Fp_{kdh_Zs(zveY>(V0yKSp@1LByKceF zW~<3*L^_@HLD>{ye-+*C2np%%_{Gi zqrK%e(rQ<^UlT;XxfUC#$|*)k+~jMXnb2kJJ_!QGc*nbNP(3oQrjQ_O{up9?7xqt} zJwJm!@)6L}`*M^P9}+j5Qw{%NO!E|)t8t9r=}R^$JGNkvrm7;DHEC{cdY42%G(Q(7=mr`$y8BVEr=$WZWSa0ek!ct}lxR8T3??JM>H zNv%PuCUY_GBF~to3rjwH(mMk=P$h=VpaHpg;*Z~MG~WtS*DE*Y8~WHw$&0O$BEoeY z-;QTp+$h9Q=aChyF#=cli64sn@Ul2*WO`$F#515G9R9t{6h6XV7Oxla^An*G zcS}gj66Xytf5DJO)UtL^2=(@)G}l{5<&fZ2RP#q^?sdnYx&T`3=`ASyxZL76stV{a0Xol82~t}g90(303`@8 z4-Y;>C_@eU`hvdA5lA_-r1O2pZwbrc8&W?$pyr^Pz00CcNizPrrhBp;mF{j=PiA1K zCBMG|QX8(JtPD`hhHzfnw2F{;i?F54aA=Bl(!Ag4@7T&B1$FbX^f+&nAAuswUvv`+ zV?pH|3i*e5F?-BIkCXLx46+WC9#BMzz>_v}ph(;#+ zz+SZwg#gbj{z@S}t)CuN)}z8sQHzPj{)h9!nrZ1wc)W@ksWDX};t5&NWqE!UytfUeO{guC z&3xAcxw5<0>CiRnk8s+*sN+}TBdm`6(F8?ex?5$JcU@yCo&AA0^kGeZ%JGMrv}1~d zGt?2{wlB7|zzbN{U|QQImn{O=v!e<#HkM|rCee|uF_6bWl+8=#{p1EC>BkFL3E~=H zD4`7I0b`jgBI4F(dZ+XHe$Ip*(aN*SC@z%k=kE{v(o=>+Aj^*-DxH-}kOfIYU)|r* zl|wuFNI~r7h2*i!);XVz-FK)3^b$9oYT=@W)f!IzjRn0iBnM&$vY*VK6kVYHnpqo( z{?jE|6>U$F?^s(06x&1}S(!%bN`p}lLd3bQ6TpHDYdf(rm&k?H9aO1Pqj7z2e?eLY zzR9)_HCC_DH6jmLWeACHvar7k(>xd-hqH64$G!XzxTzBNjt61;7npPwjjk#SdkYHX zfdP2` zA2;rQ+_?X7Qb{`W4x|9xAO ziG`ixKPqZ<{%MPfB7NBOeCo@uftRvZw^S2Hz;MPhtl zravcf;#XsoR-n&st#?ZmEfND5WN+bk6c3p3Ekf(#^lp!2?U>ClStoW4h+-$yVaBh* z&*u@G7+9q68~Gz72oqkLU)^>Zx>SgR9UBbz-uYEzKzt;;&emadh^d1euTigG&!$Bz z%)%DIU^Utk%g3?eFE#n%Ox$9%K4d1eDWWo~NQE`{H0)UTr`K0#-zy0)o2SkNZiQ5j zAV`_*vSMZrWcf*L{!c(~sgOLUa_p>MQoS(PFy0CDPeC4;Km?`XT7C4Fj_0Fo+5+y? z(tD{b)I=isG4h#E6j&bb;gaO7{S@~{{7`-9OSDq<=&{lqgTztV?BJ9}!;qzR+Y|I_ z8!yawF=l6}jC-*0A$txmQ@%eRHCdQ9F301=pcF+o0Zky@zVF81FA&rI(nuke0SOmD zRYU!rDXJ7UtVB*o@&VJD*H&OxjThgJjCu$bMwG8B-{wlElatWXe-i_2D1l>1>DpW) zt*ZbYY-QXZS>mMcp`A<}%Fl)NOGJbEum&Rz{?b064PR&o?D(#JxeoO^`ovgPpUwmQ z{FSso@XS_ySZFYtG33CY<&#JJOF|lzK$_jC7rsb6aF7xu+OHpT)}XV1`xS>yY?+zo z$9qkF>aPL=cD?6RfRE%qz%tmD`(G)Z(4OBtAUkk46l$r%klb>DW60aIwroH3ORG3aOM>wxu;wC0B}k((>!lY@&!=HxGj zLGVw&mAY!?3&H)-`V-I*@N?_i_ZjkwV`i?`2gfHW0EM5EvO4ctYuhGR7^)x#kjwta z6ILzZP}YHM?_F(6#Z$S3ZyA7sN`U?hKL{-;O5%kbs3(8=J75{yDzrw^bm~dgnV|1RhC}B)ZOci7z5bTkjJRRdwly&-XZQN4g}cKcpYPc%#MV%Rt#>#*_9J_d9&=m zATw&lN+%kj&ij%oJx7QY<4^#0$2of0O++u`8#8ekQERGR7+DH)UgvTtX)%1x?&fKf zGda|_(!43)1aM``H;L1-0K&Hb;y};pK%Rj-rK~ET-WSlktd|+Ay~CbI#?Z^iyjn{6 z4Vg0kMvTVv@v#tT|1k+klPED17F2zmAad92lAsa0uy#UqmB8B&Kyw_Uhqq}ubRn`v zTAOuvzOD*{q0VN!jv?5(bhjnWQ_hej1Q)3{JeTtOlnU1OYI@}7{9(#?wTKk}R|Z30 zVj0WKI#P!{M4~-4uH_#+Dn+&l>7f{wxLNYw{8u|*BTY6U~lJAILI+LFtYi=khx+(h+W_Syfim+$1H7YLWlG&Uh%Zw>2o5xaE zn-&%zMEYB_YZnO?sM82=!Yc#S_oc9ID!>->Dp#6gENGp#8Eg%plGcc94Q4m5v!H-5 z!_L?8cw6T_WG`H{vv+<~g{6@^@6nR${op!H}8!>zh&tS+_B#GY4!d4 z(-pH}Ws`tsW;{GT;sh9_F|UT#fZ@DXv}ykC{Gq!s{uO#H#V8+QtkK*x2)?{8vr_Ps z{{!HvzxRcGyq8Bn^e?RRzMrNZfnoSH%T5~)E>})-zndqbcGsT1@tcLN*NOndNI;GJ zFvy5^YxqDhjUkS>vEpsTBzsx~&JHb#7|20TU?irHCg8>ml(f34q3|%uqjnE;YMsta zwlrgsHsKnER&YGCKTjcJi$PoY(GHO@4@Kd{2Sq_(tydNyh)WS0A8HR!%!k}uaHrk! zd}AUS@4k)2<7D|hyfwk4u_m_EKHr{REB=9F@sh2u_DjnQ%fe(X{&u1p^|9WBtT&EP z*c$USTMhSd){S92N68$K%5k|_UIT0*%tU#Gjn`^>(<-!J=XU}LxPuiK)HFB+F(Eutr48+9}TEW%XlT2_G<)f!bExEiA^kQ`!gfZ~HJX*a58 zo%tjDobKOtf1Inl?=SZe2mB_FjfEb0ncj@$4(f)YF4WuYdE2U=3Py&x^|6l2n**!u zw#We>`0{Jr^f>7EZL~$ZpeXZ}j&#ga=Jxc>G+2&==O%rX3U;fR2Vo&;GV%zJ1qHyu zVq~CaM)s0Y8RWUDb*vEYxLyvkof5~K3+EZgOyL=rH&qrlKO?XygSTJ|GDe+e!0lit z9Mqg|qdrHhVC)=(mv)D>wAO{7uAwFqf7TKXW@*{O^pR3fOlEQ}Ggoe|<;M z_|Y^y%H0}3S5{`Kud9r9Yt(iz^>~debsp5C89>=E<2R^gkBB(5ga8wSf;K*k7kOOM zqn4_$i+%qD=!+$QfSFr0=*@~e1cgUt)?n`fjGDJQ`(>8fH+>kA=<~0AQm( z=5OzA8$MzJs$x&q%da-fb1UM0qz{&EmL_xktH0vO=*N;E0Dle}9*Vg;B(A@aqQCqW zaNv)W>8b5QuD6T*XahJlH5$d}nD4dplw3kWU8CtSa6sQ{)6;G$A*~D5rS7m~lNWNO zJcMv3g$x%A4>HK$p7by@rUyFMc)bIu#tc&$yeY>73D6b*HQ0d`H8jyPffT^CS=$+V zg1P%P-1c#oE65zX*vTD>^TN=CW-AQ~G_Z&e&@E^{DgJ|RSag|R;^YlYEG>gn#%Af3g#J99Iyph zGjd}x?J;gpyDzk|W~NR8+eJE?$9}JV-7?)mFkw}K;G;_;T$2x)EfkuBdOlqY=@N?? z%~&Fk61w4_r%9aLl}1!+d^X^+&>h3CSQb;H%extG3JnpAc4%A03yw@GsM|SNBw2q& zp88|seY%-MnAmKjD~SZ;Ko+h6RIMZ8S}u(cqb+NCLD;CAWO@y~wi1agy|;_${wdFF&~X)I#vS)1 zGIy*JV@j+TjSyF6QLSC?dZmtspvlii(=)TcpAaw=Wow-hGY}%d6XVQF+Wal#Jt{p} z4vMyP5kMTRqc;o}2kvmX#u=de-#n$VVT`DS85h|x`qg(QoL-kRhjJ`?z=kupunz8q zpmz6xoAhKNK-9@J>!;d8JJeQULSxbhz-pdYcoOC-ViIeD7#fL$kG}X;Mpt^Q)5-GwijgL&1P4rfz)97bIyz|pP6T?6u zx|>nC=Hrv-_SF*@5gSVF(VLy>8H3?*8@q4}yo5e*E04y)i+<<4a%$mvy+GZVm2 zB=TCyuT8#uY!8PiFb+gN_ll?|OV@fqk@XxUisnONv) zzv#b=>>RZ8tY0f(W?EK;ua&T%;uq^z6`%f#(kqJ3OegR!4zPfslew+U7sFRsLHy4r zxtWubog*h5os-$u&{ocd=0>!Jw$^k;wyri-w)#enG-l2QbQFI$$N!!ajL-BRD8YXi z%>N4Y{}8(HH`|$s1OMys{|m6;e~&G&{|S`t{|vUk{2%iBXXJn7_rJ#$7}?nW`ZY(V zzri23%zuw9*dKwH`l+s|(!zHF$z<+1Sntd)u5TkCgVD?->uEZWbIQCBbibP#_9TCV4JFQW0I-v-EJ*VkDEOD!Ub>Q#rGCJ znJ`Axk6LlE`*uuRl{eR`Cl_>oNa$pBkOsIOrms!F!*^lhVuLdG__jc?*Iu?JN+C_M zLlVi%?pE5`to}Lj*G)Z{+6f*5g1UK0=WyBv@K9YXpG_ zwg(otnZ;xRj*ou{2haQX{Zp3lZE3p~%WW3VMU7M72JAZ`#+V3f9l%R7A3g>-Wfs3< z4nO^EF0|P!A5y2e?t{&Xe$3kj-;N&fZs*d|`|C&CS=OEUuRo;govxF0GB=LF@u!*M zytq6W9v+$6KnfGuOfu$WI0@DZrez}2r;oceypH%ESU#3ak4h%BeIUhlOO9}D3`&k@ zzkGWD6S_v$Euv-2iuJ(Yg1f}gJ92T0`M;M%N@5&%SMfyIXv%F^d|M`?mCPqUV+dCy zFrKWEZ3*1L%2bGfE5MJmKkrLy2V}u0PA!OoHWdp^jYw8XnV*&V$WK-X?E&`=noQGDe2m{pC?#SLNSl|J+$OKP5XSeHE zd#`lNJg0Qy*QTG{70tKKo)Jd~`8CKysqU2y{4D9$$oq?#52Nr_GhQOy*EY1`Z(o&% zq+6>bIYux;?=e*I8&VUCn-Z(ch>%qdy${$Y6AYRM4G#_( zMNdfQ<&|qG7RIn{FCdcTs8P7NJL={-i7IOVPZOjkW0d^O8;G&bs=YC*j3&gLd@J(9 zFOMWQPz{s)jVN*hdr?5KgsT;^lrh|2iZa9-A~F=5d)2$`*pG|Kfc+LUMX=k-DNS z)F=eQ$trlD`x=i=S^HFW3&IHJ7HR(V1XqKDknhF&BwM;k7R>ttz2ukM?!&?E_2BaH z@fBXc^%*Y)w+^pNy_gyo0^^jdPy3TMqxYN3we*tX+53DVJ^4xDkZ@lrd#{&lGl)32 zn)#dl4>mg9+!As+-ecc$0nCcE^mp`M<8Hh%VjDP2PPtq}h$%e;*=z{c2>5MhWcyFl ziw0b~%vPE8D_OIyMy9bpD^t*P2xFVJrc7gw$qASwRUlFeEtS#89~&pZfK9B_`}^ngtmK?YiHi(WbVlT zxjMYs{ETUqD*Dx|yqY-~Gze;289cQI<2yQn!QA9C9Mb zN$&lP%|lmJuZvMIOg)FeM|sQ}dquG-p? zq2Ex=NXBGf3grWRoRwxjvvkgotnCat4~BAoN+qtl;S@T!s@?8R{=yP`0@77n)KD&? zGFY7OLnz6wDrdN{2|9r2wi&d`ocOl(W5md!IhEQQ+xS~#zob74nfv7|4z3Dfvfk$mnE9z5K(be~SSUAP>2zk%m-lD+bKIhe zgQ@{)7RR!ttgk?InFPf$YZ#l-m^K-ZQDG zO7V6ifl)Q^g(8R?dbjF@x9Zd02M-zu$5S#&rg+_9h?wYpAO;zanwsV z3@r%~ju?{Ez7dKo<0Q$&E}yBeQE;~X_2Gj{h#u?OB+U}}${tQproV>X&{2!|00SF) zYBwzpTW;9lwrZ#yG!!lN`ahU^%cwY?=4~(p2<{LZg1bAx-8Jao?t{A|!QI{6T?Quv z5AN>nZiBu2Z+@%`>v}zL6uzWt_^ffg5?!_o>h*m zVmBN?GLcg1<6FPm;OBj4ijc57ar%?cXH14J z6@J~XU;Co>$2W{}HT%xmb1YHNrUCS*M*PZm5}LpS4;HkpMrd(BL}V?;;m0ogzWWkR z+T+HoK@u3wGT~AFWkjBOEqMo4B$}v+Do)X4iw?{l`Nq^AqDSRwATvy&*)&{ENDv2` zeV^q*8h}0^FqhQE`g~E9WC+zb_%21);rcFxb0yLzs7-{9>n13xRd@1AphsPF*e9r3 zQLxkJjShaoKYmnfw<>S)0pA>+(?BRI zJDNMi$H&A7H7DX@LbYRIqA9okYP&7d=qtW0Xj+r3q+a_NW}zn3U7n|g7M(}k+_h+f z{?UXBqV~Ij-noWedgkMpueqDqUHsxAZfyu%(0?kdu3^7UCcj1t%=q0E-sQt#5)r-n z>UICn{+sRQy$WlA>W7_jkwPeyyKJA;*GVtt{(bLYrWTpRffxf__Y&Z@0R3+Pg8!-5 zGbMe2fHI4MG)1HKhw;tqC7)?K`z@AcHY-=uhg8Hz^k;`ZBn0n&?;XFv&%QuKAhk#$ z0G~J@D7eo}rrE_f0TPDzpXbMagkp$kR6tDqR*8>yLU?cv{htc3&Q{+m5gI%b7` zEPW6}`fn#nA?oIqm#60?chAc&@6|9ii|=ogTEhc%h>`>CaW?R6%K27aZfMcVU0L3l zx&E#+>pckdQ2L{J*30VyzjQ#c%5ZqsnPy88UWnvMb!llt z4;@zvi;ISaq43XU4Ejwt-iora5MB@G{WXE^r8Dp?Is_%w`e zrBG^7R~{7WWHr2uSTMgv&lczLVBI)hD&W z2MvlR)VM8uZjz93Q~w7uQKIBTP7aT`kp|r9VpIr`;v&k+rQbDJ?v}TF2wDf#JB_?F z1dYjA=R{q_nB*xS((ee)MVUcFS-b*@M64{=1G-(G^&$59u#y$CR_f1|6YLd<2FBHS z390@w2ZT^x>>BZSCc@-nh67Y@;)3J7i6Yb>>>{tYiO70D5C-@s-5_Fxx#EXrIup`* zLxb-`B@>pG@q?)xPTDzLN$En(u{fe)*qQ-lDb+*ANp_Mb9mZe7-__DUu^1 zzSNMFs`%L8SedN;VTKRk+!ESL3mt8KToyvE`3&x9*j{U}+tW~Md~g3S~^@Z-pX4d%HSS;Kdn` z%r=w8wwZx_<+%2WtUO@nsS(1fvv@tBPw4p*@3$LZX}0pO$eXsV`g|=Hm!tQyJM_$A za>QXA_r$FOLgrM>>sXZ^PEiH$Nfczt=dKES>JWquCgoK{M5q{-Tv*o)yqY#9_D+EF z*ao2$5sCILv@Viku=*$Iotw$=(2u)84;qSZ?IdI(eK1REWwY+^k9EIxfUd-X;eAZ} znaTXx!~3nlQL=p?n$@%Uojb?{TEEt+;{6H`M7i|(XU49E<7h|f7g3FdYOF+ zT_Bf+gSwhHkJ3oUT;~!$PHbp_3U@U*??2HG@0OSx?^$V_cKz7rZq%esvg5iFp($T> z@MM_f9VJu(JGf*X_b0Hhuup?iGE__FEwCks-(wiE*;gj((bL27+s7Z@rHkt|=>wrI zmFXTt(0U5}c$zvaU=d5K^m_qUudpJ+ar;zC_3J-NNphPcFH%cO~sx-L|#!=?_Fqm zvomzT0Mli7d_*Dh&~0CCe`rD(bti^C^57vON7SZd8PUD()K#eUL{RH* z{ATK%DL1kk{QTV|bt{QrcM+nMfI>^LV`{6R67CJx zbi)#(TTG)igYkr&rL|XH5&r3jq9}lZhJXfCEPXVBBexm~LDHZPme?T3J{=IZ(X;;7 z@DqHoQ?dvu8Yq|RcBDe!aO4A>O;^2%@=5LSkmkkJJB7C<25|V*Y zL|9x_CiFaC6uz0j#^%mO>6ZtQf5}68VvclnJXOjHVX`Xt;~x19R}$4u-4yYuVkOXM zC?&f#hYjK5BSF+xI~N;M)On3C_p$<+LhM*bJ=kzT=f}Rft>-`i@7;iPL@?*i@wyq1 zT$XU@p8T|fRz_4>ETyz1g-8JLm0*GuX3dYj)7Fs!68sK znKDMLnyj6ghw-hPTtMS6JhY>r@v$|nZz|p|C(*bNq{KAydyW4Bn{J4$IZ={N@AePf z?NtnSdu{>(^f42{H221stm9marJM8N)3N~y5!9Ak%%re)eXe#TK`J+&uNXVem#8IG zVH0kTsfRsH4)Pzv#A&(#mX+J}&anQbxqlW2vicxBKW$~!LHm^nXav`s)>)AvXc74K za@yP+2EGizpD6Q%mnYMO5c~oIAwOd#U}0AyZ(RnPWWzZ4jzR^S!3FIbfbWtGy_@X% zr7QfozV(~BEktONtEI5XNfooL7$;$;!brI*jP*5*pDW()8eh{fKN!Up>o6)IWoN&1 zMIuN(z^3w|xJ}>2UG<*Xd%bLIw{WirjGoJW^nvVUi3Ft*c1;MurLYAx)g*@u) zDZl(me^uamApm~jUPmU3u*Lb+1esB5>sL%BP{kKe_>CRss<~n?gv6mXwzh{Xrk`2A6mEj4L=nI7Jlg}-iNX0NjgvV zkmcd5Ci}gmviEcPl<3+3gB z80Gck<%^_4-uWv*5L?oTk`_3kWKD3H-rk99d%Xl=E1tcYDkn(%5`pSGd|P%X%lV+8 zUuf=Tmw|CQWNd{cLQ2<|6HAZDkrYV8N})*9VetfffSsZnn2WgHu_!qT9)JQJ5RBB_?f3r0f1-(lT!c2+0kz5!7xI(; z5avY+ZHpA8#uwP&K6Ys4R3oBkRZ4PD+#fZ99>&VA7-FJg@ICOb^h~&}$bIP0!Nxj6 z--k?&3SgyGK8B_hD+=mp>nPWqx5eu5QNs2h;qGB3kQUV$n~O2tTd)iLMh*Ca7#R`# z^-HoWT4#)@j#tW<8Cy4ryAgD_uz6jIwkrESb}FXnV+f#B>`QWJh&}eT2dqi{hMXe{IpPkly2p(NObLxo1fz|k0ku9(~(8_UZ8viPv>S2x3jMb!H4 z(CD!hzsU=sw~?p}mvwsGgvZe-Q+$vq6YQ2psMhIet}VGrVqxcOSjy=c`rnEZgYs@9 zDbBo8ze$A&QSV$e+XVE8aU)(JHFXmiEMFpi#U>T|vK!KlQREk2|E6?ly3IWsP6hJA zr&J10Jw45`?TwRo0qK!Sw6#I~9hB@qzI$Ca7L)^t|AaU@{2xL9wIK-K82=NUToJtk za;cCHiGLAb{OzRLVcyjj)BgLP!2bqikl&lJE_g^uu>V6-=7I7K=qkhiRd`sasl~;E zz>G-DALp+3|GD!=53lddeU@&D{~^za0S)N+EJu>6H$PAzEBOjrx@808id{RmrxVc< z6FPXLd+gpDpYs!T)wmP}N9#XrySohBqJBJVZ}50IdXR@NXj~`i~FVOVdq&{{%z#KlmNZoMpAIUnDG-PcB<)zSL%)=hZa+MnYeU3v`#3JLf zU$UEVKhbj-It#jT0VoLv9=p)2^s6IWpc9#tPpvog*AZPU4d+Y{!jF}1b`R~NT6QL0G2YAk&Qrg7Jmxr` zmlbqZ6}`#7IG4^(^k-jvJ^R>!>MB=>8&Q)WS5@4CO{=`k|zrGBeJfRo?zn6RN9 zzJq@t-t=L}URvM|(Wl)sV5VR9mr`ifDdF0HX9*?T4EfDC78rRYbf11FLa+96=Jtu{ zsQj*vV>BdQsdEdyIg3gnlwc&e8KWVOa;e%^*yrhWs`CYO708ej5@%wy#~{%kCCs9# zYwW~)9m=A>Ir25)+K(7{W>fH6ZX-5Ve^Bt%P3+n>D^)MGS9`i~NBCVXU0jmB6Hlz- zd;hleb~$UUpR%XS8WbFBG>3t&9u}LRT@ETLI(aTrozf&ZBtlZy2&B}G@*5u7D0~rK{2Zv(Y)IRX+S%wyk3P6^5G1#9iTP6g#Y@w@fYop) z%8ObXOt>XOBz8=9*F$he)Nz6gJ{qA9?SAVfNb+AbuK+G&S>4>}a2&531W3PGh+FCuQbFU&eZJX}kXB z!H*QdNA@&d-=&s_0f2v-I{L$W2mCp~FfFP)?#?Fp+5iUG|0m@xKlu1A6f4ydBG)h^ zV1AbMW8kp9=JQFlWMky5t+|DGWP|a`Pw?GKVQlB=HtXx>B1p(gAyn~tEE_fzN!*)s zWl~mFh3WmD+!PJ9syZ=eQJfW?ywDXn3jEQBLKSVd&47YvjI~<-fY!~g_zq)gcPv^z zXR4nR+q+X5*Py%cch+9E7@6y$kTLHLpvH{hs`84kSXgUeZz9P2$!-L&F8l8CEWDp)9CuJ&-3s0 zbWQBhW0=Qfwm#>lS7b!I&c^4Md4mdsyc&zO59aAatY8XazaaH0IBZ;P-? zsU%}0GcJ=8w?`1s93DY+DPOHAWwu##a6|R)&(-h2pPrh(n^k8O-^ReNF~96wuWa_y z#C8@@%Yh#G`5LuEH9*g5DZ(Fpos8x66ebZ6Pd-Bz%+ldkQrze zzT2wegHbiPY1LtMEKW}I$<%bs+DaP1S|`<4pPXbnLah;ExkjzM(3OM2(at~toznr9 zXA|Ts{U38gvn?F%nl6P6=ydFUx?fx86n`~X&PIN9-7P(K%bw5g_hwLLjeI1fu_1?x z<5tw>gdOG^3MF+QIyTC5ZYDqx$=3f`YgjT^Vpy{#-msT^v1gkgygrvXp^79(!#8yA zCJYh2g@@-9-+4-t)mo3|d(LcsYJuNuo2X6z7eW?NJvLf}=tQuisKy zceF6l4OyYF{1(2h@whYc@O@|i?-&rLo)D51SFkhGqoY%~Vk-4>4bNIUVNi4nEUvQf zOAEXLT-)1%d4L?%P&_Bn+E^bZ75+&XSiy_Bib_HllytOQaeOnA@2rzcF@VRbAYHk| zgx|Yqscj`t1zFk+S+iYdT4n55LW7O;bzGHdr#W)m0Uy-RRYN8H6H^Zgi-0R=dvFUG z$7hP1z8dlBVD&N+C}ls#a-q7G^MH9d{$13YLM=WLfrV`~2_sl;FTOg?#(2*YrhW#< z#N{B>!)GILJ#wE9B8jy)jArxC--cA-%JvIXKbLt}naH0?OnvBqY1wg?FEa5-;3jdCvmA8=c?I~F+r&6p=&Z@! zj7jK6X6@w`({SiFFW5bL!PvAtJ>(=E&8ar#Roq=^qWOlV!^(bsNe3JtOkazs_&kBf zD_=2?H3a$bf+o@q06&dmg-(Lv*;`S?ndF|9_+F1m&N7>`5me{Fr1~_k++q~aPx!;zY_4Pfct@pI&LsN zE)Io*qXI4BD}KwFdxv|u<|xa~^{Jf{L)eR{RCXbQUz(2o$d7tO3edK=?t2h|!Iq1c zNV##J8dpU5w(VVZ!4?i&OU><+;G`TZ3QLI2kQHZ$gGLPyirM(2R7{mCbO*;=7&RLC zZFoVA3od43F1(MYx45Q7%7oyO`N&A~mwUUWG|adoAxF^wXB_Pkpk=N4$U*IQMbx$Y_R1I8~%$Wa>XehG^YkH~vN(B^EFz*8rU`ds#MiyE@}H$dLD!Zf^qJgI5Ki3;?VjfcOqbTHHqVuZfxAc*7MptAkSGk z5eIRa7h~gO{goma3ZFP|^vcVue>>yQy0{kH# zIx>3{m#Trw^N*Bpyi;iNxJ6^;HT2I@B1B9iLe%afzOR>MmiSV|)E3`g+L)O;d77-%5Ky7AuEty1eL|N8eM@H23 z=%RFM=-pr-UDm!m(rL}F2lPm3Pw(ITpeLH$5fpg6+p%eJV;=t?`rR!H=eE-R%dI}k z6R`k4dY*#q1SnvD^v6PE(>!QK@u4$>Mw0R)%rq|2s}j`OQ&v8g`V@nwxVGBD2w0ta z$A+KMF=NL8`v}Po!zP)!+-{*7*fue+H8Nc*+}XLTgx#2O9igvdG0~_6Lu55Vlselj@(W~stj@w32n;Iq6`Q$_-F%rhne+_ zlobj6^632yfDcA=vf{-$hNuCmqd#LrsL?8^q%Ogsv1$ip8_05PeJO37#P#OJKFhh8 zz#?F3)gh(4^%Uq+@=1p)qt5P>VJbemK2nY!R!;bGQuC*f<;c!&+S4h>{?C%eiYis| zv20S)?9>5IJi>ZG<~b7Wx)#nh54*qN8_win2*G!6abfb6VIYN? z-WM){eABD{Dl(k(pe^8Op=N0Pz71Kd$G>P>3bak4KDN8L(sqON$si1HON>8W4nj8t zC7N~)!sAQ-G#QIeM9O1Tbj_2>IH!5v4$X5zp@&08-gB2k)N(2>?d;37f0UJ(1S5os zq%~f`g;(d?wuB;WW4x0(jODwoN=&8$2A=1$)|9{A+ETxVH6zKDY`_kZxk)~HhuZ-{S-)STr+ zxt$a(BqF?skvFGQV-5=`DqssBe4-dBuhPz8kohqT__}SmhBP@OE>)AazRRuqO>WW) zpXd(;rPQUSKb%OS^_%p@K?`I+(hG=i^)U#lr&QB>5~qhbbp4ajHpc5NYY}2NkHa^? zq?YXx<+e%1LpC=bv+YZ`yJ@PEd2@@~pf3UJc&ny(Fj`5sjujpu{8QlR2dVHCQAeL} zDUg7~x!s1g-(fzF+|pLM3)h?_hlGa^N`}7Dy%EzLQPS=&jiuz;r}DLLCRXwu4U+4; zQ=ND&C(@_e8q`%l=_v6nl2Cj(T8nY~E-_aGpIRzpudWF0!BJKO27+v%7w?75eQM6O zNdPuyeJ4lZbPn(x59Q*oiq&^#-Z0S^Fh%HkQcL-{(a(X!zG!X@{SWNfuc>Xs z!+e!86CZP2!`bpJp65`aUPAD3?^kof$A9T(wY3P2P1E5~T77{d4V)Ww9(O!oI;x&{ zZkzOipF2)IEYoT^j-6rhLP(ESO4SYRh3qB3b3e9Pem|_Kf?1YE z0P1>^zxA4E4RF>YRq)p$NgGKML!xZ5yPXg(%`*!NGmFOg4{0Am=!LtbXz${Z%T*`z zZ1zWV!Y@IDYaZ86rdp;qr2`l9D>xXW7Z4qom(;YKRL8XZr_>w_*;%pEUAd`Nhfk*b z6qdtv$-TW|a1Fx0Cla8cnI_GHKc?)gy4mR-I#|;w-wd^=)vG>}Zof#3%cbI7(X18j zZE@UTJ}i5k|4JJPS#Y<#NeP+3#jA>3!{4S8oOe^e*4JS|HB`3XypXffa+Iyj^2U~fv5LT`4p_~I;mZ34_Nx9Wk(L&#O zT%+M-F77hC)pvY^2#P;JSfQO1)Hc0v=)Ayu8`LC^I1KTvXUVc%4dMD*ry@UlSbbvL zc5o0+Niel(-=11Nx=~?g?o|rzaG2HPzB|5Y7BT%dbWRpa)k~*$Gu+t`Dq)YX1waX7U8t6Cva%8KT^cKkl!vlfa^< zuBjrM*)u>+aE70r;@AcW{DAGzvzprnPTC2AJ!EE2eM0*AFiT$Z98715?Zp5FH zP^UvQiI~?9MX~YKp)Zsj$u*gzi{m~{PRDVp$#CyYa^H0%d4WezQ_dJer5e^E(s%td*K~HwfqzzTULi30AN?7+XNNB2oXd}clB(&4T1aYJ} z;utVx9Ge07E3N4>b7*}8L8DWm@>c(Vll3YjPDQnhX@=s@VQ6 zz$Dn_lYp`&G1)1@r}V=d&d0a@;8zCJD%IS)+b=Vh2jr47S%goqD7_RFVtd@qGS7SU z(ah$0Yy2E)wr!^MzI)R6q$2k=TDKd~fBtk_&e)4o5k066)y;)94LtzhBPTM*OB!Ql zsu`i&`0K#pIt#5Np$Jhrej$Kd4p8`=9DHxF(K|?_=={rx-c_AX2p`B?rUXaAOKI^6 z(K&v;bJ{|&|NN3X^+RK@F{jqx74QI<+dqByh1>oWe}+k7XEq0G`ZQJL;l*tm(wXRr zE__OFcNa7L@ z+9x6YSQ8?$#*nGFj+f&rhj#f?>cXg(^+Fz1^Y&Q;eguD5~UJN zpt+4Qmq&rnzUsFeXaB=4)p#>fjUf%zK-E_EqlTyl!o|!ZbIxvblvGp?6PFz;4r}nZXU% zx`&(F^RL``^6YF@!x$!F6sAB0(hMuCzxO$$(haIRZQG$V(>FH;i@3ux3D!>!@)b^> z0TAm<^&b^mI`BF=f=sP>204iv5rQE^|Jvxd0!>tblStEnwxIyWR^@`8Px5?`xMKpc z@;Ix}woVYSQQOb!u#MFk{jEP`yzM%<#$N{koDI<7Z=C~(M4h#G4ElROr%Abzf9S;z zGa7(zmEMNo6nW&hfPFgSft-}ms#>0u$6g#N{5`_{$1W6@qYL7)iKTFS;~r5Du++Ij zDuz*l2}Xn;^9|CnYUEm$Z0l!8MPx|E)|K^~380TVctt~0^QPsVee*ih&7 zr>jh7AIXjngbbqlJOxxG1yg?{ZAv22ajPjS1( zZDJnre?CopuYB-z=>%}lGwb_cbts=?RP2M*;XO|Eo?*@7S>g* zI~zg%(B^I>EXUb;3Pv6CZ#H&DdQ-UclbE1UfFE{8$>L`$ye+&mJ@Xj!

lyXD9dm0Jn|ueYQIn-*9rl@=bAD#S1vXavMLPy|X_E&&+#e=VYbX z7#9VxV7j^Aw#mJDps~gIm7IxOz6&;OM*6DD`$!M=g?{YFA(s7x^Lg$#AM}k$J#S+;l#D|4=YmD?LyCoKP&sKG^kdtx;a{D+&_(YzOZF*-}kiW#P7$MO;ema{#JOjYrbqb-;Lfh zkfB+BMQpX$Y}2QwCbJ#X2#m~T3mxN4=sxD5w5zlxyTTni(;_-l+?US;9iQB1^A0!0 zoNY9AVXf*!TXQ{@W-x{`(VmMmE)tpGv7N_n{H#m!QvBN5ww!*>75?#A^@`qVK&e!; z%5aKe#>tHGz7YogZ*e42Kb^{p{4zrWZqi^){`}TUw-?v@;kMEfKQku=^EYNH>eMW4 z-VyR4(O35tzdBu8c~#(b9SBeBfDU|`gWMUtu3a--pg?yEU?sN|{F+pmW8kY0YTphg z;M+fQF;yZ@+VbLy*RR$YXU54Nhz11R+{oMOmV|4KtT`khkwpZfaxVzYJV!pNbNO`$R~45<6G{`2y) zz_dDX`;;*6NZE5G7*WKuNZq&!Rytv28QXJ8J4WdEsSEC+?S2w3PZr4 zPmioBBAT-vy&kkjy+>s+p7Ki{pyU)PCfw-1Nlk4hmPu8FVHQ}gK11rv zBdq58+rF4S_C$@cH73?NGn?6bTy>^NaISFN+AX8;8C?V^^`*-H7a=1dL0WBmZTZEU zW``4Bjl*?xLmmLvt_(dz4XHXW%4l2Uj#HZW@dY|j_h4yCI&Sj8VvMxMrk#0Q%4wjB3@e6ze#x;9EGu>aYq)@w7eCt4XW%J<`&( zL(Q{It|gUR4lZaU=&y{}p6}3L*oM7!*O9>+rpLpM)tfW}eZwp64o-b=9XD{3DGyR- zSbU8iW%>Qh6L6OYbwFLm!#9tm*5TKpqM`(RF9eWrbKg3|7=q0;Ef)Sn^k-&2ZO=8Q z1da*RQ+=Y`&9W0!^m8SNl&$ML=|3x+oMek{`oH9TexAq_!IpB|k*bNlUu38?BQiRhq_W z?Jd1=|3IO$t0^DcsFM^{PaCY!PH^&@D>Ul|8U${knr)t>9+~eB+SzecNYz>@+p1qw zhhoiJe>$sbAx_WyH6ZNA$G_w#geCK|Ze$#3X4~7Vo&<<4CS*nV97DwY^RNq7wvD>B zF2Zq4hv~7Z-U?u;A{HgkGqvK!;atG^ERGS;!`*vUSv&`wE5Ktn2>DS|MAeNu|Wq_O;@Q2MO== z?aoQntoP%ZdIlQC76L7P-?GMS1AiIA+md$O_;*iR41U&S>#^3J+50|2S3z6uSlWb& z=c#%)*)^Y6yRRO5yW38ta6vvv*0%_FOs+uu*0lXfRvzKq3uKUPT`5?v-kp63U(s&d zF@c|T^$*`&8`<`!YwQrc0u3}ve2yBu!-fBunU4$9J;8xHxw#Hs*Q-N!h1Ilj!4 zC<#O_jH2m*8(|$15O&z_XZ7#kcJvw)Y$9t>BswVVQD4ZvrhqvC*XVuvQ#i1S9HzXm z)CV<<+1b}-f373r+`zr=5|(s+P)*GEtL2AZZG98h0CXG7)uAPZt{OwP_=Z>J>0{;HfhYw0{4-EfYUNGiXt|J#K+MCKpv!Z!FaDgoG zV1t}5{`pnNZ(6 z;DSf9%S5rBifK)gh}gvbRt0~m2N3LCmZXHTTE6@6Ojc4?RAuIB^j6nD4-ifKk=f-k zbPOWB!U*Nn$88%CQC%=J6}x^=3JTiur+fgxAOS9HF^-xqd^DB5XDZgrZ)G$q{a8!y zV*2|Izl8EPK*pM~A%v#2sfd#_t@dqU=pfugcTO!Y>q*w6$mYAx;&eX!5hC=3+B(%q z$+usr7Sc)9%I7G*$1hu>0N~6Q$MQ>fdj(jcx`qHPiKb*(^&HIb)tkHODZEEWKQ=;< zxeQA8-A!;g__j+tYEU4Hr3Df2R@bK49i!hNG$EHA&$NiE`aH>DG0f_zhKx=_xjv_( z)sXE>Emf~wj$ijbBVA*Cxm{ktcXteIo?ytrkx|mNfVVcng-5&+UB%C_kGs=4k(#F?bO6$!?2-`JfdIslVqZLr21!(rGOFRe0QRL@&amP@QfB zl%C%5{JUe{-2(sbfEFiYDHy8@jI4`rvEmtZAvm%jvt+(i38s#PVU*!2Gy(keQbb9Ln@&O(=2aoq)N1+q~I zTmHQqKD-j%Oam!sDs!SDt^{Q=W_o}2>H7JsHxW%?fQWE>I|(Smrc=Jy?TwAIov4{q$VJ0P z*0;dYIx9c=9aD=~`4#4G!Qhnre~BrIbatBOlUpu6f$x$2e*A{~yJF|3Zi*?;5N(SO zP-DhQxJi?(R}!-~&CgDRX`?-+iV11~G`9$pSH64GR5yvXE>nuYt}gb=in}p!Ih^qO zeO>TRcEbrkoB!GuXNlgGiv9>kTw_#?rqM5nuO)Mh!42?A>5pIc%@?!q; zjLw!h+e8=eADso_L3b5SQen%?@fQ;Ao2{wWiiK<1sP)BHM5c*UMdsOvmxctXukDo& z9&2Rn6{&A6QY}8CeIw-c4nqrXUqI~g>Fpl5(t8B?3%DsGAY|2SN-a_Oz;IEXO$ zh_F)%#Cbt0R{l~M-@rBP=VYBfJqNS;_#Q27Jx@%a!YB$3d@tZoV^SX8#Qp65&JFgD z!unwrz5r`$CF(hijOd0d2d0rzVGyKH2)b|9BMNmBG_@pn%&XpCTGnc;WTzpO%--z4 zldVo>mwTB^5DY65t%8g7NN<`NOzVbc+>P@LWnyC6jVAcmJ^bw2$HIEWYiE-e+gR1W zm6n^&_0_7{*{|jkU(4WvUXyWURZm?eikIG8VzU)UaKU18-!z#hJpAjI83&N(y%8}C zAp_puKJ){kLqp{`v(!Xxn~#DCG&xp^J1+}iB+ebBTygZFf3t`k6K3FJ<%e63zfJSQ zJWKDhD-Me_t=x9OJ#9ryZ9lBvG}nvdhpxY&%?I~uME)*{_t!!)Pa=!tOg-c{xA1>I z|7*d&7>0oVz5GxD_5AstsdG<$x7@+gRLylolBuu)1t{(#FJ@9Of)A6Mf#jHPF<%<+ zE~Nfx+Hn7ll?>i+*hT4hoj&$lj8aY@A8x#T2AcY$c3O9KUg zVb$Y3S^>=<7AvL!KkG}gkMKr{_v2_G&Ci%zK)rqLUb`NoF0;Y}{#c&Any+oFe^P!1 z3ARIY&7!tvIjuc12I#4O(i;Ah{gfTjq^YC;vIqQRuKC(C^$RANesV^C(e>)`B zX!IA(572g#lt%|5%hk)2op`po6d~<)Q0E}_pt9L3GCpsgAxySZ6*+lbuU0z97ZaFg zO89iy0dMemF1n2W>M=7n?!qG=x!sqJR4}&WIfER;H}oA64kS2HBoe~UiA);c%S#3He!?W zvMnngTdnWf|0i_pW7`>-^DX}SDH)nGFAVR{?;S>SYvP&3>Q_S#qm>Mn+R;>h4d8iL z>0+6sf}%I7Zb2^S08_VFSNuIqB*T|p(a~suJ1n8&6`q$UU@SqBq{2|8F+2>;#5fs6 z9hX^zvUl`4HKg05U-o!MW(!pPms}onpgNo0B0~dAy`JGpW|tZKkKGuAcJC|ja*)-^ zyOyB8x%!sS=Kh@KFhgI^4sBYdoLz=%@k(s0F z^S888O+?|KP63tYr_D3-dR9lVQM?Z$hBN(kdMP{x>>5!Sh%z5(%s*B=jzb`I*F$o# zbK0V$g;bUTyJBodFp06lceCs5FQe6w0mM1`Pb(vX#L`{1iy(stN1#}F)!dS*A}>?L z-Q4*xsO(%ELLBFF2FGnlKI?)6>&JW_nX{ZG{offD---+Pb z?HLkZi-^v_XGKIZDg_!qDBYBqnq&NU2tVpI7s;9R?no3vPaIsTD?*w#m5 zNuMy}Lsuu3NgvTlBnDe~BL$hhtGd+m?hO>Zwaq`<3+4lTOA(T=6zTW8j5{W>M8WCa zVg=m~bx=j^c6}*}Pg!W{i!pmvxuaC&|KpF>qeBJoIDs#^H+g1&Zx$ie7M_OkFrE$5 zx;8Ts@jb%;G^>>^7Ah0N0CNi+%|GZ(ER!-PAdRl!n`~?rvd7>fg_&pB9d5^dl>ySD zWyz8!a8EqFds`)r`Kj<)RMTuma+=Etn737a>+|b zFwY}d;d-ZMvybOW?A|`s4^AD5E;T16_?h7D6!vq)0IMXw-!Xb4$O{j!{A&Vk?0qy^ z;~SvAB$>nTas#mYjyw5>1)fY!E<9A#LmZO7{{(((X38MQ=99`To$oDS>*tM@TY^o$ zlh1`n6 zj@LspW4fH?1fsr5!TSVC;KQV&1&;O6j1${JpwW4~kD+Jhlcd~a<>`r|0zXAU^XVU8 zAA-5U)co`CulM7i=T;2&^$C2jSx$Nkp0#8#DUyV2Psx-sz80a@>uxvi-cKF54@LQ! z2;$O?(7bS#^c(*8erUFZ%|sSe$)n$E6CPEtyT3vtNv;>=sQSEd?`orzRO zZ=9hmH<_0khSeJOb*y_ccxPQExia*{H63G~sGba>9IK$>rzNa~CDEJuWD+{kT zo|>vNdGD%v*Cph}2^gZZx$Ki{n=Di#(l+^64!6c`@Z#Teg(L8&@jdeTu4t^@GDt3p zFJ2w&vTsmDNcm#X`v{_{*J*z~m2yYsd*cD$v zq-|P#X|{AC(Spx9`g_0gPvU1LVN89A1a!D?_2?NDcB1w;a;j8)wI5FXFveu{C*ld$ z7(5xe)CixY#w$Sy&uz!wVz4x5lN#ZsecY#~-8k}t@^V<}PNO(>s6w8T*hbv7`){Md z(>XH|!IfYyWvB`P#QuVb`m%UbL}~?GTE36}4Adq#n~xD! z^%Vhj^=Z*rp|xgAtUp6Fb_49Y+Gkzr2UV#jf!=#ZWmR`Ry}T7`q17wVzmeH**>~J! zh{f=`ICi3<1zsz*Eve9-82UP>^9o^mK4QE?<}`<#a9XY7D^xJ4`h`tDq5Z4i2>*Xd z(zFS|pscTiOw`xo*Ts4JlX)An#;feLGiCXjc+i9MA#DTF@;E z3Ky=+%qOodbg=V!noT8znIFuy@F5?&_?yFTGv4ztA|;Jm)wNF@DPJ?jHq$bm7_~}f z9Ubtc+Zu_zF1YzIZ#uKl$)^2WN3SnBJ`4YpdIKnX*Ahjne*5MB#oSu}$IW$HqH)YI zGh<9KGc#k%F|?VPncB=0Gdrf3?Uf}lkv3s8;RA;|5 z6$vAxy4>h%xcU4Z4NnFX96qUtc|$wBmYRP~c$tqiO;e<~0}TH;YdFCq>?YF7KUt!B z5AIlXc^l_@#C0}=UcaFJ>zr91^T4Az9oS@}X@E&VQe=IVmG~Uo20L?T_(WV0y8g?gRX9zHVu zQMo)zcyg)3S1DG#9D$F3O*-b`=a!Bqn>=wZRcH|j$!h8@`OUZyGv`AkVZY!AY@BhLS7t(cN1B$J1q!#C}J|@G5M~ixAqhxu10o!^9{~ zqc%YKw4_gFs>ag|5xJN3{-!%PjDe%Pr8E8MyWFk6UDF*Xlbg;*dxO(kb0#ZsoYH*` zYP|f>)?yB9yb55%*Z#y1!9RgEHS0X~x4V`_w=4G_@7eQsmXv$>S@e8L(V-v%CT&$A zZOxm`=6ibFbr%{kUu1s>A#Na_{IDhbQDNDFN-F&5vjujT-Y$7XijXlP+ZH)lkl#rx zWah9up~j;ya8BbrxQx^Gii!8TW=oi(xt`02R(qzc=yg7pC4*;f-aGEiH!=Woa`YvM z(DQ10Jghro76;6a-HRs(7smPnz)pnkkhwf&>`KTvssZ z^t#qwe@)pZ%QtIFv#}^z7H$eDtbO82L2$;%519C zQ?e;5zT_90qSO5!?Le4VpOtq>>N7BZ<+5Q_gdmZm-pF3Rc;iT3a%nA6lbU&PnM}Lr z7EmP<(V^KeQFZE_wB0lt!#hEBd5<~U_+>CmMM35|k&S`J0#&;R#Qe@6G+(RAby23ms#|f zJZQgjlNl{G+YqC0f2$4q9i=@?Od>l+jm;fj*yrRY;o(91L*k^ZyE&SLz-`~G`{TfJ zOR3VDGXfW@)%sJ*f!J7m(IoQ1H`=F8tPU=j z7woqvjC3u6KG#^ATbZ(Som?0)Os$<4!g@E_@N4;9h+vAZk%(Q8!kZ+ldwL9_{XS^H z(m5{D-XSZM)i+jiB69wZnk$oe2RgVI3NO@*WcoG+9k0Ap?FaNnt-sd8^47#FkM?byo8q}__`h8@tR4Ms zK%Wp}yOM1`A0Ie){I2KeHJeab+M+`!gK=aYso+gRu(D%VIT_!4>B!yH;5~fiUdEx#QjH$GGO}~db60)m6ltO~|Hho=ndz6*h-Jl)oFSFnM_9ubS6)Fn0jR~>!ovN!Hci?vrAL^HbXrb*Ty-)4`KaUUxL=d^7g_FyHt<}6z&VlOvr`&8eM3ak1wo!pYdW3X>z7n#7>}nbfRjg3X6_%0NwS9DaP|e1M#dvQYY*58P*BW1t4gDn)50z z&{7O&lT#sp#eHM=m{sy^hk@(K5mDY@%+v2VZK}t)SBdkCvT7iW=romjyytIZ^X&@y z)KRddD9|Cea8-D#bDNis50c|#xFw&qJ;zjhF5e)^oVdHW160oiFbg1EwRJ!+U}6;_ z8s36 z?ISn4GH$#t%a&(lat-fy5B%CZKZyFMBFtf@WLW;;ius5>CT^dyEX`mb@@*GUxS@W) zr)=z`D9Hy;aJQi?TV`eu;7~K(CaMkN{8lwB^E(SKq5RjSeeUx-T`KWoCjM6Y8@I<} z1QqprN^gNN=br8|u6&>JPq=CodwQZ{w|V>i&55Q*PG3gpcG&N)2WH!yUQxRfE*JRMv^4Q1FW^1|C**gA{7;UHQ{f`B?s`{8Sq%pbtYFouCA1 z-T!&FydqsJOE~Q3HjA2?bKN-{+Qf75HK4>V7v}BX6;Y(!Jh6S1=W-I$go%3bo6yx9 znIT(ie2zbSlJF_|d@=&d4pZ{Wd}Qi z*Ruk=jxbBV>$FZl{eu7_zy8YOx7kODdQk(6yN&`Lm~fEr!5%IVpujAVw6$Y1B{N?U1(zl8S&h2kd|Lykoqy=R&Q0MZTl3b%HtPe6tcK~N z{8>WIIHGLe_Bh;j&~toE%eQOt=K#gW@&gIJ^j|w``{(uX46eHT{Ax+Wa^k9Z!J&V( z(3L@PKI<5Zk&b$bQ54o^z4Wx#qx{hlzxS%x~r z+UPg!kR$(vDk-k1HE-IajS+tJXVj*gVXe>0^lHdF@^3{a%T{t8#4vIM?MM0g>~s9Jy-Kg>pw|HLyM|8C%3~>Z_4m-jBi0*e|0B#_jj~v(zK%hT>|*c_Ul#& zS_&J~y{Q|ZxW4gPV88%2rRE$P`Fpx?=Sgvi{=D+gOc$3>9Yhkxhle+vD>)!)DhscKgvEU|+9?R6(l=RZ6>Tb5^f zgBPUoTz#$u_SbCI4chiNvlM6Ch)AYt+=}9%P1rX&|F-?_iYjvtakDdjQ+=k7yO z`5jW|-;))?Od~3af75IBS5L9cPD*DpcUs8q4K4!a`@ z_Dk0YkCr?B^GTz6`XvmB_DN|p9M@9Zz!n7&iaBFDtzXy(fAK#`EpH9Um}+UYS@YM@ zanhUZo8~WfNtz2$kbiOFCc%$vpAiJc&zfA~`hj8QO!m^a1RSx3gHwj16~tfZ5KIP5 zh*dkb)i!c0!Lk4oC~qM+T2lD3APgBSZTGoVZJG{(%}osM{3&fU`!Ghtl*MCQ1JK6$ zYlyf8)CYXfMii4X6mDSeddt0w=UUtS`r*$8?aZc?y%+lkC+R&T%%Vel1N8K)eRfM8 zJTh5K<8cIdiwI(>$%OCzH$5qOUBNjduNXppC=tXh_igt+^=&PF^QVV!*#iBS?Gt>z zfbFgKyb~>>Q{2V-XWx@g)`cC5M)zx@n}4q7mwU5u-$}bw~wwcJCCwCl}KRx#*=6C}Z_Ac6CpWgK@7`{ zxcnza59Ht#6IC7Z z*g$C0m5)jNd*xoG&UP<$NpBq4I!1&6Ka(B+cOBl3^R`ujuZ~`N6H4i~DL3zW8?vCw z#@i3*MVD7Kw-M1B+%Qf>eR}wm$BLVG6&hQ}jTR?xIx>v&)pt4=iEqj_VR>$M%QGwM zebO0cjqSC^73cs^i7LfvoogHQW%sGWu?+F9W07aIakZFkquTs@(66Sd(feFuF4Nn% zD|{a@st@=6Iog?Ugz4~|4U zDOgMsm=ic5lmP(K@8jjq7$Vl^qqQS!9k211Fmxwr_~uIb1;W0XZB5KwENCv6X{c5^ z?>BdpaU%1xI5ig-%is3zr4gKZbNnP+?P%?@^iII>eupX$vCePh%_{3}(YTe>2LyBu z#RjgE2c1E8*5@VPZO5pOaUD;WGq{~y`ddt7MAfS3Zi1KH2U?k4eBAY_=6r1m+ppLi zlqzbuTw)rdT4$qUlAtli0){A`?|M}Y_u|j$)pmw+d(9vV*hqFeDZ{S2&MYJ*5+CCz z=P^}Xy>4c7mE(Krnlh2TO}km#v}6~Ka=-o_xQs#KP{kT|!1{ZH$Ik>;qB$zA(z+Ov z>*uQn4GV+p*)wNArQzQkJ3oiu_kfV+`|p$ui08V@7VAJ>4)1ftcr6x<)9ui5-rIDD zfBXAJx0^lP4cytjxDCq6jh)3wVrXf1pJ%(tcRfb%!*%T+F~Qe->W?~u4EtxdAHM(D z^#6w+Qq{H~D0vpe!T+)nFDv7JuEeXLZ)(h-pzmO8<3!B-k6DJ2v7@cCgQ2nGyFZD# zyn)5Lv&s86Ib$b%HsZflol@5CfBL`bIm#f;Ld^8f&fZN<6yAffvau6@K|xfULB-h3 z>8}ro@ZS%SzaLWCfBaDXmxds$O#k%p|BN9B+kbBe@=weEDsuckF$7^}XaA3thuZ)$ zG4E!h?MRHE93;=!kFup3V>;O2ZaO;9$BSW96VdYQ+R$lV4Gw)jlZ`_8sTEwjf)3dh|==BRuTLk5d zdutZ`Uf%r#(SqBNO_e(3$cT)Cv!k4N4SWk*f#mmw zjBlUIJCsChUvo^VDN|{urBa|UgXJ0_zQ}K$Uvnb?;4INlWB`+bVX=zj0%7C|4nSzs})Bvtx1nfTPUijMw+PnK}++f$0T15b+l z;3WJumk%(I4ud&-pG*Ai5PyeJ1>g3F+ynp4)$;hD{Cm%SV6P0kGHhJLad{5<>i@($p*Nfv!e9LmqsUid zA@;mZ0jUjRXXt!^f*16(Q30 zHN)kjeky#%mC4{oZs}geha|J2SmgeOU*X6kYkc8$@-%{aWQm&3l27^<9kEk=N?&B` z7AIokJX?(j5x@EtFQRH;ju0oLcB%0`F~Y-yr86;zys_xn`OmUDl$+R_apnIQ+)l5Z z=H3V@{SsO&OiDsevXP&;?OtG zdY7|reOH8;h=H*1dMMndLPMgWXwO~&zq<0H!h+3V&W#R?MRC)7pBZwxMvCWPFbRI(%CnlA)zt56xwEGgPcqa|ttN0KFg z78Gwv+Bg{0MO*)8cq;&MEcKAfuPI?&M$@SRhQX1ju67p0KAxl@Z-Ahtm^(us5dDel zHn|P;a5ocEs;a!I2{<#yDML ztax{ZRFyp!nJ>HSLWz61|IlU+Xf{)rbD}J*w32fMr(S~{j6@XP>50^q>ua}}*@`On zy9^u`LANi-l+6nV&<~?Kki^6fFZ``CSMz&}NX7kz;FWGa*ogGmuf5Vq2D?*gYe>n- z?Ku_0n=&18{t~CPk_F8rhudK}{r!IDSOQlYz;Y8H7T{__Qe-J7;GpkoHL?E0>~Ei5 z-SFgeZ?;LhW(`K4SefGvqknm*gBPRmXzr$~ci8*^7^KpO@B3ts{&f-h7AAT(?iOab z(W%TizsC8CJEy6BS%#ZhB6pW}|04avmUUu)8T97NqQ%ogoAb2iy?!)#%AG#-<6NH; zj=9%?<8lf;NpiR*PGUogaG{!}Sl#h*=oeRyXA6D0ZqO@_GTsGB6t+3pZp%pYL|#v2 ziyv>B-$QYaGTz%z_xr{#J<7-V_ZF@M1ZmSQYUE9KlEk0rmNLW&C5eaWmfBs)G1k{x zHi8}${5k5wamA*;DN;{QQ!hu(EJ9i&4RadTEKGun!1m!`SYj@f#ck7tTMx0`bk^(1 zEjLIeBFF?D`$<7U7V*`3CE@~Hf=QNO_NW+iNj`1bN-E`Te)wnt&~BZV`PLdi?bt1H0vGfM6k)uqY)Kw6R3Tb4jtSEoDw94exxDE z02a0v^~x~!-_L)0L*M{;eq`=zcs5D$Z9ftkuKEL)scR2j0h?I z<>`*o^~KZCJx4dc@Zoe9GQ11>Aq4gTG`?2%A<&T{|H_;XI2hfUKGRctC1eQoWvfo z5~yZ2uSVgAL3O83L~%igL<$jUK1(+ymP*a`4YK5#E+b=%a?g4{YoOmt=D9%hvUl7X z5>VwjhS6>DrAA&Pg?BVhm`I496x2I^W|K^mBk4W=P=f`6)O51^*cw7e`Z_)p1dO^7 zJ@;`GNwpccz;F-kKvp*5_w|qa-al}Gm7Vx~{lDZI$RN&2 z%=`!1DE;rj1tA+7+xH@j#H_4;{`+px_`k*vEdO-r{|tWk2M8G382xkpS^u>BuPgCC z!4J%=oc{?wXaa0k#4&w1et;N}o{2Fp%+4~t_64Pf4w}PuGNXW&z z**NDCiIxq^FB)+}6a79Y;?1ASybKq4O4 zJ}ypF)AC44vp3Rb_Ht9)DI!3h+(!$1P1Dk9C(m;=6gm4AP}2crx*y5>@|hK_7l^*Wm;SBSIPc0>zo4r3`MTQ zoa%o~bog41>Tw=ZFoc#`%$y0q_KS11*D_WZZ6K1#7>zP@uO2dqCCEhddtjatTEPVi zMp{~RkS8c*ZH$=HQVt)-Tl9>-)f*_K9(dt8S~|@sGUozY`W(m0Y<$cvE2D~KGW%<1 z)Za;45KBIGeh*2Zguh21Zns9vg=OrQm`86d$4E_RS!2SHlYDAJ>*YruCOhZ%to$~0 zBL_}HHeZ}e(ox#shUiN%OIG2^c?sZkPC~;$_nuDY5py3kQVc#2Uqyr5ZdLG7?`hGu zNS7w-)q!YH$^1PpZ_TOkEk|jQF!fus8$OOKVau*sWJ3N)_x&Sbl;0Ht^H?~;b>Eob zo$>rd_6)XNYlNQ*RtcV%4NmQj9)oAe;9Vf~_S3_8=*R6${(>)LDm2A?u?qDsFfaGn z48~LwYQk0uW{1DJyxT>NGWb%+F1DO^C#-@^PFqoVb`JPE>LD{W$R##AuN#L%qw*mR zF`c2MdW@X{r_rl9k{*%YJe!)sV8#(XX@h64icrFf$lbw~KqImNpEbWXlB819Tip?& zDJR9kQxfWGZ@x4<_JBVo*ztM!e?IQM_HHHptRkIgsoyMbc<6_#Y>kTs#r88ZzuKlk zEwbRwxpvPlAX|jos)UgJAt0EY9zs^CpRJL2 zQNN^`=9xct(%*78%}x=6rjsQ#I%EnLLP1B9NqDDy$UZ~c$SA{(tF@Q&RUW4ud%h6` z?vzOU6rFTg@P~uI$AClIWH)vRZU^rmiT?MZbe~vv?j{vJ2U_A2tywcNKJH=En6D*-KAsF289V3-F$7#EzAGN7pvKTfI8%JMo z-tHHmLbPs;)Cdh1ax?NYtq=yJxE6=WG224ee@2V+6@V472 z$q<%S@{!nck7%oB^E={z5!(m|N=%SBa$yQ5z;M$?n$t;ysr3+)LKI|eQfTh zKVJUx8N2(ky3LvR*@_MIhm(mBj4G!ui4~>*Z&HCs#WB~@((7G|=20HkxqnLt*hE2X=ZBDZxyeCQr9j8Q`8gok*8ptS0KF~(NzQOVFZA0umUU>Z!hl{G}pM-^{VTGTG|Xb zaM*C^#ynRAti$nPM9z46RNAKx7@&HpYo33XOQ*EP3I zqSymugpo2!wO?gexbY!SsEGZ!$ehgMKQ71@ClF#QR#&r-f%P>I)yf97sm2KggI6goT$^_NLs z_Sz1`WLb{OeAt@G*ge3Y)^zUOxr@__^3L~8w%=^xBR31Hch0R{9q&ni*|6G*R@TB~ zRo?7RBO#l>G*);b7ZTdRJ|@P*4eYeqJBPe!Z3d3vr`%~*gnF;AxIzk9AV%EBkT1Id zsquLq>24asg*kLZn4R-MV45vd?~l;;MCe~G$^V`c{mIMzl@t9Je%s8fTz~ww+1R+~ zIf>aAndsTye{itUGjqM?a{pU)+wX4MuI85J^bWR;^tKMB3`+9K3@Xk}whrd{R*np+ zHb%w{j!ybEM&>rAUu}(y9sfu7?SK5_{{6uJWWN78@c*Ub9TOun>%T1Tm>JpruUOu( za&j><{l~=d#M|9TMYZ`H$c9v`5?36yH>*{sBw`_1qSR9*CM5WglaY#!l$8^i z5iAM}!VjC7c4cLu&I+*5)?mx9VVO*Sestd|?YUyf{}=GllOb_%7Ec6awIr;)Zht-9SbFC1Y=}r(MnV36Sg00{Zu}C51>>E^$y4z{ z$Fc&MGZ>fmv`_#%WFLo3Ej~@nrfZu1Y3uBsSFms2x!^?Wg6KRn&cUXeu@g7ryW z>sHuG{FKUcgN(G18{quSs(&8tQs2B&CFpSRC!+_Srt7Je>kD1z4xAM{lUf(@KXayc1!Y2o7dHva^bqSxnqJyuew`L zmU4FHV#I<%2Wms`X#R&(RR*1xU<^=0F{?K8W0N~R5gLW-{8MG!FP5me)CA-X>D#0V z4(YM4rBidc26d0hYJC>znVw-= z&MobvhR%^^Y&?XKwGH%(vJK7Znwbq+1@{~!E0|@I#_dwnWE5d)0xUvymsrUi(lt~V zL~2v#mhiW#yAnr09qy$aA>KRRpU!|=BPZEkBP(^L(iiV`EOkvL1b_|gT)NA4*PyqT zblr2!y46e_@y@xm0xiK>hht^a_4D(yi^#XP8^`0LwAE7)+icHcJI~|VVed;|TW2(A zfBtft&qJ+d{a95Db_RYy&-tX8xT-NN3UQfU`CEn4E73Q7RehG??a#bkm89s?9&1s4 zyLI0g`&xQ2CoD*OSPQ{QkGYxnNpCH`bi;qyhDrj@ncu0`w93e44)=Wk;fv(N5bl!O zW-LEJJmstkURZc(p|YwEncuU6F~#@ym_Vte!h!_ouN!c+Z11D(kSjdHr8Ucnom8t$EuDt8{Z0uLlv?$& zr#NbD5{qmhtqwr;ZHBLytpCB!FUov0`h&GuD2aP~9{lHU;9ej1DoYC&7hS{k#v16W9|WvErSnou#O+>f)s8^8&V@KAk#Y<$M#lF$eAEvx;xU3_}}Zf)<1JrQuf?j zK&mG6$W>%GL5`j}7}_BT)F|&JV8U*^zf<%XLP5gi`cl!{M}UBNO2mtB7}V5A1+T}n ziQ&Nbskh3J+e`9rz$rxnW4&h}W`44wx0>J!a-LwgUON~QIZWa{Xm;&lH1$Ny7QboE zt6Z*{acA*uyXgc=zOuJSsp0o#I+6EfvhCn3h+)Ii9en_Qc7NQm{`FwzF#_nd|Jc68 zG2RXmmB6#!P22;?(!s?@3rBoH;<*S>Msd2&t{5~?xRt#?t?Et#ANeH15ebw0x!;6# zKZ9H8L39}+Ar`UTG88v(iv40CG#cr<#=Umdp^WS=()WrN(a8s20zVhfy96e;Ynw70 ztF;JYarMgB(kfF-ZFiBrRjLjQKxnUbf3BHDUmTIykWww0l!&=tUcn`pfmJ1B83Rof zdr~~Cjc*^2h1Mp`L48LykTwGdGCHT$3mbeKiyUkXr)ZHEztGth7*Bx^$R{H@Anq1v z9u@B7no?($9GubQCZ7zMHKUT%pn0`y^4sK&v@ z1o0Z}Gl7*0{n3}x4T?Zv3XjkUmTS-_ZS;?!*e5hb=IEh@@-tg(b@|}5SzVHT*pNyP zx4<7?{E9L_5orNaJ(T>ZUOmZP2oE=(i>HmUk~-s@WB7pV>E5o3KGurKuPr*9&l?+S z&pRF7Zd=z#KyJId4yXGVhTd9?g-k*ajs(byWUQl&+!oyNT!>E zpkmdT#^e_aF|(j}%RSmhAkSfKH%tm>CG9vAmWgFt#&%xznnMzW?&Os1Fz%X)w*7tL zvahCmN*0W1S7>fqP(iR%!WFP&WQN84LVL%BQVN&SZ4LXPD-ayiz;-0;Dyb~-m}>8H z6dOA#Jb>NLFZw==x2(bX0$gxZwq_y1ha8UAqklmNXdM^(eR?;2#R+QQk(3`aoo9N? zDOXM9r|(O^Jom_@?l!_)U{?HDNeop~p5a0H0MRT$&h2+sN(jO}buEkD^esMVF)exgZLgSMWxerXesh}ql~MrY4)RAWncxCt&n^-(IAY!Y`pn^k zh!P_+_Z!63N1uWNQe*3fBQ$Fm8fw5O!?*$>+DLv25|G&k z@hRzjpt%8vJKtlroyEsnX_kRO$l799bW&a;L<4jkt1fo9ix8Q3v`!z%aC@!bFKJfJ z;`blpwXK}JWfGRsUGek)Oe^fIAUjH` zLbIxBoP)^kj@388+ol3RKvyL`Td7Z{_u$s z*UnEt0fsuiN6wFX*v8-big_PLU#Sb>7k7sw2u-FXkFOQN`})Ligeaj__qnhk&lUxt zH@TI3Cvs?$)y-#l051XyxgJZsL$=jKk(K#t7;H8}mq4|%k)J%ePdh#P+wYQb$Lmfp zMvB8BOULV)fF4`~usai^jF`G4dkLCWGd~y)A`Zc8&!ndlviJo5{R*{Uhjp-RHcnBOdd!V}IGbUyszKw4pLkf^kMxlYRRY=xw zFp|k#7+=5sP!?y9{lU0JqHTh%Q+$d`?;#a6gcO!aK=j>EIUAsDZvVE!CkcSOUB(FR zWC!CXMfl49vGb{~7sQ3&7>g*=+>|0qbn3$L<eGdCW0axq^YY{UEH(a)el zHu708y+eq}|1e0Vbhsp{<7EIOE!0dDk<1kFo#nF9xkv)Ci(;o1#qyEY9!Z!K8&?U1 zJwXYSgQ7^6?o@r-+qq?mq)_?7HsXGq>@1+d2*olQTBc9=Fw>Zp%9wsKZ%FfTI`OTJ zdgqR|ZzSoj+jA0Y)H_GgntDL0NGSD@uecOnUQ-irbUKq59{%DrrEW~jvGSG$2`i6c= zKP;`f!ICcPR@h4g716Q^v2H`>g|Gp#CEkcQQ;2R)?b%u-$e`xl1x#Zr%3^f9Sva%@ z8O4M$dvqqklF`=Ct_|C?2L!vXn@=|5_Jq-ed`mASjT4PGM7n)x8k^koF2>w(3Dr2V`AHv*O1AA6Bx~aZB1e-V25WNp&WD#tl%yn7hf8D8}IsbO(b>8M8^e&O4i8Hw1iKyO93zm?dr@0(QOI~*s4JO&cBc5?QQ4~tIG z3BM@N-F~Z{OC_a><($=GH71G=MvF=*tcv&A)ku&R2$sEJ%nnhcrKkUN%&ZRIIN`|j zX|`(XC7WHG>6@e_x+F?IUFa9T%}&tDJpbc>)g8nB`tuSTw99H4&GwPf$yr|I>s<^m zZym1jVd@y)$_z3~WuDmjP%Xw=@c1{0v6=MF>+ z=JtNbp|x2A(fBYU=J<0Ha|<%vv}GDNdaAhqakO=@pMhHgL2EdMQe(mWg8?6poV#;L zQ{O9Fo^!=ne$QT~L}F)Bg>g;s(6*~d8P@sSi&nJ*%D>Z``<|KWwY(x^&T3BVVSWE@ z)+O7{m%x)_AF8z>99Msp;Teg1#^yq$R~vZDYu6(`QMPWNg>*Z;;e`S zN~kDyqHUzkv<1`wGqa_$5_i`Zd_f}XzOM#5phP4`1pq3XOMDq6kQk4lCi|eW{oqFP z$Ddf*g;bm}Q>76~gys6x^EgmMJ=l`aNV*7IkOo46eTUroi_POQ#Iq`sS#uCO}gDf~(*I@FUeFOp5&!-&H!Hw220@z3J|xwFr=5rHGm z7eD#@K{CY8>`wqytyEVv?LA*? z6C9>Z%I9r58-!FTFy>O7RyVebcQ8Ys{OHYXiqESVZ{x;`u>92s zehG39ee40jf%rXGwS6ehtpwW+qohcx)xbc>X)o!$#j9(MdEHs%&h0W!4+56NS(eOK zSt4E**)sSt$4jecuEze-z%(CLoFKO)$RF1z1}ybaF#@xj8ALn**I3tDipecR;}j-; z2(Ai~+qm;8Y9$pNX#?xL8cwWxB{QF_YtYErQi)B1yA1;b1O#XAw~(bxn;gIR$7w-` zKx(i@l8?~D4MJ%W+Tw!$-O?DYUpfQ*jT&4|O|%~o`Y+R$a(=E#qQxGY;%!;5R(dm- zhs^og0%-*1ns87IWe=yK{{M z)(T(ItKB8v38Pp1q3$?-3w6OH!>iO=T(0@6_7zCEW^6HL$Ak-i$UD+^XO|0G-Lp_Q zpBu+F`H){E15UpWTAW)z;(NVos)VU{TaecUx^&hx&Qj3{ov-C>fDUc>U$ZMGI(`-V zy-c>{pYO(>XK~-2$)xAae(vwayUTbw>DA$Ny|dlj2gUA#h(?KEBVu6VdB6%MXF88$ zr3bGSJ!KZ~3QP*;4lJgE@70DuU>G{Mi?V6sExohF^)D&{&c|IlLET~92MPz>(C&vk zwJuC$p?UCDi)p>+i*h>F$QtTFv6i~RI&s)#{do`{Db-u?ARaBf;E1N5NZc97^zXe9 z=#R$j4VLBPVRB_s#(lTnlksbi%m$WBMH;WT=g*#2&37V-K!xWo5v7H3lnL0g<9fft zQFE@YK?Uv4x)03Hg=oJ^)ov*2`5u>(m#!x^%5BG!!?7wG&l4Si{IAOgZ+s)33wqxX zuYxP<0omNwK0wzH)0_LkY=GLrjt!B=^C}=@1yHD}bNJeEN!V7Zaek{LVTSl0nK+gF z>06+BkZRCaet68qMO(Onpl{l2)Y=a!(k5I3-OYwbus5p>Yafy;45WYDqw?(z;(GZ&0>#~@>0k!sQ*_zWn zDPV97jRw1`q6aZr7CESA$tjaIG;z=Jgp6)gLo3aa3%wI6Hf{H(R(F}@Ijghgwud~4 z@3q_Uyk@iEUM@}f`9bGMbQ8t?% z)i&EhzqHNm+@7)>iVeAH!Xs4Emqx~>779J%R+yOP=6m01nR}(yav?mP;p3($y@iq% z3AZpO6aY9@7cqxEjnR;P$w&*j-+cF-^qT{>)V87ni2T*=aM$S{7VhNc8CY35r2RXc zi#RGUN{pjOGxO}}prV_=U7~ktwM#e1vm&BvPZ}%z>l&N-Bri!$rP+o;0n$3DJp3mX z!^g3_l$B9plG%h`^~4{XakO%*g}l8a1+`(QqHJOdqOX~sjk_@60xl-$Yf#qbDd{0Am8u(Pu>Em|}+T|W4 z;jaC1>lcM8yg!@T=06+VecOvHMj}vFLNpEUh^UWK;U4;h1o1;La+%J1KtOA6r2b?g zJ*mYdp90RY)n%~gT|FAexXQd#(buBki!|4?t8m*b2aBtFQKi*%0dJTZfEZRkFBu<$ zaYo!;IlrMq22})u_)9_+1LGQKx-tp9R0*t~{tgmtx_LIO-p^9sBj-6j_S`*Ray<9G z*y^jS26kM$mCE0WQ^jr6?}f1t0bQN1nu)UbUQb@{+uoLj3`Y(nX|diI>X#9oIx+Q4 zbh}=OUvv%Z(;_PtQYwrO0x+7@h6%ID39}|Me~%&;1;IZ#Dmbr8uvJSP5eONQE{~z3XjH6Py}6;4$502 zE9#8zbiy85IAhqWawGEppE#{)_x}v1 zb*m^uOCs>9jiBtbE4Wm=V^=Zs_?D!lp&pXsq^?+e%^pOFz(nTaA%xK_>EhJUjE1+f za8Q0&@s>~VFQ7K|TlwXSTwRrq+xG&F9K2u2AE$A}ZP;`z<7kn%YTI1sA0b-Flr7;n zDBn7i4vc5Hqt7zjme1d|tQuDr0OM^TcL|r@tp&JU!5ajtuC2Cr5!I&x<9)3rTk6G{HY;QR*my|R9^PAL4$85NuYeLtRVcpUQq@zN!!P#QG>TLrTfcVzRhN&+?Fjom(<$PUQ6bdz8G*TDybN|mL zNaVq-s~GzD&HlNVS0}=K2GZrTZUu#CdjH)4*Uk_1@F?ga1}F7NZlmEF5mU(0`aC|V zRPSMEOS9|YN6C}ba=Xl}r!5V8UG^6X1&G8x$-~|h5B!P2c*}zSU@$6+%%cPi|U zI~Jzk)FL(kFz;jVrZk^Y*u|;a`jNM{^JyG#vmQ7q0NQVl08fCtg!F;tJrteCVDkGGCGghY|nk>}UI0o0I&3=Wh~b7WaNPkHa$G zl`1y`>*nUWL%%OP)++LbxyE*UqN$}OW40NCin5j@6;RNq<+j-OeuJo{K()YC9NJ~3WBf-(eL=PTAvM}CZr;Ww{#mdINFM<`w^5~PYDJF z%VmD+T*S+z*kUnW%!G{%+(~cp*QHbcN2O#U6n<{KgGmJn`g`1Z;a-@FBtBhO@vbh(6n9Ls` zpX}8Ht@Tt06UKdi9ii84HqQo`8zetlov%=}6AYYIeBmJDZJ+pPRo2M@dpY3Y#ZAKe z=;YU@sPLAQT=1cr)HD!Mrcs4xiVVjLv8||Yz?oL-f;bxjE_zOz(}r8?S|(#2BePA9 z{Eu|g+ww?&;{OJ}F8{!<5YlJ-LizlRUtejH=P6Yo_%+^d6BlPoqfWC0;2iv{Kx9mn zknQ02+R;g9O7obCsMS=*v^#5O2FsuRn7kACoW-~?ynD9A;_8s7b?YA1Lmt7HaN(W8 zR398nPy%njeGBamM`C~+4v#_mM{5ZBKqgIyN**MbM8+VRDWOw}x=2hJ{~y7xJ|N{8 zzvfYNmoQ%KGk*g{AtRuyOY$OW;iS_xyDAQ=Hc7K+=kF4oo-8={3YINkG9b84rC}6C zP4qK;VigC1a^FBl0(^#)Q5*0INy9+|dGu0E zTx@he@Wi@uj~-LuQOA(#noX6p0$oGP+|YMm)}pDm@8z|J<{L6Yf_Hv|Xu^K*zvu`F zwPDpbxqRi`w=nv41_V2#D7cVeZk02XVjTtl9*T_WtNb}yTK0?6`vO0=)X(i0K}0VV ziXaA-g5ZVTlfee?BZx~Pm~2@~#-!4zDdo9OXn0Xpai_gWgPhI2QP)!+X#5dP*b-9E zwqJN>#=L15{t&=m6Xxgeo>^@K{kc705v-TF6~@+gEm1KqBmq-NjLjiwLzgE&6LM zRyQEPb?s)QeId*Hf%UNt@Gx3ui@#rT2G%B{+UnRT&G%QXcJ7lnf@==FFZvk>EK>R( z1Q_1VHpzT+P9?98afw9lIcl&Isz8Q6H=UjMc#4LXSV-kA-m~$U;kP*1dIi?ur^n;& zU385K9!c1V_frO5HLD+0DB0a@v7<3p3VV)9XaZaa*N1rS@9ZQ=%Q~7PI<5_VOl$JW z-iS#tM-=G3MN+WM%#B~KkD(>T_l9*%ac6A+!D)WQ+n@L=bqW5Bx>DKxOkHyS33V~P z+)&(L;&qK;UZZ%(OP%mQjySmEWbl;t4348zyvL&*iCYp!RTgV6z5KPu_b%n}H?OZS zX#jnu&;m5flJS?o3V4isojGV6EtlX_EgOXIgIKsPy!Kv8OT#xzr(l@cQ?*9dhE3CO zJk%kc1xasZ}`p zLc3?W*!j%r!OAIp;SZT zQrmXByqCAH2h-X{x$cL^CaPXDGOA-{-pey0Y5~knu75FXZWuaW{#%c$k=dkNKg<8 zXW_%cjL5B0ns+72#Jt=xc;|jA6M5WC9r~qqE9ZY8N)I6eC~9VlB9Ab(m5z|5awK8K zEt}e|c*~aHBc@!nqKSiQA*GMh=LOs;H7m)pd%#WeqgK)X>SJV*q#so!& zap9!~-r3ph{;HGX3^1*2Ckn_3~d1CoE}QDKf$#ce2oDh10cK z2OyKZ5HXM9Oh&`k>oA0@{(iIe&3+>he2+ff%@?JRfr6D5f=uOC_v*aR!r3lLV{t9icG2bqBj0hnoMQt5Kq{&-vt7_3HZL#x5YWL zXRciJ*3P_N@o1w!AGWSN~J(Pe@r-aH_bD_2OGlb}>!#yU5s zZjNcBcJ5u9E!VJ=!uhfsg=7AV#f2{pla3?h>eA;Yzzl@?INf#eS5t)UEb$+_7jU}1 z7LjlBJfZO`2CgS(Gw{C9(Z8wqXMGB7d^hs%=u^g8*gxo#lFEOfPocek)u$St&`z0x zu|SlI31_oo1NFD!@8>GmLa8cJc{4eyDiDZfTXV_zh>&$+9~n^R+BcR7`*b%o@1kc= z`itxSh=nO|B%uE#7S`ziiy;n{PXlxxsxhde_s!P<$bjhvTPydP_%L;BM?Pzta z;g4|ENmSH|`ws#ookg7{irwNacRFH}xz~-jFoTvP`eDwYbm`N=ATv1_)mcXa57C3! zoif6g*mf||wW^l(nsl`mKLCG71kAtjq4|y)E|Xy3&CBwhUNWJJkMF{7*)RM`U6Q*! zhkx7yb}4NT=c5)>*&=1Z-BD4%=q$+^_|sz}e_{gaG*3NwQaw#ADLEV$G#})Q+rZth z1lPOJ-k_AIQQ%QT8Y>YzQ2CUA*rv7XM7#Gm9XNZ+)N_-;35@vnUufkZ+zYe~rbOMm z6dRHW5l-yR3}~dweIfc3xy!rKs1&anqw}?zGbW= z{6ctodhSqZn~P=A!>lr|o+!k)CQE2la<9~;znIgrP6gzgdj}}jKuM}TU`N8~F zL{-l+0H0dnJ`g@$kE(KCWKbq~0WvpB=!4C~nE^d4} zHv&mT7>U$m73&2*btoz8yL@CD4AVK~WJ5L>sSKsv>z^b~9v;fsGNaQg?`k>eOmDD#% z#pR1QhU#5!hrN~G*Mm=%H=d`?^GEyMR}92n+s!76QF7^RcfkDP#TsXky$((VCYKdx&-+Jw9bRD?t;Yo8ZnhD{fjd5{tNi-FG0Y3ciLrQSGoic&(e?0(X) zn-0EiDHy%qiYT~E<8@yz6>nPL;wfhZn;xj&{c1L)dOUzz2R;kh01e-ddV=l(D6#Z) z!(5%z-%Sf1S8WB5#x6T>(s|Gt!%FqNdNe44Gq1-3Sx)^H-z$9NmzV5EkwB;T@_n;= z@SF2y3 z%Bq7FrCmuXCBv!;fzx<+_VU;}Kp}wx(9Z0%M#e>~zHERlJ%~Sv10t+(2JS> zs!hoJ9+aF9l7mb!EZeaM0q!+&--sg6v%R4242XSzMcngrP0W7ujjMW2S7j;jEOcXD zUQyvb#m-*nFt{}4!Y(QldCKbDm66OJysCfXu&&RH1D!d|rTfHY5~@YqQ^_5rgv?4- zB9zspVn6e!4NN?$GYJ|RPxBDvkF6?YqVF|JZ2)wMe&q`^%R682*x9b|Fk?0+<`TKR zoAPqDy0DPNhXXo{pfZsM-Pn!TtJhki-3q>{bp|k)AY-lKqyFr( z^gF_PE$xdC+Aa(QnEKRQ{|cB0Z;6>h$7W7Sm!v(-kC7CAtCG2zC8R9Md+RbA0MBV$cSp) z&=)F|xgPSGPgQaW^5tFfA@AQMB$$DQ*MzKAvJVcycC^DuBHT_&vM}|?k=(g@-gln6 zJSH#L-UQ|YO_L|AMVG$;H>9|%$rM>@Za)`@)`qw7JB+@k314iZ{H63!yvxRxG9+hH z8KbJpVxFi#6LlwX@WOi}F8~KB8c4ddg8a-oo`2@MWdVpNyj)wZg?;-kPyuY@ERaC4 z_whjJS74}_ua(-Jrs(sze-yK~JNO3mYo}Fj9SAfylo9-O^`3i3GeZCXD=C~_lbrtW z27UB<*mefrX=krV>qCMY>!5vWBp^u>6u-RE)w*K870Bp}64Ixwr4Ntr^){2TrMe;% zem1l~wjCQ`1i8b8C@GHRWoyIEOR>#U%osQ}8~V;oQspZhv&wP(N}k_aFh{efW8{QY z9G39-=K?sqL$3R(7(mw^W2N6Aeq#S<^a^tG$uEY|yd|}YKn|dM`Zj9(#X& zNRz+B>+`d*N*^DY_e<-!!5JD3kR#Irf@K;?&Tu?eB^5Ss1DE}dqus8ebEpjtc56p4 z5(k^%-iND{i&!76P!Wk^`u3|q%{334dkZao$nEkor_UiVd0gJc2t2CLaH6bk&i3JZX_{@bfom2UX~Igs7MeCv#f-TvLg%lC+#il{C^5+IcE z?;5Fd+?-caG~WG2^euOpO=&g7wU%(+N&Q+Nfm-=Ig(p-ni6GQ%I2%=mX%POLO`@TnhaBZp{zmhfB zBQU3Vu-B_po+pF;GER4f)GS%o#!;=+)$Vg+ZHFz-~UMIS9IdE znXc*|oN1Ub;O-I^HyfOLYov#x&-n0D44=-jv;UgVRv#fqdCJOT-}p$lsaw~k*+-eUB+0DD zgfz006K4t>t(T*aOe<=gRwVG1fouw@9WU|xZw>rBWgyB^9q5ZO&Pcf6xbF`URcT?c z=E5Jm@S%O>i~aHpxN$V)?>B0FxN>guxH&sXJr^!3yI8fmGd=Z^^L}^MZJWXK-jU_h zfR1k~YQ+0&bMZ@~jE{)-WeWDH+vfCY+r`*IdS{X-7?e>4IL1d9;U1+KCJwZH_VzVDGKdvzsaW`uC(n=EcrBh;fVcnE@>lc=Z*@vvrdm>rm z9>o;T;`mVUJ>S^JXc(X<*wnn^MZwy)I)Wr^_ z-w6kcZoOb{BmSaeeDA%z_<=QXNopzc+wR0<=tOYgJWDT*;1lR-)bsm^g~#fcTdd<=}peb%%?y zRu~S^z_P1F<#Uu?H#mZ8F0KMd`*urU^yU0UNSJ)D<6i=Vgkr;LLtBc()j|i_>VhEe z5#M#-$eVySnYy%}2Yt+ji-$_oLLx5Z`AN z?N+XJjR3O&yng)}@kR2mIa8x1*V;yrM}!X}ouE?)A=LB#{Ns*6EXE_ppc9qvC;6^# zD@mY|yU{0K8tyew2T%*yoR=zHtr`hp1rokPT&5!-9Kz0Q;Z;%(IS1sTxS7cJqae40 zz$$$rOj?PkHlYlk5N27z8&NxhpUy$Ql-k!Qo0jOYR7-8fZ)v)*e-uJR>ScDU%A7Dz zVdCq^NNO0~&4z@@3L7=k`}0Ii{HMYh;af2k-qK?WUjUrHR^Q!&Ei=IW8wcKxciR+j zJV}+98#Z1$iureKYPVvTvM1Y*I-IvVhvjYepjyVuu*$6km~!AckBB+-N9_ejzyS=9 zG*}I7C9_t-MF*zCu&W8nnTBgCxvUgc)LZ3`sCu4#$^#LMtzWg<%v`TTGEup9_rJ(L z=r9smfA^THwAn$DJyq%k2WyE&2}4jz&f$DoAdMT4sNAv5oj99IUIA5f$YfdJ#vy5M z>@iPZzd)qzma1|4h~>zPIrp>I#zGiPM`t7gaSMC#l*b7*-iPNXt2le77eBmiTMA}o*&290>O5Y}_Hr+TahLxB+p^m1 zSuwe^$RG^Iv5DffQT3Ik(UDW^#=nQ4&AmWLS5c>pO-N)|ACaSc)XJgFQEMwZhLH=l z$#qF$EJYPgg`-YHOb$XDzoro85-AezyhiD~b@f7V3;=fye1=t;;A@MA8ADe27^W7t zdEqnuQNYPI3(-otI5j{9Vz}-=BQzk0*yjh4dds zEK`PX7u~C(JYeTs~=6MMhzp*r!eAE-Ps?Fj^1iqs+9MoOK5q5LxpKt*jptuWp)aS z^RHsfK;FhOh#>Y7jV<*{!YP5L9h5%KV}7*x)RPiBvqfcY;4NEM`k`$&@r1AB+&=Gh z1;d26T*$`kdptL5ppY_gz|eAy{GGDN#z*4~QkLzOgA?`}H!`&%d3^WiR&PnZPwGYG z3b=zjQFLX1yQ6IFH`|8_Vhq-8l}|MdOV{fyR^9EJ>YS;t5v31lDEwsFNA}WjIX=JY2hCfDOBPt?TsC`IM z2vP@ZKGy+`YKb{~OS#ZhN$Z`6V?<=@8e$tnwHO!LHDFa$Bnk8nYp%oA%0qzmF^R2d zD|nmQ^n~I&CUBv@Gbyg-hUisF*8C6%3^zYgR z?RCB@ZHU>JqC+`AwFP|W?SxO8Ej?bQBkG;g10OZZQToNRFGcVz{tVS?p^MuZJIKW_ zU~;V8>+HRNObyp1F)>k;!f%%*%WlhKCdY*u`cPA83T0$N&=%R2U$o3--M+#Nu@ZVY zw8NURBxLL+jB~^~v8*9e#*L5t!Xj?8{Fa`{C2=3xS)$)gtd6K{se!Zhp&k^Zepbz8Y`DyS0sp%^C24LHWe%q3f@Ntj6Fn&>T?bR`b0!7JxddVpdn;KT`Enc+&Ae5!~CV;w^LR=Al*6k z;0WXE6=E`ZYIhLqtqN>ZaV1X(&z9_$=W?(JFR`qOunq|uraqn#2Fm%h3IlWM{1;t` zmdPfXtY2^v)P(Nushd&pOvpYI9#$#EO$XhC_fLlTG-#o}jCLJ%RK^FCoR3lXhd`T0 z=eD2Ass@v;IGy=!&37hl)!yE><;X?0CEQ=6EOwRzJJ7A%+wRhPo%iF)pYpigbZSO9 z)qHi*F$axKRi2rV*Y`ufT+HG{F}u@W;jb{co; z14!}Fh&SEJC=*|J?q&-^qsA0&J=0@xh@;8n)>wPPyXPUnPJAwUDeHDTvde={n^`&8 zQ#)wJ%w%$q=|u+@FYd~9J{3B!!CfMLVZfn~Js>K4f(JLPJAgWPFaqePIfu7vqgh>+ z6t*U7fkb*}$sA}22V_&KMFzSG#1nKJ!^Z+^=k62H^nwO5R< zXFuhJw+eQW_ViF*XW3ZyzZfg6QXklBmHfU^x;tlf4=LH*3r6A;^Lvc+y;rEvt#ruF zfgEaEaFbx*;sb{a-%Cf)QEoBwLnY7O zOEcQ=d3=J3FBS(stq1N^NOb<0PE34^%*%4@!3>u00Z}auUKfu#4=ii9$mEy)t{zYF z+Cv@}U0>k))gEs7-Ph%#-lW~{KW2E}fEi%RFR@oPl?MPrCK1!Pal=_h+s?w<@-@XC zy`o_N&xyOSd$;?P2aV&Y0~*b5w*;uUzNKyW&An&7Sq8em(A zv4p7BIK0knWwFOYP28JTN#_W5k{0iO#TxuPNRA>)6OGYb67nud!?Qzu3O44$WlsJ4 zNAGB$(fQ- zDcazr1`j)*E7c`9MWq`aSChmtqwOrX*ZGw9vAaB2jh2A(cD@*9_?5Em7mz^9m#*xq z;~}1;fjJ)GmKv2Pz;w3m{^HJC#4vuDAGs+EtkI!L1k5s(r+E4lgm!+Iqmc71=vWqN zvkT@aY5opX7y^9NbpO9lN_uPleJL5oe+|H$ZeQ3m*yBN?FuJB&+t2ktZvyk7mTGc8 z9!+t)}h=F;q&aQ(7y7PR9*%^s_*}YLT;#ecM|| zzP4ptLgRPtVp~EHso`gw@R-&CbnM*mHo?grK2JE%3ok`)q-Z)wz~cVFT((VN8{Y5m z$W^~AcQN0;pIG#rO}ojfiHCb#dj{EA_5fz$7EF_sPTh?rIzfhH(7Hr=p`n?LzXZqO zds&QsMnOTxXWp*rw7l}95DT@%a6+sVIT#m-EDHIVvB<5YPJfASM623@TvO>@5b#0e zCAsbky-Vs|;FUFGZy92K!?(!fW9KBLKNu=&Xvd!JK>O$QmNy$@5J}nwen`W!L8*gK zrmBgmQ(oN$PHECz+e2r+dHvJp2=ba!2DtbvZW85w{|Ihp=p}KPT-EA zdzG$^ID~v|#wB$Ko;8PuJ4GhK^>t%Bej8V?l~ovd#tzGFK-y-d7MX{`{moPmZ%?1! zT++f%0exAt1&jn4+#Tlc;M}G0o?H%U;?4c^F5(h%BhSp-Z{s}xo=>}9*H+4c#`u|T zXX(X2858SnR9r-%WtMyPn>uwP85U<-HX&AJg?_m=cgV7J8Q-z_^X_SJYR2spEW|yN z!YwS$cwD#Qnxlvd4{SJ2%-LLCbKm@I4u)(E<6HOr1_^2ntRd^;<3rLi@F-d?1*xrt z2NqTS&3oxtrgfGz329}OBZ!CJyrw5@R> zqaa-HiALzt2|_?|K>mWKh{j*?x(5ed-jE@!T{ID~2hzxRZ@=h##e=@{er#mPFk;sA zd}z|;cXzoRc41OGZQNM}5?!5#Tvb+3G~N0L(tx$zx^;&Xs9^983q$L5PjQ>~c2*5v zpsgwBUO*c-q{KyHr0IA}{$kneUe# z5s~FiHnbbyDl5fOsfa0%j;HXWQ6AHbdX~tD*bFdABk5U3?j)ezqx!IlBM+`y9o@u_i#Au*;v>=tKIHGVs=o#iR5y~m@gg{}E`ZV4+vq02nPb|) zgh?$HpG=*3SmFjWT-}7xw!Y5zGExZb#w549N%?uBMrZHPnEbUU9)d;%GN8MFd4S)A zQAH!pnLKmZ-bpcJ2zm`bsP;xo97&oS6(d=s9F)O>I? zEX}ws-~D;;ov1W|&xn{FmO(*svv-W8%@Wdx_K=;J_P!HmT=HMw!9B3(HVjKA=uQ9{ zx|0>Anv3qEGYy=URzmq()cjX{$ZualL7x4z83X^tpZ|Ob{(U(iBvcLxdskDOd(5x z#)@X?B}4~zvO+e;R!m%61w)@=q(%XKJXK7muObf0qR2np-L7<;4R5b&vIx7H7>iA7 zAQ!k#`;=Z{X-Jm zH};20cSLPH8k{YK^3RNbK;Bhd3U{hH>(*>wL&<+slObBvpL&q6i}PITr3U$B&rSMK z&g6LG>GjYo(9*94B2^JX24+_QubVYC2Mzb#GT;uRcDOvU;|YQu$Ku5y;90mt=X&j0F3`oOmn$2DR>eZ|$v?rF8yvdY*4 za@=HACRMp#(eQQ|%BivRHC}J$yo`ZW!?DL1)xJS*jb~TM4i&toD-+b4pmq+GN_}%T zT4z@bJIw*eo@=Q&byS~xq?54~Edsn4Mn`zeb=iR}Dust=<}nL85?+S^kAOPoLM8O7 z70Jhdxh$Nh4@uX@38FbWkvJk|>NC!DtakpDL+Y8_*j+uUF4Hb?Vv zvM&#FX-Pwrk+Hf&?|^JnK7`RK6f!yOZD(-aSrXMmcgzUk&2{gd~?4& z(p|}>k%4<1U0jrB*IANKWb z9ZEZ7&C^YR%&8Cki(fmbnas9jY zyny#m^&*AOWqHtFmN8PMdjz$U(hj6>7Z}LpY4RiIUzxQYCQ!Gj155*PYm0x?ULFvDk~soUxYeHdkt+Yl930q;Y$SQhd!6^ zlgQE$vdNk|Bp>&mKaS^!2%h`w=fCvXDs|lJhrX`tDLSpnG+s7=H%cc^F=0iNVUU$% zjBefF!0#_0SL9Id0#%8EcXQIa6m6$@0x({uddvdJy|chn_Yj2uQc|=>p7&`rHu!38 zEG45AxHmlo49>W{^*vnjKmu4S4(#vwoV0Jrzqy^m(;Hj;T!4Uhfqyu8HQehhVCbj$ zl%=t?{Ij6M0nTGP6mNX*+gkD>-3ar0#eP~h61)F9d6O{T|2}U5Li)$NiL>}W=1m;6 z{ulEm=O1=;q$*zcaMK<>ulD&pcastA&%!9Qt$eg!QFHJ2lkMSrlwYgHN9P4lA{2?B z>iu|grv`l|08>(0Ko+la6!AN~KDyyb#=up_vNxZC!FWZWx`?w*(bDMn5*vJWcamn> zc6WUiwhgn(@9vn{;B$Z&2{}oHomOgfr1_}uTMNn5q2l$Ow`%2ZQZImulT)(HeMJ3W z`ARboq}$uPdi^4u9O0{82Io^6tTUV^81z;YJr*06TnHvRG! zOQ(l(-r#;BnLLoLIl>3j7;y{emnSytZm%sCFV4>N;33lk0piO!2`KH=hH zOmZ1&mb}7)SbikndnEM*T`PD<_wy21EGvzf8_CgKMCb!v!Sk+$re1>31w>_3a4oek5b1=ghmITmJnJ z-;pM+xX$aZw>}|D-I$4)klabw1P@|r@|QH1g_j;#Ye%R@SoU08B5SFZa-UeiI&WP+ zZSD~B$_a~=dxb|Jhc6S66X>-C`LMr;X_1yX3jxO0^hYf7v+p2BOow7>-B!qW1zkC< zITm=D)78-U(>32}m$rI;L;hqsIkCfK)JGrm4{4L8R7l!nx#K^kO)k2&3d52g8cJCL zx0z3cdJLuyKjAhxOZ6#;>0Mw#v6tI}bjR4?_AB(h5AF$$c{y`EGkGH3fEE30_DX_= zJXp=JbIE>wm-AnyO(@@>@b?74Ax?r=Wg1{asRO8};phooeTKYoLfPm_!XIy56P*i) zT3T2e9IlNim4nFQ8m>0{Pi{OM9l0ahQtuCTXSor;QvbK5P3R>5tF*~#?7vQ%u>8xk zNkJmp&NHSg{|_)F!}lLBFrlfB|%$E)7ORzET z-^_Pl`##pd6$CRc=2T7}h&!}4A6=3inF^uzq_DDNN>$14lJZ?D#^1YfTK$A=>x zvHc#``=?&_N9)TRo84M1hu^&c&7EbYT@_aWt9>GG2e3cEYS5%H^oNJk(dN4u+;T8D zA$aBnqZR9X7jSqg&$05EsVA+>_$kQxKJF*$qm3%5_}9{YBIC@4Pbl2lUl{NtKBmXs z6v)2f!^IW3l}a9kzx*C=VvtAe#mke;>6k%1rd%cxqM7+A3a(aPrs6$W`Zo%opk_Az z&2h|j_EaQgCp-z8WpKuQa!UdxRce-90RL_F>W`yM9=G5fAs2w&3|8x!bo5XwmiM4?lMg||Y&^f{@N9Ha3sxZ{ZmLot(zEMe9T!_rv65Xb_wxDUUFf-C&4Trwl`QdD$s8d{ zX20K2c_;F1tLxQ%N1!?6Tg?Ota%d@r7|d3VpftP!hH`L4rQnY@A^!nYD#t*O6Bhn+ zR5=Ctw;MzvqM(Wm|3EhX_p&IfeB=L37DcXz@jqly(t-s+zy&5-7bxTD)E`bMkImH- zdV$WABCwAZ3S7?kSf|%-h1oDUad*S5Yp0M*gFfXhtAQy%ZF163Au+HqPKz}^AnV(@~VD0VwG4&FT z!0ZV)N^{jx>Rsps4z1_ygi2|o&|YhfNUB5kMII4`43I;I5fg@3g;`}+sdV6pEfpfz z)V0*DXn0y58%)nw9;%vKE|*Sg)>xFzsPfs4{CEkyCDTbnd$76t7`$a4y=3X{*<(hhOvkSq}flt~-5cwKuC!ouiu{n2(?% z%Mw|fV#686$r8QAa!1&lr*^r-LI;dtL9+{{gim-pD5;9`8NDSfaC-E1HCdyF-f#@X z-_o)hfVaizCG!i1+uuvzKIgJcmNE|e&Zb68j36N`Z|J1oDmEsBVAURGWik;=Uhyn#Gx4&+f`6|J;~h$)ooW~JFaFFr0N_FLT6)R?xdSjFF% z=G=2tH^tfqzI&BVi_&!4}j}|1znS99j5a%3}Ju+%% zNG_!K=}V*=_!x=eVf4k@CR-(rVv()Xkf6LY7r8XKFN6-iKW7N=V%jq7V0zr1x$H=s z4&>a9V8b~qS>|FsMY(^o{GFjJjp0ROQkIDKmMq|Wd$c2A$Ic*=aGmlga3c>3*-eWj z*BfDsH@mZTjkeM4bMu*@Lbd<_15C(z=Qjeg>vX=wCFCbgX8qq^-;-t7kt(usq*c8a z7_J3<&-JJqlN|q&EM32`!XBj|i{Z^suHDan*sedY5*1ZkkA^- zC@Aq{U@9ks=EqGuVzr}*Id~ZCZqdT%Bg+hy!X>*`LKj$#5m(jSZ+lHbnD9OK z&qKa?3^P2_GFtv_tS8(p^OpB>s7Xj z{gH5*YvjZ##f<}7a1|3f;Z+OItn)~QGpMebhT^B9kj6NGyfX9kCZtM$SS51$n;$ig z9M%s#*f+!jiuY*m8P=mTMGNRwt@kaK0+AwKjuT}2mZc1>5NUi7O5or3F(eg=@)a*x zj!^a#io&kYU~uKahFP4FcKW$zX_xA@pFHon zu;nc@^q53>%d&%(qQ_wS>ekhWC*_w}3KyTq`?IdvOoj~N3$QFB3TIWpq#-lY5OD<6 zA*%u*L$UM&!K%)Xk+!3P*4cZO<&aC0fdW|7%?6eE-v;ap+R>C*ZTqoOV|G;?OLEnb zACW2ro&7GhEO5G`39cC&_R&7O#5a)2!6z=WUo0V-Ut)R6FdmfcK}p(T^4J{ff3w8+ z@g8T-8}p<)-{4TRkSY{Zz1L+Mdc>Dq!k67p99Z@dXWtvM=P7P4m5J&`j?eRhN#Y{qn#F#%gB_xo_BwZ&)v>(I>^?=30kN-e}DlM!j+O#?_#~ zuXZvmdN0brsqAURH6!RLx_;d@a5Le?aoS%6iT-SK!75<|jX1-dh#=IxNW_#Gs*ViC ziD-d3a~r#V5`ZCRUCB*PQ%^Uu5Ze3P7+Q_Il!tE>6+5Bks@N2m0glS{db(Mwl`rFK zcr*{nAZZLwN*;Zhop+D&;sb;_iqM_uAt$gF4{3!zdMnGb4)9PS-7`@JyTY5T3)`8i zrd#i`Np8Qew2h-TrnIZh3hpJa?v2{0ER(Md-u*1Kp+c-rK4gp~c|d#Q$kvjJV<^;V z$I9P(nL=q#TB^*H#?bg2rbr<{I=Dg0JIM?9<>{iNYi~YL-xz5 z3chxOCu+S(e1SgZ={__;hJ{l!WKZ()rJHMqAnKF3+OKb=1Ug5nmKc>nR<^ptSj-6#8FzT zqh1F47VR=KM%jD}g)yp#>n5OnfZ}JP9!)AGQ-mLjNDEkPTj%;AgKbXPws~9-aAr++ zStL$#ZB3-sOD`WMs15(1QaWT2RNMPTM&fQNWdGs&rLYqb-{#0|p6$ID`bYgo<|at* zMnT8@je2=?N_K(A5JosEpR=6Jo_+S9D^}qs!f|?E^@~l}9zSt@Z3?d*g=z^_U3Jbd z-IMpX$!n-`?6g^c+e5AVAg4|v@-Rymrd0<&g#Ge$b0-cxZ@rgUe|CbUKm&;4`fBST zS06O<3zi=7X0@TQ(qXcf1AnE>&PqQ(dhvac_;nlds5d&_2*x%IK^d{Ga_C6~)@(nL z>qw{>@mz52iDempy>Z>Pp`zPU6x0`1b?Tkxoz7eQuguyiUvux^C!k)$?a4HwBuu-S8nEFxbfl?WHwlWQ~7?h zbRZim!ftNDF~oI%OLAI8VUBXxEk#%r6PxN4l6y|Q$ppLdN$nTGRgAboog^_|e~h>q zywSQu)u_0aps`ysX+N1Se{YgE*WQnMIqQXh%6o=H4Tk2KMk?t`RTDOm!A)3t>M;^h z`=W5M0cW(G#}4KfPAgc`fh`Qx?l6#pwUWs6Bvia$u+v(4WCh3%nEiNgTLR_~>B8n4p%lxViNmt@sU~bgi`^G+OQ3#}MDAN+u2qINVaEV|iwc_Gwsu{1?LypiwtHpDr z-=sz7Kt*K8QMiR0Hf8G7xz&Fj`jX1c2p6ohE#Ono2i8Q`1}c;L8XBAhJtDFoWq zx1}}3{e@K3@Q=ti{29P$k7sr9;r}7+Eu-Svns!kL8a%;+1$Phb4#6R~6C_x0m&OV1 z?(Xi=5Q4kAJB_>3eaPPL`MxXXo;$|9{bQ{kJ$lVvv#OqYs%FhuMz=!EbTjV}M$qv> z(*u#7&@oYtNQ1RUbGag7FDVq4+?piqGiS^?;}>4ztR>acDi&zu=NAr#KF!c@vzz>R zMtUWkZqt0NKGCPRkxqL(8*wAviX+40;GgT?O)b1j zcKI3VyG!}(NK{%-Yq&Ly-wfniGAUGtq}?0@(cz-Et)96gdtNH{Qps4=J@#owDMlI$ zCw9I!HXDCm%*$>-oak^dAF0sX89}O4&AoH$-RD7g-6Wh2#`DXPAj=fl!d;^ z1i-#0FL?5l9pU;>Ze8|3w{7{4a<*r%F|`xoftqm`yCT0PhPyE4tF2@jJ9#cQxya?a z1i=Y=yn2hk11)iMD_KrW?lhf!J(1%`bve;dN|&3_jQ5=a*EI7fB()(;H7pvD6qxMi zwOI+(ux#iIvkI11$wt*5E{U&DS$c&#$$4FR1hE+7xK@IrcAd2$I@;JdL7A82%2|?? z=+#Th<<52Udyfe@2Y(O(nc)ATUTSyyM?C>~4LWA!G5Ba4!aq~_8Tslq`Y=YeSfPB^ zRjgOJOcUjJbH@+{Jrr$fD|f24j--D@2#**yk%vK~*k$m$46@RSn~qcs5x~t4F;3rW zU+#rbe&aeOT>7Lh6g;5)yD(IAGQHdX;GJ%R&dij)ONL+_6?c%nJ`IpYs}2nq5)K+U zS^w=Mz)@?&@Jq^;H+g!DQNcldV{FbvbFOVPCY84#!L3PTF?C!ohuV!o2kukc=QBV zBAr^zbYLq>Km&r9Q+z&FzW)+0VGufk?y@K}R0QecH;!PzB%)=Z)|RPp<`feH7YLkY z2bRr2ZVc6E0D;=>!3+-(x=u1GkDZe;8xfm2`_tuwyI49;RzBs2bxGC*8tW{{cBZWT z1|O0MsrHEq&_5NLLqfee1W6^|*E5k#0GNiYYsLgfr=(E`F34SIqbeWL7GOuwGs}xPl+z_fT?IVWBvblDx(odaSrbWOTn$1A&n8xUEq>uH;Y)}7@o|0oE@prDO-4J!3(?B!{=)3S?L}?w_x-p6 zyB+9}d0ffaLgUk#iqpc^|AQs>Em)A0**`5 zeF{s2&cPSA;IfPqqhak#YRWnSA>b1t5bt84@*EAT-)&OX$g4R9*z5x~|Kx$|OZcfC zG#GTf@#0oTHC|aMyq5!6xd=*3pZ19A(JXh`=ffRbHT`iZ#s13NzMJuDJ>ogb-7{G= z_~uyJ|M!<;E$1d$wfXu<2Q4rIs=Dv3Tb61iJNgK^7(E};*OA7UPghrfMp)-fV(L)lw+&0p|IA#jF(C_!Nhb8XSI$)Su&ou zYIPLMSGexSrt97OT9eBU%KpV(!c=1n+nku;DdsJ>eUu^MtPI7auD%DGrULgVDN`)h z3y69Nck-n_n7@n6O6KimtUgHCOa%4dy*X4lt0*binLV8#w6xcE46M4+=cQ+E<3fUq za0#avPZ9f7J3!izl#fKdf!QdJrArXoeeW0_gPfQ zyNTmBno^*j0577{_J=!->Go92fb+R8F6vqgv?i1|Cv59E=uh>;7lDLwe1l{kOrDtS@5$jeX#Y*-uftYctAPt*-DCCofSTMfbb z3B!IWamk)Q?=*XF!7j^$lP{YC?`zH&;?_w0Oqq)Ar5T ziw#i@x+n@;wt`~NHU+MHJhy`Arz>RabWD#pLF0a_A1_viky#Bj=bkWcX@KY4TcJ5! zqLE~e=p>Jno4AX4?6%ZtcvvYCx0|v_C(rAX zOlu#Mp`bdDKEGB!++R=UM0E|VV4^4pP8q}Ah8+UpnB|t`8LTB0shQ&*QRj1$Y9JT4 zeg|5@Ij7s({vPzUkrSEZZ_of5!@JrM_Xz*{I_6$N*A~>g)>h)Xa=Y9fH{P~41wq~$ z1lmOeWJpl|n~$&l9>*$V;y&XBTxpKpIeeoc7!-kp#X-;D)<8dBDkka>qd2__!bG^{ zQzLBtrd=GJ0{!pfZa8(vA*2W)YSu!I_o;HULO^e?15v*8d9hsi7vu$i^EIo=6mI9@ zr=9LqrNtE-Lf#dxGu`X%Z_GELdx3e5T||tiMhb?4<&7X8#*kfh_=u5*Lej?z)J ziY0fneUa9Up^vZCoU0;_j$-yP6hHE=D~hbjZoA?kol>)XiA^|}S1kXRzmzEiG1#yG zg9m2Mr4|Mx6)w};K_dgRKy-K)4i)K9g{3HgH&{+$|qwYSiEh}GSk=IDix%d9pN3o9c(3&7VC3U(Ck zJzJR_JHmLR$}_T{&QdMLBt#^#pFcoX>{|(UhuaX~&*GVGzd8h7_dyfge{xoV2tBi9 z7=oSdAe$eYTh$|OY`S797fSdD$Oxw>`Bn;(ON+CE!o)vWUny5Y*ppsvOvNH}!np>O?MJwX1YJSv-}fK;H@XOa#SCWG+&E5X-+@f2 zR*LVdvtLS<$5r$*a#bCMLN*{_S;MQ`t<*e|GR?^Sj!iLRDBi`R&I3&ACTw0lkX0#q zuTyE%;Xk=n*kA1Xq^Ny-Mm6p42fXdajje;xqBson3cJ7$)5(yZ{PxeZw>QhhBYM_z zc+r(?Qak@@l1GTbF(A~rPjU9TI=}Zy@hRlIMP{usTRPkZ=Kk%7j*|U3Rx18jwExOO zu=!aMX#lR1BWs~^&(~Tz9l;$tU7313rCnj8t%m@;>ss~N^ON>_*XuMIDn*x9X132T z;yznwGLag5`4HzyppDn}I3Y-UTkUsIC2kO|!V^4dy^^CF8z;8WcKQdd@4@B|SQ`y$ zkXBhBw;pN2!g9C!4C9fOn9AAw#{-A;|j$@ck2Ge0E)h z`fa@p&Riuz3S33va|_1v!dL?o-WY&k^=f z4$2wBiLQNjd!lKMl3xkvFwF}CZ$^ve|9R>^$T(~N9(0R$WQl(S#GKX6<=$)##^oWUFStJ@CR!Z?{ifAZ?yuE7uD4}^9_145GwGC7$q0D@=@6!YL1Y93S_Lgq zrc>Gf7Xfbg&-76~AVkRON<8FJHGr+mRoCn8<&Qe^2cY&d+aX{mC2-DDVdLc#bbX+B zw$a9Xy*2dXksO<|6ohrLMKT*Xrt%m}vgg4A_i~{8EFg&GOg5nt-F}(DV8WGO38o$x zjb9si;Zmt+V&FU(Orw8OZq3gAB%^S!Yb5yT@tkCwW$T$D+XrklGYf6$^=k@381XNB z916TqnZT;a$a8$_v%}K`rx1?|Bj3ssG2_7o7<7HF?1MfAC0SS=DPm==bqgNVZ<|D} z7vp`w8LiaBnM}ydj69x@^pE-kr4#Ouu0ie}J(Nd7T{{-;OU!OF*hkHkv4^p+k`P4@ z$E)qGs$*E*Ph!mv*w}k-jqTgOY4AtOJP*GykF+y0y~4vCfEh6N5RtWYO#FzZ`$2qL zkuV;Di80X#{L`B*i?h@ulv>kTBkHnFIe}+7mAQpQcI!Dv>;TxKMo|m0<2_gg;lW4$ zM>{7y|IaZOY7Qh}@tb}Dm>a7=X*qa(B)ydSTrp9$Q<)aQc5QL*n7ed)x-wYW0&>wW zo2;W=kKb|(D0n343URSIY)>4bSi0mVX>cePg%Gr8WEN(5XPhjvnu}GNZYTptpHQ_d2a1zBt57a|Eg zeZt*IT6Pw@It+7oNyzsR<30+P4cpnBS4S`2GlI-f^#$HVbl~7Y3 zW15eKGSi?sSZawT)}gK7a(1h;H3bKk5Vd0t`?0xN=;PRX3`?%aRHaQvv8g97J-= z2KdY5`W$OA>tEw#?;*x$@m^l%(*S~wUFII+rnLB;e2tEK7~b*gOXdSx{`@RLwAw@i zj)ZZBhx=3lMWrafI!_~Yb0h(u6~7d+*4XbufVCF4Q{})wmB7turI2#lL6;u5jt22u zKLb4Q6ayUAjr@=G6SXx!q>g`7L7+>`cl3_t?`qZ8^f3M<78gh&$AGd{-X!K*V#Zq* z5am`Sy%T#ODq@u21>%iRdGfE6rM+-t%=mtZ;$Ahciyg(xU`=e%n$jzXZ^P?mF8DlaYuf?Q@eHgHohA(TyPH=~lAkPXyeRD$b zQK7?V^66$~YmVXN-X?eF@7Xy^iAt2t?A+st3e`zy(=vO>dL(GVZccrDFE)_kJKgr^q}5)Rl+@xbb2Vk_<`J zRH1(d0b$O5u<`gWcl=%cj+MrNQsC95%I+4wd_!S5)w5mw#JC9bcziUiK7U}XcW^o? zSADnd-Uy;sr&g=V0OHGc9y>_tOqND?;pCOpvpTBj&2u`Hn&H%U2i$&Cw2akH)qHDP zCK4pvXHs&Wz=kQ)@-oMvIx?TnD0jR@B;%F%jKD04e>SG4%9T7t8n$E*o$;|g5lmtJ zI9!l;o+ryO?otqk&o^x~UtlXz%E{PJOZi21Zt8(4E%y&W#c7Z;a}k4{vK%3s_4-lb zOqqJI?K*qc5C7PA7NZ_QDo#!^DOR&@73ODD`%L9B4Alw@)hK9o{UCZO zwY_Ej1iIp2nMNn$hGaobH0RNQNSzghsxE?k(-3q5b=wkF7#Lh+9(brC1{A`!*4F9` z-sR=l#0a;l>_)TV6QR^nA9&5b)X|Lpr=4NASe4#CPFkNR(5FhaiPh0wx3iO$RS66MTDVPmh=Rxv&-=bGk#`mP}Y-U zez43cyiC7L*e6p!QJ?2gBZt@k@0Wt8J57g*_N@NWWxQ%s_GJ$-8_!ZF<|>AO+NIPw zlx7WCqLd)j=}FN_3`P+O z+IM;ej;6Sp0{tTESu0UkjRLNNJ=pD(7if`N;h>rr)VZA7)S#%5oBz*y0@ zKI=;=B)=hP&RB}BWzX+eq$acTUB(rI)z4~}T#m;elgO5qEV546Q))1YW4(quJp$Zu zf0G{5V+GqZ#~S{DKSzzg=N{qF9At#5F|GYmnofVyReJg$!z|AQGrC9KFANW}Rd$;^xlcnuB0eITA+6C~p2L+qth>~+99%!1!K@e?izg{@rqlWBP0o~*o zd^0o3U!qhibTxLlTu{w3kRnieESSW$z@5%?r_orFfW&w?d=0o*cgTz=5UA=vmi&`e zRFtO6x}W83L+BNdfBBHqb44vS1{jdUjU`E;u^*gB# zsrcu58AOcQW?e@&e-g46f4SzT2YC}QJ9o_h=|a_jk@#B~43w{6*razsCeX`er~T`c zY}BDU*OX8}-1;Y=O>2Am_Z84YRI>beL}J&=k28`Mefqa*i%(rq`uXJ%>|3EuZUwnH zuT{BCr6;5N)8Q9=93XxIzVFGqTJmp8!&$Q~AAlM>UEF4$yVgJ?E-!=)JFcclt(DvI zl7E(KMr7wHe*s+ z^wj6!ZK*pb>djpic5$Ufjtaly;|W_;0%;raCvmx}AkPR>1XXFq{;?T5GLs4vx`cJI z@^Xut9o;|S`yHZi9FU-_wJPk6_8FhLOaYydqJJ5@{u|_e@8~8D(QPOG>$f`SFsLxC z!VWQoEX_OX*&5M^e$IS57?Eqo={%E7RlM7`Em1Vk%WibnA$$Gd%Mz-oE(};t&{xLR zX(KC_p?@+kIb>x{r~Rq-y2{9>%KNj!HvQ`cDXqOud&un(7e9gf#E7^7)2dRkdV7A0 z4Cqwo+2Z|Gyv(PAs?v$|w&E6}(4fMSxrZVkGN-ll#xIw?lako4(vd2qamRXWVuk2h zE64}>ji%T(>lx}m-k@4LokY#G%dbyS?8PG+?G@Q0grjw}i^Gz!Vd>xlo)6OX*j%SE z_^GY#3mHpb_(kqsYmV+f)P8~!Gs9k4voq}Mh`lUYE(w&rgOtct_g>VgFF^TSzm^dD}IQXUbdE`_{xfK!t+_|# zR5c!TGu>Kwz?5b`2Vvte7oxNV&5KGl*;GYmZ`uBOvnF*%-NhOY7;|{|GaMH|C>GN# zkTuu95g-Ho&6$|+``8&qi!uG{Onf_it0ql-m3*{gWAFJr#xB+z=#PKWmY;$Fo0LV= zJ@9`g@U6^@IPm@J4jdnw#@pGuov}P>f_&7YC7zte{$POG;ZzL>`H*k?mwpqcsXGGQ z_1N=^-h`~pzX^}!U_v%hA)~!{uLk$h93LBN?NJsZSAkN5S4i1pzR?UWi`~eG<#nAH z@<<=2H*lM|@$%m>|8LhDqhHWH1CqBSR-8o(j36Q>d7_sm&xgoBR6JjoNp|P7G9e}p zZgH&1o*&*UfHm_s8m#}RVyAgE9}D86|7rH}%bzSmyQ@$f@)CCBv5PMD6}4Wpe(2+M z!UMjt$aOs4!RoU}3~{?FyV0e9UYdB{Ho!wn{+!7}nK8?>%_RXTsK+jGjw6u@h+X#; za6-d48W4CXEftb`_v+EUUCmk$X>XVr<;hk0NuI3i+Nn1EUP20bxoF3*>K3J)oA1U% zT;!$`>&zQ?13wx0`E}#JGT9B7>t5!?eFlPV_O-vI@?Kgydm^UAl1YXeo(#Ht=Dx|% zWye-M#Q|-)bL+``n$l=P{0~Nr%xDkw_C~ppDpazr>{rbO!f^Tn2kkTme#qWf4Bq~8(9)#nj}S> z+p3mZ5}~+Rm+kx0Nljr+5`50(ObNqo9b%4FoVFnpgm|5CK~9cRn+x`e>R`5ano9-b$`qN`mToOPY@zE+E0bz-mpGKI+{Qw?GYeqwqh|VQO!y2twUd3z3KDNL05x z91q=>kvj^#0xQAiBMak6Yk~6Y#h5 zk4DE_g&HdSqtvg(htFNtW-M)=8YTzl`WtS`wrGln;KB6D%Z;{hSf3(^@)b6YQh5)+ zDMo!@kG#w_F_nUxiQPDzxlf@-)x}H>ZK#9D|a<`n$TQNGea^kbofbxSa~`DG`X>p#Z)ZTv`7X z8MIi`n;7I*IedcuvBR$uyCDyaY34r72&3gLa`P*UM)wbn{_o`#p_hS!+$9PFKuSSY z`yc$Ho~=<6{#@SPQlSySo=KpGQ2@Q+6(Wb}n<9Rv81MIUwvD=0*EOxgoraW05Jsq> zq3O2q^oNK;w!WJm4S6Y;>pYtoMuqBy7yy2r)BF;v>^Jh9MXf0dVIDP%;sQgK`0iV1 z%D~Z(kmNlwvR`3HgJ*TW|BR2;amBQMu4$*C^C!y-#l7$^`4Q&pkH}vbou*|rzcZui zUYa#rJS|nvkjQ51F6-T4W+`-;W?IPE)q3)WiSJxoU`%EIec9*^eU1xp~Q9neR!THkJ%Pd@TGq2D**p ze||41a@CjHXF9GP%I2@_+m%}-^~{ui;m05afl!SEP3RfUvHg@YZ=-Zp`&z~+hm)%j-A6WpeKaus+@&S&vCpsN{eBXa-ca<)%V4VXWFmgAkKnU{?A?Hs8`-Th%=^4}pRh>w zQXGg`F67yr8u(Vf*8fuQyU+DPh1y$CHP;jJ(8Y%z_Jko1ml(Rg4|#B-u0D?|X~^hr z?}uBOg|EeMpv$#^-a2M851OUyshL;XA@)7irCpPKjb`g!ddNfY7DHeqo;LN2I0wbo z^wXLK_U%yE5gPks4O!_q!FX*a^9{%!3w{$4<|s-x(ie*IGh4zrUf1%T^U!#uU)v#P zRG%<>t$RBs;kc-_1J}+jrDyr$BWT}=Ro%UW6hD|VO4NQa&K{9F)b~9Ow|6&{W69-b zSbd_C%U~OEk)5{&3j*f&61D_yWkBq1^iJPw5wDHnkp!M8D_o|923{~#U~f^BzI|Rs z=zcPYx>ceI@3F(pj07Lhu+8m%YCW8J8mVSpYk59;ZP!3&z{j_Eb(&Lt6>|Ckx$uRy ztT^8sB0K72;|vVFOPYspOH$)s||9ADmVd&IIBSAU7AmAiVHUKB7A(Ja9`@5LL@^J02< z3#f21=?D>VIIjY7ZRC*KF4`gL0G8L^dK+ILA?3dw!@rQ4o4-EFUx%Rya;qm2tNXm1 zZ8_8?&f;c36cJ1kiz}9eQ8Ry0hz_m$AlcJD=n!yWGEM@e zi`cmA$w6i{uz@caU*6CgF8BsdXXjX&Xv!*?;pc zY0C$OFB?sc)`OdTEvrM^A@q#hZ0L%$yhzN6^S z0~_^@13Gr{CTYXZo6R_sylZ81S5_wWE>vj7cYg%SWNXPG13lDP*R=ZaFC_gTv>@Gy zD^a(@>((n7eL&br7lTRW~R2}?^!xqYSV zw;o2Q3InevgGm;I$H}tvTqi2Kys(LXrNg;(&+qc7r0U8l2y>g4Em zIfREC=~GC}3&|mDcwY0VxpE4A2R3gBm*EuhWv)%$Ig(=@^WWBb|6qL%c!1xj=g)1v zvFwo#b#-NCT;W&Nn`BlAzy59V=ph|XdL;7*{f=91;Lt%s@T-uB#CVz3IoRuU>{5Ar zy0HcCr~UoL{L-q;Vk#^<)$#! ztAKm_Y#KKBo2pTCxz4G2<@X?uUS#;k1isb!39U(qzQUh#x$G<)C=dyH{xDT& zxYa9ueaZ%d7sB^ZfEw*Z3}&6evk!TG)jum_kF-@Sj#L(CYMoPVPYK$8DU`E!t};%s zOqT6Kdv6c7u~lPgW3@h6O0`l#GHq(ZJO41!`BsZQxZT`@(qXVJ1t++#oz6lU?)^J< z!rIfPu^{^&v)O!)IEaT%V#piUb8FM|4??gm8}|0gM;zqcLx0jZ*Lr@d(Gui&204Ic z#pkhUk)aIqt@0r4>^kN6tMQg&>A{J2v$w1|lvn5rw_j;EY(<*NJoD^*C2Ruz?5r*c zEJZLoE6$?t)@JAj9b@t}^IbdBQSZ>}-KsIMl6Gjw)C5hczPCVzZyCu!GD=1%@ia;; z$Ji+ad6?mTr__Ld=SnzjxzRGbrV@i0I)1WZQ`3~zvXBqZjf}6I?WiTKG3a>OxO{7; zII?8Gx6HBBu#p*`8+>F72w$`E`%4bq*(1Ia?Dj9JKARjOWRr3>r<(Bp*`F-IDzESJ z!Oqzi0g5Is#~!f~gTFiP&?fqC0MNxnI7bk32=_SIFx5=sPI-M#7arM3Dsj>DhSpPP z7kiKfMzjiukc45kfGTY$@q^PEV*7JW^^ns~k6VJe`KoG0{^QS5q}oYM5j0r9L12ck z;0Vn{c;v4r__t!K8TvZPWmtesg$CbyTVNrVvQFh_RiXjv8sIz}F>BSg# zpEZw0WMd<%+FUS`1*9n79QR$i?(xb@N_@AN@yNL1M#Uw9I)Eq{?W{zBC?s>)t-HZg zJonH$X$=_x1b}_#hvEAJV~b0ea!+{ZcFzhnPwjBlez7!C>I(@L6yn?W!k;nzy=h}; z*iTbD79?I7#oaZ*8lf|7r-(0=fVyP_x9`66Xc1=qPGQXf5>0m#%_Z@3eAl9wmw=5N zL?^~seVkZh#|uFeXLnCCgAxzfp&f(Z!u2wL`=+PNq)CC=%nrB4&H_oOyF<#7wL|>^ zjZH=YQo_< z9w$#2=Uqx8zCYny(SdBr<)6_*lkfN^s2ZRY0Vli(g`bJ#U2Eih!=hRF!e@QdM2;LkRCLkJ0%%w6)P{TAKO@|bSc9jw&S;}NPZJ7P%< z{*OF=YTJH#=1E2-2s}Lv(rn8wXRWZk1n7;S5Vz^B_tB_&Uth0%`Ezj#O4eXT6?)$@ zPihp_QL1=$!6PPN9zDcprDID#NVO#(_et_X@&iMTmaJBi&xa5yGen1P=~>K1{mAV3 z=!k_P5d}6>-4P?JxAwAwwiPcL68@nS0U;O&r zrHJEQ?-=RRQq{ogv`^$+ssx>o5j~3R7SM9*RWIUgo2<~!p;PP}m2UvR@gkChQoJP5 z+gwyZO2ZR4e~8))N@p&DZ%tCnFqCqYoKlUjPnMVQ^KPuXmat84n?6^vukBS#9CqOz z$XpOlTR)M>VimC535W5%7gKLY0h@Qfch{mn1gJS^Vym4tU$taWu`f$NxH$6hqb&2U zDM#Im%$!ncS9NV0hUT(e3+%KJdBRqJcYQ|4{qXNvn`Z)Ugz~VVL68{M^6?G+o9?jD zZDya^c?dD+T$lXZHWjare?={LE`yweWO!z)hRk?;{aMOUEwE*G_qA;a2GjCxLr^Wr zINfTN?1|*gtu2J&J5kdEVt10dcV!BTuCWJ-WiCF8A}H|n27OpRrZh81T^bO;Y-i%E zIb>gSLso0jY;0Ty%fwRy7O5YfJn}w^_-(hpGpLDfUr-JDS~knJu$rISlZC9~Bhl(* z6*KO&{aK=BpTEGu)QHk)AaM?%)&kCt4)^HXIMS~bfro}X{Zhp$HOked{BaQtq-!EHd}m>jry6*W(~jJ`j$wAJi4A! zLWvQ$#X8&Aq3IhfTPCxl0uBAqFUz9brEnANaHfEp_oHt5;@h*4Z->5GAQ9|c&Bz5g zhsof_!83-r%SeIcl_j&4O!tT-&RA5H#mx?1VC0I&w;Y>|v6B-Ti)(2?A4e6CZN{M5 zarbEkl{bF}XEI9%VeF6^!Jt8<;8(IFwc00|oT<#~BgvkwJq)A-aeLnrjbsdW`e`v6 znF^(7Gi)#^-fre~pFg_?l9&U@q@R%DsNcAriWPy{d7#7?wuw0A(ajM zYkLZkae!=xH45~C!I{KygU=y)D+Lu$cbIh=wq*W5X?0kSa?EzZ(6j8Xh#VzZy@?OC zYRVTOb!MY>zx*y4!M8jh2Or801M12Jmw~#TE1PVSDh9tf!JSPBOjd+8f11i0yM%80nYv%cIn1v=G+GyztpVM zoZW^*?s$Vt;dF4eTEEH|!+D=vRdy?%jg{1dIE_+|{uYXy@Ybn4BuvJs6NwL5XWWJkG(pP7%B=A}kko@CJZpmnQuCt>9h+(QtyQsrSXg~- zpZS@bwlo&~(Xsms>VQ4eC32x$6&WQ0iV0pr3M0KQw2`Vb&1HU{CXnZ)`d2fzFcbGn z*b#(JWjbzj{?I|KQfQH|N#I`eJui`wZ3v^%+m9MAH)FT6y8L8b^P2(bIA3YHwlnOq z^q{>?Y|$Jl=loR#z8j&C%a|Hy1gOyn9Fh^?-r&lciC}a@B@2JaJ$MOE_+E_4| zStH_KOccu7e9FG|@{96s#}zabv4J*+I#RcSUf+eU+3pj5CpemGjz5h9JCLvOfC6<= zN7PS#|Hf^2eBEH&oH4jzF4{i$)j(H3w5)HsJTB6O;&0FUEUwl&W#i1%FBp}<(7;7{Hqu)!6vFY^4vn7=pb;Fuz{nq_r^R`#v zsM9lqzq?!un=W4rG<0CS$YQ{1$Jju--BNh-qI#=s$i2y>DXhfW4qK~V^l?e2_kC_$ z^2{>rECyL99!2O7ijL&#^>s|Q9E)Mrv~C+;@AUUkig@!hlh0B{-lptJA-HfVcUy3J z(X=sc38ehlu=iM2@NfHd4mTL;;u;@Xl%5cjXIMSdU)Tb@MWJ~OE|{+K*aB-8Vq1cv zIRaol^C!LcCBf=>Ib<5ZVb@5S2*}r68o?A)rkw8*d!`T&W_#)yUUoD};BFWYMPq-s z6-v&3yDUiiWxPqWtMam@#t#;%HzJ96Bqj#0E-vSpfQIv@QPn1E9K{o-F6$pI;Vq`j zx2m8_-S^L={1qC)@yy#x9%2G2B33&AqwV}v)2s$x#vN*M7pU#1UK9L=sy>9GLJHRE z?08Rc)_?gQ1ELIf_>gw>AAExv;E`)~m zcX-jOD`jVU!h>@nn$nx6_?t!K*}B@IufZX#|8RsJ^xBrp+v|U+xpwaT;=|B0V2EkwDD5L z3@pJby05MOmHul;Grkec%*ao^BbT&hGia2=N)RQ=X-_C@ayWUi{7bG(7U$UeS;%%t z#U$K7%<-n-z^|M_8rd~Y0e&T(_9pO2W2dY z9Br!kO&mw*0GWh@c0{Pu1Yny(F;L1<#Jt^dz{+W4__z>e(I@hrb4aLZ-7uLcxvP5= zYJRC}auI`irnZ0~mAo5aa9{UrD3u7jUQet=f|$PoD1-R@#aIR?Im^?rqI$}Tk(&BZ z%>SPHm;ad42M1>iewfb~QQI|OF3Lv}j3SbjBR*_ESOFB+k%z>+z9no-v~{I$gfoRafzAk9`v{xB@~b}Z>A3jmxy z>gc?vr0@y|<&6IDy=fi85s63cQ-?1nRJJla0fCd5{+cDu1?xo3579GI-sp&5b}SyoSCDF4&zq4jJJ}RcUm8TSDAz4TwCa zX}{?j^WC#XCWiXl2!G7>z;F#mz~!I!7ub&2sxJqDl}GwN19gc4C!i1m1Gp<7OUYj6 zHd%%qLtG6{6}Qh_s}kP=pZCu@?44!fsPdowM9QaX9}VC#b>^ZrSyxfs0+a^HEDLo1 z1V6JuZV+qauAq+r{2J6muf%%S)5#{EaJR5O@|t0ek1VzzvTiYstr|sx(SO4%xd*3k zbia7ShHLKhbVBH(=nKt*O{<5#_;p`fxGf_1{8DBCzu4!n;oRG5RdJ zS-05=$tO`#Fj9|9vPBEC+v6Gb3RNCY$1he`=z{Ak zrc58Q8azX#iz>y6uodpM5JSMYp&<$E6N;{|9Q?(<1H`_jU?&>E;IDX0V*hDJk;)wB z*IyzrzO{tshDu?M*{Ts1e#F7qd!2kCFZ(BYL@dYUIcN&kJ#zbn@sqv zkxeG{tkGQGEac+g2{`9909lK6$@^@!yv&7)T(P)D3oHncSeiALL~stwUcVih;LxFB zv;&;+*}h-H{x2n&#`@Q$pl2Z|AwM-+e7;%Btex*RHmcTrqYVbL712y;dhN2hTiHr% zMNEY^6BlzmX3W<>s7nU5IF8q{tGjpvhqcnUZzoUn;A&s5?pC?AX>HD)0CP+dQ)Za; zmW-Uh%NPaT-ip@De%UV4r1OR$1y~S3Gy)YVZOnQ}nY*9NergJ7jRJoqO|Wd|ZHwu* zn#G%l>V40SUD=Lp|GNDR5#D4dxsSSX)nwZ{e-5)z^~9PRPnlPWM2*cM+}+CG z_HtV6M%JDEW6Jd&)tJe;LK=s*zvl=QJ5e@ISgPY^DP_!aIn_U6r|9CKWKEDrp8s0M zHwpzUbB~eI7y6kjc=cgBH@x(YhKREIsFHO>>QduQ5yx?M)Kj(5bu9JK4pGxeNUBD8 z=l3<)-C}KPoI(IhLJV?(muQ!f4AhjCaUT@#t2}O3>fy?6e(v z-i#udniyHcOn4R99_xmt1#h3}fnZeSx&g0*$5SD<8_E6DwHERgR>~Bv zeI6;K6EBhIckQ^Gh9c{}@K7u|Fw>@k?>G!cXYc99~F)Fe#)i`OBw74xQ2PYtI9zF zExZ<#&g{+1hwLp37qiMbA{SqFC`j)xL=iA&0|y%BG}SVVJ!<8SsT8#PDtfxezw6T= ze#W{|F{Ay=j;D*&JA<=N%E5r5W0(d=NHn0Q>YY+m>UHxN2}*;cd<&$Cbe}lsoY_VXV>}J9N(Cq z!rJ$*#IgI?FmQ){o$fd3Qh;vUd#P(Sh%REHG>MxoVn80i6JIibM@~QgjA(}+!l1+Y zZ@R^V+-vbirasN<5R}cSaQ6`;g-KE4w%cZ*2V-IumCpyrURaaB3vSqExc5+YO`I(w zlYgD)XTqqz{H0(7V<*;AXAUU*2Hj~Ke+~Y3GGwNSI|sriI1arGHrw05<_^_o{cM(3 z?dGKL1wSJAC5-&THvX3#{4Zeq8%Z7Q#Y^B+smPhcXAI8Cr)+*8nNFVk#X`SFWBdgU zoW%1KJYN5a+eO16V3hd>*mSRs*SfoSA03K_vof2U7k_+vi*smYq6&W^?NWdupNkIX ztk+s;x9>eL^Za4`hb%H*3zs%I)bht&eG~q7-O%}vG05wU|N32ehmGI;Y(S*Dq+%#| zfKtR=r~Bt5LKj{j!b}xp`Eg#e%tZ3_g7ko`*phOh+X(A1X`l3 zCQffaF`L`o!(4>lG5iC6-c4`jt8C1CNbULW9ef3@yfo`?9%$%QzJ zn_YnR)a4Aa9O-KR;8E!Ft+&Co+U^*jcXfxb`#5LB7Q)YPQi4+)kmC146|KRK` z!`l3wZeb{;#a)XRx8ek+#VPJm+#QNL6nA$E9^BoFyBBwNcS&A;@_){G&ii~g*ZGo! zTuJVm*?ab$S!>Pg0Qc1blE$4o1bNz$&Q2$*z|TdJd3WcK35I#d?Q7c<{t~)dY9dMI zV|W{6D8l>hv}xB5bQrhI?ILs*`!KDM4?)j_dVBaA-Gh6V4-xJ!G0fSh}_6Q#z??aWOZKJEeYy z?MzaT!1(ae?N41s30ZD5w{r9c*sW)D+O9Zkb)T*;>=Q&+oZ}U<^Xnhtv+egWeX02a zeyjf!2A0F2a&qh!rfY(SPyr+}lxnEf>1idH96heTar{Q@rpCN?yB;mA0_D!K9;{Rp zxopILFpH2qDU=HEKFrvbVKE;HCVXOiv>vz4WA*v^ zg69Cij|^8JTlWK)4D%DS;(H&E)Enot`IcMB!;pEeaAVB9K4!kOQYyE7;xn`_ji- zK^t%HZ>yQxrEGj$C+NA5w{|naHIRnaySy38+6A6Bs(UjGL=#k4dcD?s@+L(Y!W4?{ zmtB44b-O)9e{%FR!2^#Ci#mT?2ZU_ z?cw%(`T-cChCEj_k^1bsMCz`PlcWL@nh*ADeZGR-ES59;@HG(UY&tG_v!MP(r>oxm zKj!n?MG!{t^Yz1%>q?z8!W4vIg97M01R*n$nuv-1lx57USnK`6N(mSJSGZiv&SGGp zgZ`^l-p=y?>YY)EG_jqO`p>YJ+xCo*J^b^cKxQ_)0dTd~JIgqpID|x90V$MSYs8TG zg=kv%P6yfCaF~9jC&+!U2ZH-Y5mOS+tC71Qo$36;0pQDvtSl2TiC+uDq-zxbPUDd_ zj*o*Wiho2Mc)4sF>jFs?h0SL*TGY6zJU12DjQR-m4=^8q|9>ff$jsfa14A;V@@9Mb zk4?*6pI^4A_rd6u*|>bX{$Mqfw2SEm!}+t9%blT~RzT4*<<%r!_l~~r>}-MO6}{Un zayjkhK#guo0))>jZCg0*<32SPB0wi5P>-z)n@PY7+KK5}o9g#6PKcf7F6Gq_z!&(7 z&T0~(VA$bxH9{TVP9a=K>|uYsdfD#k2{#3K_cqexpKoV$he&kQ0BxUy-wt>Q;A>vl zz(LD_oB*x{kV{CRp?&{#r+)oc+wWIH%=CmVUNbLmjiUCLrUNoEZ}$OX^gIt6>MHK) zkC&~%BYmohx^`f}x3-nIgTW-b(|#o&{cXytaY5F4X>@f+v?btm-EeR?kHXD*provf zVXSI}g`i>~r-B@bmpD#sF4$pEP>dbi#yHcJf1iR!)dT;#AF&qAKn%0*Snb72IB>Gz zYXH6KoAdg_58bl8X$qbMue*Tv4L^>2noxf|>Bd~5*)p4GtqvAL7C$2oAd}ic{ky%T z{8We3GCi&>^ZmYwvo*WbIKKC34nF|U{13!FfoiQrz_E|mHRKLD{$c||TU4^9BDUVS8l=6uL0ddLBBONN^m z#)PC^SUcD-Vav;&!l%mm=_46EuVfr9{jEjts4wZDPzpZUE9%=URlB?6-LVDYpj3}R zpw$+#A!C9zvk*IycekC)+rQUHjih8_cMc7rUP$J;9VkQp_c{1KQ|P8@(J+y3J#gY1 z?^LnI@!DOFf4av{m?J;?_=RPt)ULfgKBaHxWfX)ep{}l}pe-}mPDJJJ*WGyx)?@`aHa;pE$}w?DT=z4}GL4c*UT5X4J3#8SG#=x< zrnuocxbRh(>X&;TN)qBF=AZWZHbaW-75cE)s3|@V@NTvP!~4NLwM3WfY*$cRzq=PnWoH^YITv=!RN{{2rAxJ^PK;NWMp z1!Mhw;WmAD$o-2rqRBX6`$Z(vd2Do4g*>zHgoeh&M7nZxr@BDrWXcb&{rF|)|K;V` z;Q8JThY_Cirb}AiQj_m}XHqxdfav4SRV`U{Pe^#H1ctTedB5Db+MhWm>t;`vuR+|qxg)cm1#$}bCs{&G?J?K z%1$T)a@N=g&i(Gw>8$s&WH%^s^S1-UD`I?$iUe$=jm7G@ek4a>e2baNMN5J}&tjJ&m=-eg1wyURmn zj##{eI`qj-uBv8BQZtepl_HD>D!pDVQt#{n`rR5DRsJPNL&o3#CqnMXNJ{GWIQres zN1hLXZt6?0=66Yc1)l&L!S{zxZdWm&M;JNc9>{I{X<^chk9dEu%T1Jv#r8j87%5sv zgE$uJdw_{3QtfZOgPF~9G~W8le=qZ2Pg}J8qJ;4@1RVTaLm*5WvNyi={DF(!FgVFb zx`L1gs@#zLpD>z_(e;fyEiG&JGamU-r>Wqp$q*U)zRm{h0OIU`g^L7sbRI;K!)^y} zjg@_;Ks+bGY(XSeGJ>UFE}S2X9Q%GhYAEpc00HRvvlyI(f8=4iVmG8 zCi;7@*OqJ?w6&ugL(bwZvdzhuD5qHMvnmh29Pyfb9~6PP@D%|1yW= z@rS!lmDrd;QgDTgr(Cel;`AsZn0$KJ2L66~+KOL1lnRa2;`m`e#7cAC_!zr$@DU(- zt>{EzmAOelQo0Vf^q4PXpA-%L!}^7lpCSn~N`G3DKjLxSd=?aRd*~@7OEIL=%$J}k zv!q)R{jGU=y=V+6I7g(>c|Gr~{y@boo*Sk03OUX6kXKqH8h`+#k;-PB8fTn{Y_de` z&t=dC5#{5uzkU6t^PLopbQ3#I1*tNG1}(oR{Huzdv36aT&8qY2ksm83&Sm}K=v~h0 z+$q;<>8bScVnZ$OX~LR=EwKn`_VTLUJ>dG0+~JJ7^Sae-)N^9!-o@sTph!^EdVjfj zNRncv!!5!bX~U>B4YLT+qg2Z?MJZ*T6oJik+^nzwOPwoA;DV)R_j*d+3ATQ*NAKLv6)?-{ zTgSv`sk=xiD}EttYKgDXIsr=8N&TXIR3R3i_U`9-0;gDdK{ubSye}^?;TozjIQ;zw zZiyPxXU#;pn;+_>Nsm!4Bp~8S7r)nIoOYFh#qOkmhiNO_=Sl{ypVRl3GjJ&m#LFV6 z&eMyy?hLPs53kCM#603%T~e?4mA3ohoN>!@scRmDj>L?5(fz&hPuTg7W?RLc0}T+u zDSz>cL$32fJ!m-28i-3|zqTHa79sX06~o?inkKY19KVL0wcco*+xYOi!d8no9Q?92 zQ@&@Fqhq&{qKyd-R~7|W3C}wlyn~l2S@rqYci65tZ8x;zxcKx05TAruJZgwGF@^kW zbV>ir*~2`-^uCDxB9u=8_~TR;-bGre04 zvs_V^m-jCl@c}o}*E6*ya6j{562IY}gtaqwUQ^3GswjzS!`T&k);~Tds8o@>;rZV4 z`UMkb7^&iwaA$Zk?BQ5D_Mvc}?~I!Zaa53#L8Wb}@9!bfd#3lQ3J&$`MdVEmFc@#` zycAx2oWv8pcB>lK<<i!)VI5OH)=7XMC^q$TCea!ni!SFz2K-bd*r` zg>H~PPwd;|ePD37-2}3Mz1u)$XEyAtAc>Bk?fBx0ak>0I27=%+Fy`>%xcgxNnB-ia zvCrukoY3I+s9Jq~JmXWc7m~#D__Spd9tr^mV;7KhGiCI(g9ntP09FZ2T%eM}E=~ z8O|7y{-nAe_+pjkZvB<&;geJG?&!{d4@eanVt}EUZ1#KQ0CRL97sGwiSAB_BaUZ6dimc@@wC+`=BjU}Y)f}&` zZ#tzY(tfOnoGeNOzIb}&xlB-W~L~6m7})@ z5b)aL6A47N9{zBDXmt@=Q&duH^``a!vRo*(bG^9iDUVNlkpb(s+?D{2mM!tLs0fVh zM^S&P51nbD3pQbx#n>n^G%a0r9^EVcs2{pDbX1l$H4I%?vB~&)`~vc?;xw6;Rj~A3 zp94t3V`cdR+bFT{aIP zhiBfus82n$b$hs#W-%HFKtcwZbSl|JF6>22sefE$zrB7g^=D0)xgy z?!AS5PR#|6{mJ=Q-bVwGCb-p_2?*0tStB{MPa@^I5AJM@L3?=y0xwst@Q*XR@f?mv zheIC&VQpAC{FG`Z5~-z_eZ%6p0yKS191h$=d3Wf9 z34|wufaFS-@NX-8HPhGs@yzE`jDkD$Wb_L|QyIxa_o!HVRcyZE0}-~ekht}5gn8aG zNxKyLSj_3n*3N|hLvw;`e||yfhZJA`is1$q>GF9q(^+l* z?_tv$6o5l*w(+fm@gx$2FpL+e?i)^D>PuHA z%?)j~wL!03B1T*Avr^IyUjSP=4~hyX>H>Pk_@Wy5?vSjWzByh={eFk>dFRE2aChJ* zY@gwK#?GO=%Y&T|Tv9h6Snx%O=UP1AmQ--Ok!>PO(KxKM!gT`jl3<00MdvBnYGwS2 z3V8rdkRV$od4Q=q=HxxcsjFZrYee9$f>u$^AAvB9bY&(_UzOJi7lOJulTDqNxs+D9 z(?C)86N5>|-_7&Too-hc9ahP9mG*rqw))MPayMULG&_H1(PNU(San8^ZN+Dl>|mXt|mr%I!)e= zrSg9kZy*}3GH#Fj+e}bjY2bStGY6dv&(W>bu9ZD2gnE^1yM*raicLS;eY?c&iBxG1 zbZn{>=z6)1{2;}?0=Wl%!JiA@!~Ey_dW&{V?!o!ST@ct*(&_GF z_@@muR;$D{9-TRNH)33eixei@w+_dDf3YhZ0gO?odeK;bINfj+pQi`$S$nRn=nWa~ zYL2VcUxVhNCye53^;pX(WT&L#yE*(PEcoZ5Z;x$eFSupVt|Q0@#x_O{j`qg-)_=dW zHLyTNVCN)dCjI*rA0Lylo1HO}x}1T9v7sZAl9PesUw=zl>zf)gshJx&nvrsIGBZgS zo12DiVY0BeLo+5izxP%?{+iz03&vbesEMgI`QYU%_ zzim#6Lc*|li|`1vX%(X$PtS;MolT>!&3<~eU%SUHr>B}`xtZKK)Yh`K^dva_CkcTI zkj5LYs#0=^o~;9R;kg=i&~1Mdydsne?lmW)MS*~LF8cq)HIOj+i^jaWC4KsCrjpDX z?1w)fsUTt-Q?YiSrkWACQJPpP1OQ=$j~R^ z`?FHb;0=I;)H;hk>L_y^Aa1pSxig|>xu)@<&K-^#aA%`3U2 zP-T!bi}=}aIzPM8Km1K*^iGOP^50=suz^bTAR`;61K>T1 zajKgf2|xQJ9Yc`8y4z@4?blU_Utj*fC7NrD28uR~_kzNij-TXI^f8U`ACC|OPx=|H z({1(eS$$?x?)_@5(pzHfs2n0R4~S%+U>Vu^$n^NKHpTXGmowO35KjvBonEw67Ko%i z?ch&cHyP6|E1CbP@|%5g;@+=coYyL~T`9-Dg>!1&JxqsNhCC1Gg#+w& z;^;pI!L((D_F>a~pt3Xthxw3@TkL*8rI%{Ba5yI13|<|S<{+8U7L0au{1K{)fNDPz zf1GvH_y>o)6|YclJbZzqrJ=p-hRCL0D^2(FRsZ-%m_d!phBuB^kqzss$mksXT8pL# zAD0};I!VVj^{x7lzG6;8m5T{+nZ-*4_FY)K`x^18j{V9Z0hiAFlAOSlCL1@8=}7V` z2+hbsL^zY+CNK#XoNDs>j5+JJe0jLtWhBsHb&O;^Lw8r8q~}_b?|u|B5W)_3UFsP( z05F*`fnyPlB}j8TQE;Qb`#E!*-kcNP1k)z*_W*@dBEeIbJvlgU!8XW4_fJqzdFK>& zhr?gGMJB4#%%)1q$+R)R5S$)pnRN4S^?cr2(OO;5T1|2KWI|4g&l+LMTy5`^bgr#upjOj zymY%r(d3Mc#V2*q8+EBy4Cmk%v3$*E)34pKJ`opo6+*bLWy94MeX~{yHxso@4<{AO zf5v@x94MYN5=v++RCErGjzu(lWWmtj54jbq69B`8GZ#G2@YF@R(Xln3;*3@QVoEu+ zk3rG#@XwQdoQ=ma!A~ho0IpX>>A4Sg@wbpgii?QB5Ix)9UvqRNFyyMzzXP$Z#`d)t-yf9 zKmqRRVK@~<+<{r8FdL_rGUm|woALg8<63y7<=>LY>hbMIyO1lty=L;qeuH-YHl*Q_ z_VrOWYV;S0B$!eo1J-Z$~La8ucp22SMSCTh3feps~W>QELv_QZ$3Rub~=ovX^%(AxPe*g{jDldEK3Dwkz0@Ytyb z&6-pxW1ct6WnNF>as=nfh_Mt2c9B#;|IOBhRHC`V(`gEg8dx$VNjEvu_3I-ISZUdP z`F@b0dRp+__`;s+u(AHskpr`eDPx))Kkvvz)c%#Ps{ejaKGixub49={>a{)KVYvgB zslNCC-zJ5zqtIaW(}=?9^34#7wc#c0q8(HEcpua!db`B*`f>Yw*Q`%kBf5S*$4UB4 zASVyaCT|>jyRZd9)^=~=LTgb5*53h)j_|=HvDajLp$)C`PoKA*$DMxy3~Rh8k8;g< zUb8174sJ4a&Y<@UTQ#}~t%UDkbc{62W_-+v zRv&{Jo=&#@Wn1#<8o#MW6QG2ktvXW$Fckuz$D0S5daekc%DmIw(>;vz?)cP(rVC;* z{n>UnX!C4UtBf;qXv#PeLTnt{GqsMPZK-Q zu&KQlw?eNt2K(RpYfeb-P_>9}hSOv*R_#u5PIgVQErv0z zsW(rGznuml)KG zob4@LsRnDzq$T@S8bu-9AC_q1z(nExq5}M6VEt!K|yuQ3tDylD)0Algje&C05KTrm&Z<#`7BC71c_WWMVAJ(I``cT!J_yx_qJN@Nx|=}%(zsy9$#816a3ocW@$OO=dK=~ifPQ{caGHgl zPSn&g@Pr(cb*Z`;9$c$Ng8IXW*4>!3qpW2FOfU+0Ki1XQ*>q%TzR>LL5wKmy(Y+-b zY`h~5>fIptCRG4*5^sLo3U3Vm{D6`Hum3H8<@a50iMf8zV)AEILO5A&$ug_vGgGk> zC6;cBDcfZ>f4_Iq)E+61RAnuf_?|p$GSSZ4-{>}%z%A?7J=dlaSy@%LLX2k3nK@F@ zS0mMaGxyf%1lT6qN7h4f5}j*MC8$dX4)QKp4)yT9F4O?fSPH%XH?zB#0c-R~kSQFS zKT*prBUF7O=NCD{VY$!xEp?|RsBAwlYm3#G}w8~zGlqdO0Jm9;4al+!G z=D<`(-TbGdteMQs>E>frV>%xA5?&$WBc{k+fLvv>Dl*JYY5u7F2B`~Qn%Dc<6Cv_^2A1zSA#ztt1EVSgDU$#wK{|m3z}9hN3i4d zxGBQY7lGpmtai+$&qZIxbbDulP2m8-Q)VVxo2``up)UezE8~$$VxXdbxRC=tz8-zb z4vWwFnR`YKDr_9sm@+EY5S9&2C z0+aL8S9Ukz3QA`eytIp?59D4M27XF3Nj5{dGUw^_hmRvma?|u@9ZF|1jSwE1{!>P$ z%QR|>w?}0WUs%M;TdYBw_h`xji9ki_y3gi1x)Q8Lruo24ezK|-ZaplHW)>v-;a?8o zUzX~>J+TbN?7#1|UMB#AG(r+hpZjbUv3boeG(+$L2KlVy2|wGCf{6xSDuP>J42pg$ zYTs17c5Sx23K)Ep8zjUoHUGF?KZi3~o#Y7FVJco#U*0(UcD79+cr2nAaSADeHcr0{ z^*<6@DED}mcJ&hZ-2T{-3Y}xi@FH~o-NIAwsj9NIhqSmR|Ls~GG{VSP=W~9h_3u27 zdyZ;Gmh*aLi%1NRR+@g19+ZY0&DgA+H(DWlnBVw%S|Kn41^M(rN(5OHYmpCSVwKD4 zycGO1^Apn<^qe$Fjn?y~Db+knB?_NzDS5-?RdOz7P3wn*m%T51a2{P}{;OlYlK>p= zR1Zqd4nePl#?>70L={HsI7o|@>U@wb{%glY7lNV5J{4mRvQXSaf_Lv1j%3^u?a^r) zA8s|vn)Uu`mysVK*5&Iui^*2mJ^6B?+N#g7Usu7a-|I-e=r0tdESm6J!IJtkG!tfi z)Wa8QXOT`Vk9Dj>Rwn!oJl;VcOix9_VR4(qoC7L6)*P)dE+Ypd`6iU@wH`cvr+?T|eYlx+ww3adJOLpQ zm<7!i8xaO8&Le)WBuB8&$9-}&IzIow2`eljc%S;=fN@6{L0hjh4PVUowBzZ;D=+)V zwFtwD&fI;NG_)uO5xwPgr)9H@R!jUC{Z*l|5&WaZ=~e9=8iYL4@R(@W#r<33-vQW*`QDF2xV**-LGa|IX; z@Rd{ZzQ4S13K<%=g;EjE?Z`{N@@rKadNz|;&V$vNxp6zgb9Zi7cjTXn8@;wKtI}54 z*iKw;2YIBy7M?M}Y`EbBJL4qqSn1(+3n};iq%m$cJ8%;D#&Gf-5@Ge~Hd4q5b!aM? zO{lo>ixDto2<{txs(C4^6P@vV6jSZLaxCu12O+tNAZr(C0ft#Qx3@;*)N30P{z@mO z+;Gy*Xu=m%_N4+l2huHuZL$U@X99Hn_^I&5Gix`Gt^rODdF-S`%|4%JbmVetib&Jx zU1O~(#eXotGiN#Okh?Z3RPr;xpZ}UWS~7{fw6tpB346;Od;Y*OUFwD+Q$7aYV|CcR zlc^l9WHARyDenYP5qT)fj+(w5k43-}N>3&0p2lKvb*GkX5T9<`ltY28)YeF~P>&#nH1%~AqL31t3ahv%tE$%;w zu{aW^kEeroV(Y>SMl*iRDax#IHP#>AaU;Ne$7d+Hd1;C}Zi+o(!8255@=uw0@po8t zYe;)xqQxb`voKILj1O7g(!*-XDrlDSaqH@^D(nug^aA;;0^Go8-Y?eH-%jd)Ot(UU zO2!iAWRg|&d#w$&Z>7p|=)xoP*KUtvoveIY<32}Zab(FfBL=T{KDfSHW)88XDwyXm z-xNYzJ#M=RTDJWvJu3uz$k#11SFZC`?brMP`0=8{)!>{R&R0Avbb3A)q) zvN|2Y+ecHW07I-IkEG6?6aMIApeEDGW8h?}czOhHUxEMe2Ghy89y+>BWsd!gMv zp8^`x1ctT=YJ;a%Z;S9=0L7bP4&F?jDpMk!?VHx%4lGQZc0fLXwcf8W=IZt9h9kwW zsFc}X`EfSyeZ^*R*KRuyB($mj$9xu}$B&QNbKoNbUNmD!R0=ye71&5d{NdXd+Numk zA}wUU`!+0ksRfraTg?p~@@`(*eH;1*M^;U)Uob;xei;p8A|B_H;lSU=tAdwW8YmSE z9&7KNsM>Teej|-M!ev!D5G<}fP`4`b!SsMA&loK}aEN~nxMrL+#+Tz>rfBL++OOsU zA+Y0=WnrYaRV&yT#^${<;bXJwv6S~Ql{1p})t0xP%cPQjtD1_&?CBfLCQ7rYIL#p7 zU$#CwAE*X)Q>Hg-VibI~#tQMEoSrb~7bZg)xA97;r&t?f8q6X&#Kyl6R7pc4;$+eL zcqjE@kF%a^yGP8$!9zLQQ?nnhioj;HKtxeqlP0yLB=5p=Bl>d&0*|7TQqWcDo|6L%F^S!Vrkye)|<+ut5c)QKXT+3RzH#Ieqm4>tJ>H_g-n1X z8opD@VcQ6+iDcTRbrR`aI2ye z+p(>gI0+_Kru6iMGs!3tPqxuCnF`~)ummZ3hyTnAT+QbtW}nKI1ksIAzV#tv%dbOQ z8hbW+$d%_!=$8VFqbfNd@T-07THa2de%IYi{c$X_nOU{?v)DU^Ok~^zZlPV%T2X=&m`)8r1U+9kLwnB{!;g|`n)8XsL=d{zm^||(!D9*1J&}H zzZ^9o_4foaj{FH5(%ZN}we1%<@As8SyiT&;Za`J0^}_ zOMtXreh+_ooyh`Z*W1Z-p&m)Q$v8Nl41Jex9ho^mY|EL@ak~aCO<4CbENruB#78cH znn|-v@eUceqgQ8!!(~tTo=d8)JQMuMwDZ391hKxYI*S8j+U+fl(>nv%1=ox3Z{jHA z?uOVXs?p~DnhkLq<{p6U0)qX-wrJ&F{Yur&{HDZuF8u1FsNi(vdnAqZF+LT<5*OD= zR~SX(BQjW=1;f_~4&M^t(k-GwS+-54RaJzqi_VygEoY?Wa zc~E#P#?$Omf0jc9HWp}0CCQWGaGD0~{(ClXC?Z(M3C53;vgf}6Aaf#}`^-YtK_@N# z?^RS;YBf9>bL!dYCriB4ZPZAYM$rkygHSBF8bR+AjINPZgguiW8aMdLKuKklL>g=j zjm&cM#!iHUeRa$G3mEf-D({F6b)_tT=mUeCmf&&FzLyQ^$`|x^p~5_flKznuAo-N3 zZ?-pVbqv^S#}V}BJh(7=ki_x>b!hr}0JE};lS!F?B6L@euEvoFAi!1UkNZ&+sNUE# ziBY>1Y;|Zl>3Ds=0Z%kiH8kcMglrrXqg)d$Y}U>t_*6O4eqGoiNorQ+7EJ4GHxn-7 z5o-UCD|R=Qh~kowxxW)AU;RVwh9%dPsIflLy!I}OscLf)^qHxYXf?*G zUj3D?Z%#cfFfz@>Q1JlNbZBy{qt%H_h*^cNYNTb)Im%pejmx=p+AX@Jg zjkjuj)T!J#6dv>aubsb4hulhS{lmuCkhGkfoNzL}PP(pBghGteEV^iVSR3eTs~ZqW zcV|X^w*Ren{_&wng5TUbRQR_Osajz~|8hpGzzEP~ka8X}MOEthL?7K1*PIZ0TZH4v z1(K(L-w#+RQMPwqV;{9`v#Wt=EsWN`CK|T{NoMm~L?UxT*SiZFZj5WHI-?4!j7HuB zc6BA~!x$h8k~E1`#1S+DK6dwF65sC&33MiR%j5L*RPXx^LDpowIg; zaNSuO_@se)L*(p6u8i=*p$)jtxk6lsR1BKmY~PoO+Y!W#kY}DByEE_g(`{W z1*A0Mol$t;E_!nF^-s%vDZkld5ox3q4Vbj^bEHh}WdMtD7|X{s!~?ecfC54vDJToM z3b0!VgaO>W`-O_owcSiFr8U#$J!NOLyR?Qpft)suh~V*Xt#D%-Z1&w;|1P`@c+*CK z79P>7pT%qxuTX#z8u6|~ik1ak_T`{?pwzt$^XK{f!_bS}dI2}!6EmA?Xo++@0xNrL zD{dUwsH^&o;;OHn4DA4d?0TcMjdDt4X(Oc|*{eHDk$_n#V=O-F2})Isqo2zk-!O9c zJtBLvxRL1YO#44n1K9O`!{JhSrZT!I4yfzU4AKuaQGP#Xw>XN~gt`eI(L07bDrV8; z?v@*|^1j2;BZ^1Y@4-HJZc?iP5cTzE5_LKRC9=pO19vQf3qkVTFG0t-0}hSSZFl%> zkJg2tj`(YrBlcuH<6^wxs|VRlskr;~7hkX4E2gI6HkZTt@v)LQ0B z2B)FP!%o+DP5#V;KZJ^sJ@0O5yxAca)Mw52tFyl;uvBIn`B02N9Wwo3K4duBeGUJu zqtLs!`TW~;BzBqwd$E}~REj{tskZHg#>ISa!$h>pLTI~eh2C3SNaLR7fV%cOS?X86 zPsJB2-DbeBg^r2027gCx<<(q{ZduDo#dW6Io^Et#p_%KCmlkaHtn@Nr4@tOm^p1OX z%4&Zwfr7@Q&ewD@4o#&V6fDeVnO|=yE9}j!AK5^tz!FrJk@V>$7V%mj?XcgW>4^_^ zqzM&q$8$@pE6;QDma|Pp4P7KY9y#M)dF!2OQ={w;VYka>UF1*zN|1vg0JpuE>;>x6 z8xYdDQYMvHtT+`NgLPIgjAp~aZbna@QLAenRra@c3K)c zC(h71U~Je&GVjcTBXA8$C^jMrek;rQz%$i4o-_-m{byQU#ojRZR-{ELCL~w#Z4w3Ijg@+-uoU2YpmU(VHg?(1`f1p7jC6L zc6}LYE9(g2COa{Jq0u&2N2QKN^=1pIyY#XUi$)`&PgMp?{h34?dcvR z2|30n|Gl3tCCd0U!COMx>ar4#UW#n~TeM;S5KjQNlMB-5b z&!@9H&GdFZ&VStxD*y%~*FS$4?tSk0*VPwYnCS@&R*3-o|E(L?ze;R0m?%DKQk&;B zF7ta{Y*Vhco_LAfy1eVY;3d6RQ%#G#J9e+SGcP!IaB1|K&7h|p-ZWC&!q>>Z$}s(* zQ<=tdhUEJ!mVUdG*TXuLJ>S$DBpu}jpZ;hK0ZJ}`LQbDo4wI@hr)1HUn*e^ofVi-KU9qhFJ7PA!M&fZXK&4|b&;FA z&noFK<rkjcKyReOLpp}%@n;V@nEJot+y-?DSOP0 z%MxLJs>Mf#o{INZdnFmSOASdWpTrH@JotiIK8@0wri=9cg>*{d70$8@_NQm9)U0jg zH;3P>^jl~EB4MxQZ*$4UW1Y%^!UV=|CD3n3GhR0mK*KQyzikblUFU>Ub90lc{7?Nu zTV3`ZIFy4^d;CD*Z+J!e$^;&;DO+1l+{3dGTN32prx(rLnbpdF}^C zx1@4`7yK=|vkYW4U}rHVR4jh#oqMK9L`{gcQ%3?<(g*fG39EduIUrhvzTz|7JO)Fy zFCD#dwHCBYFKlCeuYtzZ{jxfD14LVPpdi4#E=OZ2*Mp(b-2Ds3;{I8(G6&qfmHA&v zj+qWi-E;nR?>LirAEvnF#&u8yYa)Ib$#&RM*GK_ME@4nQ6Vtm5&9-vBSpo`^y1iCb zbAHt8pizFvzj6;#0gk^>t>>a~U$RJ@6#P6YTOU|D&SrcJ$g*(1QWA%BYMubZDwKKe z+iL9#(b`@n>{}yo41(!APRdJsGcN2xim46K0*_)AW-aXtjOc@)xR)Legmu8LLbd(* zlmq6Rs5tEq=R2%nv0npkb4*AfN%kSOzkz-#N!xzXu-t9&id{)u=1A$ zLvr<&zH}mX#dAr;!Tbo!yNuVS&HA-DgK>|;;h#p1zK+5ABMQH1lcT7!mYS8O>kAF{ zu`DJHWJJOBdtRE-K{F538u5Ap4jPUCx7pcfVD-``B76)Nd}N)jO&7nkmjy|&?kV+O zyZOf(7HDnz1q*mz*tfxjuoY~d+v4rd8vNv_^E%0ssD>T7T|B$aYzJ^e#HG{FF@0*p zu9T)E&vA1x7l8j=j$t98J7;I(sY7J;Wjr!6WtSV$=tb!lr5r;S9;I*nf5JeA{7fKg zwy8DVn=R7|_j@@$wIqM~ku1QsO!!LHUL@G-*=48|%^iu1`JorNgWU=3DB?oDUZfw- zEOhKy!6{@O6LSB5?MKKsMW6`N`xNiOFNhAs{}cKmL)dMwEtNetk?-&CcvjHwI=o2r z`?a1gx{q5WyISuyp=&fe(vKjV5IZ|6P9vn@9!o+aW+A3Qfo6F6X4I3MM%GDH*(7q0 z$MWcSgqC1&M-_lQnn~)ECm$W`g{|<$KC|qwFt6Sb&l1-Bp=w@wHP8Nac!!!zH9-ua zlH$0uv0UYjz7w9twq_0c_6jWkmU)v$ENGv-$s-=D6;d{nPTh#~NS1*hD9b`#dMG(K!(6GAAH-HM;*MMg;`VXdKU%(iiSAPO; z*2+5)A(|LwuoNsGf!&}}8z};yNF7DeCwxU!mxZID=x^b6xx^U3RSOTFX>QpL|DN-|Z?54x%L4T@C&F!kK=?oN{(!*N4Y36E4JOd|57cl6_#0OU&iETwpg*ow?S%4s$Hb`; z1XTw$8AD>!o%`@kOR&~EBl<9yfY_=#!ti$ER+W5;|8nHE_A-C_Hrp!vm)JA)e?cD! z#`xE=?7lT)PoENs-HU+*H*L&}sdT7*0LRc)EIfjPWKYSb77mh(Ddmy zl7>f?em>B1lL*TC{QTqx(8w~EY5w%N>VOiK6C$mRQwZ_7(M|T)mvM^!I;nnL@qzxK z5LaeY*g)96Gxl30uoVSGu?2TJ;q+;7^tfeXrW%bBdwG)A@2b7qHP1d_R2WMY$H_`! zl_mjN&*#B#CUF6h2EsysD&pNX*l5YmKUB@_u`4DByix>;V*RyCxv59MrVuhlv4Rw9 zGxIJ{f4VFW*a@AsP2Ad1W!Z+=DVSDQ6SUZf(V8({WY13cwLV{+HTuat;%jAQO+bqp zHZe&Hx^VRT1s~KaL~D|Nd;3sj?T~VYqj4xJd%o&wzDl8Lgra+vuVz!6+S|Mtx3k&m z!})zH60D#~e_sFR3tP1j-pfsvN7N2yv6Jk~jmfQcO;0yB8#!1+p6ld*3xJX{FE0 z2rBXZX9ne4VI9>hq)7qUldV!}Zq9;B2^!l=Mk6C$YYQ!?(e-pEX=bu4sg&}L2k0c4K&w1&N?@DCq#fTnVoaq7zg#<|Vbhz7FhM9rke^J8=b);tB2rfmSDecG=|oD$*%=XD z{u1T?nCR;bTzcsuzwj`f@aTij-8f%aao+Pb1W}^z`{~xR<$6sBEG2w6gP&DyNC?(@ zEO~TerQgVCQ;OjpqQ&J;VXX~jtBEBGs%YW4A@APN4M&|k6HBD%p0z{2+Zk_vU$>CO z2Lwfr?Tlw|n|6D$-LK$dsKhr{N7CF+t}v!%I?b1XstE1b9|>5~Sw^#xzmD}j8Q~vE z*dP6oXquwOhMloCZ;vjYLQU_mj?~R!_*yB%(7X09am0>eNDScYm9EQb-0mHZv;iVn zpxUM0T|6zsveBR08 zycZ?-Lw@x!R&L!IdDf!vd zz=hhL0bY z7*Vu5@){s2(=8DS5jAdE_L<)=Fv%=JmRt4L;u49|8x{$UH(Ez&vLcsc)~%xIq4|Hs^0N442>?c!~D z3N2dN;#Q!z6(}xg(c~pj`n=EkoprwVTkHJs z`>l2EKSD_E%sn%EW?y?>d(R9bv7@MX8>Z#4c*#lq$unKo>S;__9b;26H_Df+39kH& zO+*6B_F=4_Ik)VMeRDh=gKmEl`SMMP(INyjFUNexp@foHhJn)ed7tNghGKPRe0n+_ z|NSIhRS9#y96C0oUNa|ov_oo~#o(#f`3d296)*7`c`79U#aX5Lw@i?@Y7xo3oVN9d zVD@93mWz9<67v-@7nbatu$BS{|Di~v^d;v<2ki?Rv^zI#d7v_KH#dbjYbNnXpEXb! zp>wV{f7l_{kNiE?3ne)MC6irWxoV;>EjvDR%z;g}(YLRbwBz-tJ^)W#tTmFH+O%D1 z(mpYZ0c|^hL5ds(6mhBxR2~|NXB&H$TQ8dn`lhA`$0r5u-SlJ*#U|Aef_G>L;rvfB zyBzxL{VVS5$!A_{rZGB7nel5mVS{?6FU$6(2FRV>s5VW9PyTXxTZ3cPXVd-LUBBaV zSGu3B15|)&u*;&&pEpH$Hl105EsSV<`aV%t0?05ksU1hw=EG=#npdh+1Xp?D(;B0( z{4w6SZfZ+N*n~aFoz$rNN8so&KZ{(55DbN>#TNbfIEq}Vb>E;FDw0BlI`QC z*&|lDUX`h@C)#eAOdAh-J=OJ`_hRED%K_!PZ+K3;=uZXVkA5OJ=^vFDddp<3wi5v*ysB*17 zg&+0g9~RIUMm@d+)(a4x*R;t1MZX_d0X*Dq_)u+@y-&W^YeD<8ID%PEOTK;FHESUv z{9%C4Xtzx~Ty^!?P9x0Ang5jKuRmpHoI9GO5;%W}tIIO^!u@hd&qA8@qa)yQ#+6x^ zv%D^Dj;*+wd00uZMJqllQ{gI%U;|h0HhKa>xEiZjqqGssdnBBqB%bzddQ>yHqDLyI zF%bXh`jD%DVjod?1RANwythZmM)BtmuDHpsoS=yn7$@?8YE6vVxWOq2T6}KYZna$9 zIli=>tLmo-URj?T;3K{ZW2UDqLWbU3rSw*MtR$T8GIK5|fl<+meN?JshrDsOiAS-= zXP(%J>hClWZLwLFdoGezT4RmRj9Hm5{$9u;fsHt?{@yqBe!|&y(njyb zjD1u>ZBOu1Cc%3d@+HO|Esuh~YIAx@exIhU)^2|0r%=;N7bjcgpFLA$vAo^S2xtxn z8Xt52hAYf!imvLINTuCifNJ(8_SKhe5>3W@6TVrGOa&tW#_LTh9ke4^w6fYmGve6x zY_4>pNH`JlV7^fQ(4DhvnyvS^v*a-wsE?TYPK2`y4Xo9FRgOx3TE+ zKLULBJ#qxPVBdXoH_aINhNV4LIVeY>sP%K+}ir?8u)(o+#&sq{AxnpfL03l5+7dBv4U(lbU4p)S} zPC$Qd*b?l{4vxQnN4%IHD^DFYSHAXrfb>+jna~rX%#Xa z0O9+UNL6U3#uM!H+2t#Pp3QH&eKThjvgyrgp%(E$r^Hzxc{UN@4$QHyl5*F~H5GJ6 zMURp>@e~tIL?MkdE>ex3798ccsS%ZfHF_`pf^BoL49U2Up&tGS7D^W zQ%1s)nV3i&mW-DcIw9jA414a}^q;?8vuD^?;)56}1N$d7+oVFUlIiSwL$(B$LuNNF ztO{~@E!M4tB-W+=U_KX7<%kuqi z|FR-hZe0Qh_T?xRPZ|Mm8tZ~y+=?it(?ZZ^_a{V~*2 zu3DnQ?r`;n;U*31N3qBU+6M6OGOn{jetOrsI*Xp7V;v`G;*skd*f8$ag}0Ef>r7&hO=vyf(h(T=Zuz#!WC#j;TLUHzJbaU3}b zdW$%QV(;E%qT@Z8w-Q6W@2CnhnRo_^j=u>Mp{BOS*iW!^mvw7g8sjTW)6_}eG^orm z+?mX186>*7r1ZV1(?=dE`sk5m7(3I1aa65_=2aZ2@Y;RtahS+m{T&_>Jq|ONEkd_A_c)Wyim%nBJ?%7J+`k=4cHum8ZN3ZxRx}pcc1C-JXA&E z-G(aFCx?~D`(n#4pqAGsF+x~f#q83iR@ARBQUXnqi(+L2@?Fcv=n2b&&)?M7Bxz26 z{0Se_QJQLkVDJY25MD)YL6zToBBRW9HOT#ivvI&;j$eFH+Fg`PXId-f)xM!vYM~sN zHeG?$&Wrk5Xmn2zi?#n^mQ_~m%_QT*`Lr~XkXQGU)&c49=_9FL+YD^l5;l1VUf~U1 z@r#i{6@KWzRQ%~r60Cw_8B(XMw^;lbCczw+)Xk;&<9*chIx?Ig{7o>%N^al@2R~gva%qQUjc*w18DS1NGKwN928fc+Eqs-)AlL4* zJqUmMWq=2xM0FX@IHK&23a~g&k*>ij&C-9UpNIb3zyRy?(h*ZQe&j3v zICU)@E=Bfxpy|0pK-V3{FR0lwMHStZzqqnKg;$>|KDU&L&|2N(`(-uI;UrwtBpC9` zu;NQrn1UClhzU=&s|OrD>t1@cRHH7FRhkN>)=hv9;RxK+piCNE-iyT*viv}o8s+Mn zI@4~f5Vkn9Mv=w~-I_CpI*l0MtMGo_>v7W3?>Sbb5qZkPjS7eAx6tyIwJujNq89GG zUU#3Jiyp5^wT6t)Rg%Tsp4OJ4xaOfu_j{xgizcw+_v4#9{k1Yr}(4Zt^jQz=U?L8)91eRFlmGF9fyq7V`%y8V1$V#(TTs(6=wV&iB5%b4f4@O#~@gMJH6oy2to~j^;N>SPC{8zFoy%jv!6%Gf@jGLRhu65_c73GB`M5M=py? zzlo>_)kEhfKBQAc9Nvhss^0D4;4K`1k*jLA4?PD4o!=+OfLbE;wR;HA=l2Ho#j)~P zKNCKx;zLJqLAbvq0?a6mW#`|hR2D4q)L+hO5sq;QeE{qek+Fz6YcPABSJ&Oc;9H$< zMsY0d@6NQ%=)7;l>mEn7ZJ_&=%WIntc&R)nxT|{hcRt3;j&DN^D3imb@H8Ak!yc@s zY)^jL@}hZ*Vk^${z=fucWQBF)UVdzKQqi; z==~^SOLQI(asOqj{Gw^wmVYdi!kT_}6~ymznN*|g=hxLoz;ng#%?Ld7_)Mc+ptmloCGAmD0_3HPPtfXUCH-ww4 z&d>Frkt+%muUwF0MNC`oYQmNnh#dCfAwpUOXBQmCK|Ji~6w*4#o~j+-#X!=Y&XdIz zL`_cJ#)B`iO^v*(FrDA>ooM$%R>dGxm;~;IqXI?sOzU^`8kgH0y+p zRu&h$@U+HtD8B0D4J}q*Xn&s!bRs)^kW+Z6cmcU$nRs_)h)_s~suJa-dbs>TXZPi6 z=dIkY$iV)Cn?tJ=^XLcmufSz5yWSdl+GQ-Rwb$}k z-jJ{jVe%bS>21rGVEnFi(lp<~ZQ562Dry$ltmOz$hJQ{WVT6BE_GB{(YYUTHX-a{Z zex4)d+;7K-N^WTx@0%Q&F$}VDdYtmOeW&24RO$4WSp~nP*r->J=wr(kAGfHKfZXYe z@#|-52I*2;W9aEl(ydP_D# zqM<0N3pdJjj=bl&xLtS9TYQACk_p+z_M(Es3W6P3(zAx4vxv=@( zTn9JYs-Kvu_*>Y|BetcNtij<$o9G_-SKI#I3!)!M2fECY>A=42?W zlZVVvU4MQr7+~vTsIO&i7W+E4c9>Uz`Rzz!zbxm~bwR;lSOZJwcEAX}IzVQhV@YH- zOR(DsHVn|8R=z2yRRcuae>8~cz?g4zh=$I1|*fnp!tV5!@4b- zDmu7As7P_X1BNM2f?r;prM1AZt4gYCxsOj6Liz?jb_iIW*Muc7HP0EOCwI80V5hMO z;pq8jDKu4-(r2c6*nG8EhzC67e*(|_~JZtF;XLRAkR(7Ot=@6I|K5ZhtEIJJk>|r zgm9BTeEIbfm2dcG+7N^96Ljn8W-ve?x^G!}9Z9WG z>TRAG-1)FMpr4%Kbve-BmN-p z-s348OLgJhf^9X=QOAYl*Vvo>Jj7!7uf_M};hva;-}Jge3k)eBM!cZy6ftf!a=Q@< zF{L&TOY1lz{PW%8vTnP%i@K&;{a^g_4^MfAY0kp0&XVlfpS86Y=7)aIjVQ6?7vNu& zAN$pVmjRa4Okk(BoRPHUq3~Ry-O#*d8-I_cG|^^YD+*|z|RH>`A z=H18;zLX#%cAWp0PXA>q6fx)yZ5&LNSNdoM8{hXVb@Bu~*&(4|lZ-C!Cw%o2;-P|* zkoCBHgKNv73iS`m`QveE7oBB*1^}nX zv^cqPYt?g7wXS_E5uErQ`kQ78fY*AtHk&S7^+jr*ov%BGi{+&1QL&5QS@O{kKy8l zRW6-aX&Z_&O)AUSj1~GrOw9)@hU~+GWf%@wS>+b@jhLsYX>r?vUXa{oNwSBq%+S-w zw!KTwKcVcod_rlqC*MS<$R+!`4N3qxF^ZTHOJeLKR+u*#4E%}B9ktCS%v5NJ8);m4 zC;sy}4~BOTp3G5CnPkkqlG!epiNzUhj=D$3IqaE?R;;%V6o>gI=A%lGM6LfuvX#sz zLQh{k+k~8ZhIO5()6Lh0l!H&&z|8DNfd)%hr`jSs>BhYs;lg;d;X`v?&B|7!crL#l zrUovw9I%hSi_o+^lB$1a-^$jM<#jIzKfq9{wmb~r58NC1s6A0e zdBSYe3|u$PkP6D-V`@Aq4%)u!+2&>d zD>{Qf&vTn+cFg!WRcinhb1|}IPR{E1TPoet3yI~>E_YGPML1_EkKL&hGlmYj!FJnf zjE0H0e9|W(0NV~O4Byf#28<|bsM>Y*3rp`*D`?Fr@EfCoUdkJ4w|Ab0ES)7P%BK)W z(no(6J&WtjTsuco@|?mJ5TE@oxzrt@r|6rtxh;Ouvi~JjPN&n}zV`%AJoc`1Xy@+2 zP8cR{PAO_Vb5E(xAkKWwxu$`?p{jh!Wt`R+9{LM)-UJ^{`V1Ap+sPk%7IjuQ{j7Fz zd6QHx^zAe_zX8ZwwY+#|1R*;BMs-^kI>E$A9O(239Uw4zi|2NxU9QwD{81 z;vTY-gT9&`BDV6Z>gto~HNjN0V;^_6znKyawTSHaZ(S+kjf$0Qws0jR_E)~~ z74{KL=1FwMNQ(d65sF}wVp5lXwTnM&CcF{4e8Jnf)S&a(u0VaKJesBdJiX^S@@%(C zpmV!-0fldoi|6@uYITRQNp0|f|@ZP1vMj*Eqz zp)WD~g@?iXVX~2BfhVe_xPM|%gpAr{0_m<%p4yh@!dkleXg+qiQ{J`3q`m(J(9AZtV6rh%RidJf;?l>ON^06YIwISqHQ1 zZ%&%!(Q&}}j}nd*V+q@lT{92ciV2qNysPN?#1Pk)8oMd!v>bdS#tD}MC=JD5f;qa+ zt`r@-d}yW^&)RCIZm5162J~hYE*BNzWQuqYHVM8*$hzHNo^4x#$e;42xs#Y@Y?Qd2;s6QhB=Bd_H}On^x*1 za|yLcYB`wBOE2zh%H93J;|n@+jFVOBc1x#>{lN*Md2+o+cRyK}rC+=FnpTHS`e`yBJ= zH*P3A*cQ9>>I-N6b45!ru9Rv38Lv!Q8@Er)L>S7DFw_Oj(F=%JpjBTD#jub1TU;=7 zR-o}7g86rUd~n&Io8Qrrr9RemjAd?>Ie1f}AO__K-}+7wv|9sfieRvBHc(U03gMA9 zDeB^Lh!&O;hF`{6sWMn0m?0u4#b?f)e#wqr@#Jx>y=vU=D2k;QK+cUaUqrYO(z#t7eAB>t))|MoFA3dw%&-iRN&=P5GIlGAvND#-=5_d9NBPux^# zd_ogV$c>=ajcWM0M(vS>PRD0W>-eV1_!3V*l-TllDDdp>nwe8nzc5bP7(D>LVx^=E^-_6x>fW8b zwt7?S?^2K^6`s>ejaz9B?A*bcWcyYK?pqK1zD; zX1)3=QW1a6b&Fn4TzdL=#d*N@hRo`yXjkU&w&<7*NFK7U<;!u<)-F$s|VURqA) zeQ5=4*`(9#MME^4KUg2n$9mmDsd35!juxV12DBLtIoB|mK;u_-m06&61jyR7oKzP- z9>{orp7s~lcBWUEeyJRmI=*FF-zR3UFotOIf3k0pMC59?Dvqm*o}|%h7{xiSVFrL- z8ce9nD`u8W&9&4Nr{_gOamFm)?&7^nNiUsuXDUvdi~cZ^nBq0|L-M(X9&9t9ysadg z)&XSfzLkbhA%cEvPwF7ErLe!XCn`Kmc6b@yn7mtg-c9dZJqr+`e*+lNI$N_2JVbM| zEwBSP&${vjDAQ-%#hWY$v1_zx6pufrb3 zKCs(T$*VY?r916>sIf_vIz3r8>v$;r9F@Jlo?3bT^N5<)OY?QTA`Ul!5qjammLfk- z{T?w%^*8|NI3Y@_tB(bg)`NFYzedY#OdeD<-yFls?MR4K!FcDgC2 zl!P!xphmtoEd`Rak`_&5^tdkE@xpQqHM6$q4##;>Dp)RvlboC&IJ{o1H82c&GKnhc zwxn(Kv6fsUQH1lqHqrh1@eT3>BTM`ciWgKzFi_83-=oAVTqxi|Ed7%2kGJvY5jFbk z>+F_}ZZVN?sjXj}1Yurxh45<#_TPSEd!PtyS1-cBn~}2T6hv&dz@4`vv_eq?UOxUK z+6VFe=j%4@zfv8Zq#OU4cgQQb!XvITVhw|8Rfc|$->8lSG`arn)vN$iFklFinji!9{)>#td)(*e`f-{JnQW5 z2$}IzvX3+8ff;6+CmyB-g-4{$9CMC*-areUN$G&ms)pl7RU5Uv%})s#ezX^GB3C>O zrR?puuH*if{yUSceXjz{Q7q|!9uH{*x zS@H9%ZBHz!aD2@*7Q&naxvxrjCV*nMxh=6aq4so=#f z8t*G^KIUi?`5&Sp9Rldlbg%IR}bq=K>%iLv3hYMf^GFc=@)CDh4h_yG~D!lEwZBnkPh7 zRlvU|skdFa6R*xbGb!iEKgZc#7q8?hxT|5Je6s0gb@w`>WhWP0h?B*#=|3&bfB6tC zh~>^>iTY$?8*+L7scV8oeCBntuGXSQ7pI|kXiq=`RVoqskCdb5BO%9!eSYZKv(v;% zyA0XoH9?ze6__6#TSC&^M%6|+I7n)B&)@yz51u8-AwS9o+aJ*Ypb>ghHq%#PPc56c ztT?OyILZJ{gIvq^KwDD*fY6s8OOcGJ<|Mxh817@Y-J+hMAuu0A+J1wc#{BP82EMq^ zdfJbHRdrCM*9e`-54Hn~4t`CHV^FeZ^&C^inctlnJuxDGuRd97r;^nXqMH5EL2Wo5 zw%PwcW&A9v4iWCeGh|V!);!lh^FaK1jTLncYn$$C+F${3A}{gINw_C~l?H82&W@^M=771&!Kr9m~5HQi3Kgj5`!JvuzP1J2N(IB)9E`b zWPsSM#cA5MC4sqflOFgK6_&{7Y4eG|BNQMk)!IBv>9^={QLaSVs*YO5iX)D!aeKOD>cl&sP6;6{pb|TRxWMhh-Z$A*JE^- z>f~LAs82=7msL)LSmBFH98jyBwPxqa^YQNPP7i_J^I_QZhM5|>R1y5dY-2__Y)}93DRDd(kS}uzD+Xw*89(-PeELdQf=j_hZ!n(#g=#Ev zCsP9|3;FWx7Ij@KP2hGUE24!3}gy7yRy#<>*b%lrny#2G@qt~xmsHMwwofitm=Z;yr4D8|Qg3=hZ? za$aO@eEDQ+0ZSRvpu)aaVtrp8J!o~KA*rP!r7AbI-q!olA28@PTjSwJE}B*TEUIS3 z`HZ{G8QC#z7n=TF@szKpUB$)ynx6hZ>jJgDpR4yRk}?1t@qSrP>#y;4uQV|UtR-%v zlzC>NoaoDlvu~kXU2kB2)3MrcpZgkd8lJB-nW7xCBO{`lM)JN08d$+janz_zNp6kf zsT7xVm?NXYB$>EJH6A)*F*EHG_}#Cf6%`2DM?U}e0sBQ@8&&X25mFMa-2tdx55+*M z^SRU3F{kdAv?q1B3-DdBi!>VHi^Fs7Ld~b;^3ZGAPPMAHxmTA@%hiNLr#7|?B>Rf; zj;e)41?P@)L6$&sfU(B6D`Qc+?S9&7P={nlSFJ*Bc`c$8nbp(I+4}*%k=o$PG1%CI zp>R2e4j^;S#>jC0v&Z&820*mF-?VyhZNk1kR$U+JVxC_v#r0`>b^b_5@V%zd*}~Vj zX_=UO4Iwp~NbsS_z)|2DweVCj#i2F6zIr^^b6ou5cBPg_d^mw}SkMcZAoqbJ$_3~4 zT=B?roS=FVX%YI2gulyve^CDDj9A^1XM}_;MKJy{#&B zcfv=jDvWGo1~934#-Hu_R<`cN<*O~YvofTR4sdzY%usy3ac(p9if8kgy>nr$b9z;x>vHmgJIr~MK|_}Gjd4kJly z8`PoR>Sq3d8CT_SwhL~fjU}K;W2!EOc*!;O&K+5*O%vuexa9N2mHzjxjpBWu0~$Tvz?6cK6!W_u&dPB7lO#qXRN{vfiebP1{9(f?N1;F z0iMg7d@ASF6@A#$)GyBqH6;%xhv~Xj@rOwgPt+_{zEI`Szfl1~Zm}d{oB(FSdm0ch zU&v8iIsaV+{bVO$lG6D5q9_2)p%z$@rq3PiVtQmqz<7EIQ6h;Rf@GJ`AKRku<>dD* z+>{RBY0Y;#mswQ(PD`M2a8&~}++Cs#p~(uXX}j2T>K>Rs1x=<@MY z3kDd_mF0Z|Ow1{M6{*i_^pwvza%mJ=GPXLJdivF8TdrfMy~S^Q{>n0@;`*yaLT9$n ztL7b>Jw|X!fhsw8OWkZAv#}>!*Ez`97r|rAf8*n=u>r(#E=0V${&(UVvZ?=f7kU0t zSxU8d_H-kmLUw+0;gBse@`ra(Z_`qL^m$A2PjUX#Hob}Esl5(E4w802Y*G!RvE(uJ zcz)YMvaQ=~dZYu!-a|gtZ&f#uewKpe|GVe@e{+|cHW8o1ajOR7dp7Z-Nia{_>Y=&f zHcI!Ay%aj7wEU0rc=a|0Z<@8=6q{;k(j4W724p5c8jEQ$HWKRm83X@@7W2T$JxR&( zH218P_OI!CQ{BB-GgAu-AQm2 z3@ciSO%^kFxwf~Eq2w*$7j|t2twlfi>^T#X$L-;XoJ3V8@+O}lEi+>ad1;eZSXv_V zF+WKs91COYq}FcAmL6Xaqj>ArDGp86mbdLmE*B0Q2Un?3G!*KTdd`#V9Ol~kt5bqa zo^`?WlyEa`zO8<&1ha)*9F9!2J){~5;!RvP-hM8~-jX!FZ1|FSdm@)mBsrqqQ9SP0 z`rJN_S&HG#yTfWLSwGnczE7XD)uZn{WfmxT1MyJDDTXA-O`r5s*^&vRUEg35Xytze zGSq|kqrA<6A|OR-X?|yyZKLAT%99lI{O=28vUzG@WA!4&5m!uE^pu^IL$<=mDx~vc zomNMkp>2zIDX73LPaXf8T5j&0psRNGv3>S`6x+U69|Kh4d8u*2GdyeP__`Q zgmj2Wjc*aGd;^ zF6-Slk(ajFYjW8?JPcDNFrJXgH-*UkV4F#!cDPhPXsA7}EYts=b-OyuQnfF&ktRoF zF?VkmI_I%#kXhrok{`g~*DJTwD%h=Nv%J;zpj8D~klj7J-;tDSnBJYNK9HeCBrchc z%r64PHv7b=xY(oz6%mTY$Xgj$pb_v%7s=!~U+gWqs;KYw?Jkzk+4SSC?I~OBe-(G_ zA8s8tfJw>GoUu`OT!lNSRv%LHlr*a@UTf*FL6|1qxmIEroL(ncQy$R`!uBUx9<=&b zvs8WnlR_(j5x}k>ccIcdEic@6dfBF;(SJ?#6PtxIzg#%_?`o{T#W*JAX@4hnU{%6; z28usT&+>t#(LCh7cZn7I2fnDWN`DN&RlyqE0BO8?`@<6XdAkf=^ki<1;Wu6 zkNdo3dCMuPaZJIJ30bUS!tky25n^gHxCFS)Kiy)yD!?Ybaa4p^3~Ng>6aIW!I9BC| zp?{oNpOEA^;~#wM?OavX=sJP7iIDqPG#At@FzSwWeqdtI{Hit`j>Q6TDj(_Wo}uyB zjGt7Y4SSSpUHW|Kbn21&xig6!_nKsaM$>nRsnL3xNVB+x7jQPUD{AF+km563yPhEw(Rn!O zQrkr`=7Z)_b+pqJo0{bSTlLx8MMD5-lBdGUIM>6MhczH6?pyQz-X>hHq?)gNT4)%m z#TIi9Mj^&BPx#e&oXKuG5(ugo3Fw25^jcKZ)4;h~_Yht0H-;TN#-w{}vfE$vB#xn0 zZt1I~nhMQ;O1HQR@2s{VMSO$V9m=Tp+f$PRP$5BI7n9&y@bY=Zw2z(y_P9n`gM0ah zLQB;xeZd#UTHA`qEkl00-;{qTI#S(TG^h{2ad>CG77be#B|(un{(6OCc1J(95PD$c z>5Igty99is;aA$HpH_3|;NsEktHLfyhNV5B!C^5^ZGWb6&)-Gz$GHd|U|LSuq+H{+iT4(L`HP`V;^D&d-D>Lk9E1TBzedWuS z1k-~jBm(Z|EkG&sC?C7IfxT!`ls-Q-SMgQNryk=>4tp`)2Lf=ulQIVu!5Eb2#7Y@Th-UVrng`x3M^KE9~)o2H7O zo6PI&kr!`;&B+h`@1{Ukpyppx)rkqrVyE*qb$b*vY(EZ$@EZ-nRZX~2W1B1*$>xm^u9IJ zN~<3y<&-_!%sTb02zZcZ>7NMZyb|&SPL2>6%wL> zoYf0hDGgur9A~8xmR|O&WPbr(@Vt8TXoaHt1AdYseOV7q@jZkSf_r|%zlf_k)7NDfejQqUD+p;b!V2wq^@H6ege@G;DmA^@(J(iWwKP-}}ty2D6F6$o_Nw)~Y70BNp zYv6Vz5$*xyR!Vig-l@`u4IjrRNm%ox=_$=>IvjWE!sI95XsVT@{1%c`9>qLLiSfMo zmiK#8%=+2?#S^vR-ApHaYJLiodxf{X{v>_cQ_thnQ9zF2<|wDRYcGVgkH1Z*zq6%J zW{NLiuiko7z_e?1`N+!Uv8u4F+->>ga`hijVOKaATxabg^+Fu>gTop2FMhHF5}U^;ju#+poeyi&>JKr+oIc2TL?X=KvY8p z^%jj??^4w>mX`U}2g4&sX~uf0F*(g5;u7#g-pbpvo1ZO{A=_a9?KPa5?s1U_K!qO< zwVe04T^2YCwB8R&&F@k+Ibkg&Fpc43!na9_4d0Tjy~{92v0*W~JpSr_e?ueVZ7C&P zeO5iJ4|Qh|Nbm;kkGV?9Ch`j(^$JmPa7XOlZYLP-t-MZ%HAvKxepvPg4aFv}wDOUa z?_*_Fex{_I3U=Q=&*T!m7y|KHtnr#dsqH;$^?)Zo&X+sDB?2DoH2DR(WG(&UIPiIO zvcBn2XVZm~`RgKAPN-LNh3z3OXw2r0!d9w(g49XlVrQw6<}n%5G7@GHDoLO2`wO?) zKkjb${p^2^B2eV>-sKs%{yrEk3mI!}v%#U*_&jpX;WTJM#dUvdq6A~SG7S~=4VkQU zqYl_DmS1Sm$PlJbO7cAU(2}p`oBcfdUobq{lKp&MD?GR@>3V;-p}oNPdI$=Idq{<` z6eWDwHi63;GNA z6EZznQ!d87aqEL1v^K4a^niV>7Pu_X-hPO*tujUuIv=0&;uQp7lQl|@J-BWEs8c0&vb%f4a<#O8v5qbL(p8vLv))aVOt-qOnVb1Zl-8gOKGijlM-DTl zg|Lc8X9L8ijHiP2g%hM07|ZplDTOEXXRqOr0=m6PNFUR zrm`;%)<6qnWX>GNA+lsQ%n(gw4?~xG{E2yUqKbgaiSg?~lAbn2qMv<8!+nLwm>DPJ z+9-3Ozx$K0^XML-7IbTpP?h7~XllESrPncOKjP|71q7BS;|!RRsszhWToV@mh@S7c zb2A7G4#m*X7(BD9D#tQ5MTme{U-86i>8MI9_X(ULsmC9M)iuA`y`N@{?hi7%ih2)> z2qYxe-Imy%z|N}ACua%#c}eI!G24=RQo%#1iwQJ%49XhvcARh(6lsGr7eOA!;}>#I zF&=1|40p^gQP2I?JZ>xC2DRfU;N!CKbgMUfSP)v zl|l6NLCzL-%lQ6gEGWq^3`FS(Kv!&d_<8BM0dCalNF?lRORvJ3~#3##@iGmi^ZP?6yr= zTX&KEg5%D27zTwg;>XhCT3A(WqGOR_QWNt`m*T!I3yI=IR&g7|^+Q}cO`HY4#j0Cj zjPA_}Qpdhi0fTR}w~l62@jd4G@Vx*Q$H&RkDQx^ z-t_K;qz)p)mIF|Kd*d6uP3Cpjf}(3IA0OyB@JnJ2=iGfZtM9S%WmS3u!vj_8QurL= z>t#?IT?1-w`onygqoT}P+yr;lHe`lfNKIX?)c=F1QgZ(a%do8c6P7`L96UCBoFbI9 z6r(J9T_Q?Tl%E<8bJbJ9vptq?(kVI8_$Tr*VD3=nLd!88$dX?@84$K0z+UfRWu^D0 zrpKIS+rGik#UwX8j`#dA4|Lu>f#if|dWrFeG)VfHwtcB#ZPzat^+qXq1^Gi1@x%Dr zMLIIJmxagf4!vB+{vLTf-bx)29~`Sd6L!x-f&Aoy_rIZX{yfqO`N{uZm)o3_To&7N zr3!&6a3-A0SAUca4`(_evRH>E4Xv|8(7s;rS!OR^8yo0@ZBpKJ8y&?bhN1#59o5$h z{5~tb&%oPq#XSe?Z?}FF+)D?YRFo2r{2h&PhTeMiJ-0V*PO80@d#HV~R1Da^_bRU~ zEJqJ+?Q0uF%y^0T03DuG9iC zj$5@=3=HvVXjQ&=bJnT&-s<9o;z`nKNLYDa&;D<55{rF|hjqN#A7j8g&7TlkdMwS2 zj1?-!d$-oBm!Kkg-WTd5zC)1ZR%S-|!JR33Kz((PpeAjZ*MXIFR_-02qs*uLz1=1> zkO#eJ;KIh?fU)l81}fj)lD-qnLe3Jv?_8k5>EQO$?NOAwYgi}(}4i;R%)_I z>zm&H#oSwVwHmd4vK1FyOrlIWAxGa=zOP3w{ZJ(tm1j8j z*Jm@|f*f_c_PVL#`c%u}7N*~721*<;f&{;(v7|_8Zqcm^bKS!rzVZ&~Ru*#i> z5sv%T%&>y79M9}*DQ5Ldw|MW1KTgD+pnKZ5cPbI=>&Of(zE6K75<*<2-TlH_YHc;& zVPyP|>1fhLmK$=CnCpv33>EtqHT3-W|KAOd;{rW?s_6Gf9zDlaKV)vdn%nnboYG&) z%bi)w&$-`yxOIPe-nzSYU;b+ix?rCu^!`b-rj!zhilPn%Ss(E}20rjYpG}01uOCHV zgouiB3GMsp2W*58`t`=aP~Ok_kvZd=xWre5ik7S{6Pax8PYaytKU4aUo}ivjE>wkiX?3&8 zIt2A5qo_!8Z67nKk)A9DPea>Fxo4v)RUK`l8j49#Y+|cBN%OZdz|LK}(?n&d{>{*h zG2=F-+D{m!$Ks234fqL18$rs8XGW z&yZeKvo`|X*X_70vTUAB*!ZH^e}Y#@2|K8g@Qf-bg=Kb*#eFa%6HX+qU!MLtgeMZj z6V&55DDSz~{_lhcX8$SWWz1x8$jS`K{+-7^Ulw|^{YlSGHE;?@!2iy-_~2#jFVT$y z2VMTV2_hfr`8XcQ7+QQr9xb8Jdge;)kNda;XeuHNRA<|Vb*;I82M-RV2cQGnqq(L+ z8XGqa`_E-{v4BEYzsEt#RuSeJEWK^Z{$L>3^T?&4`QlUnR02OHb35> z`1bExz^A30z2zZ2coWw(JrN5Y#%H;*#vMT-|IUU+JsWA1RWJqX7u zSW?A0BhKM@{p@LV|M_K2R>I1|8n#T!&~&5R0uH86V!Cci4kb-C=gz{I?T^lk83z}6 z%9r@MdIf&=_HFH7QiG!$zPFqn^10d-ivj!1;@G_GW?`xHuJH9&$C>|v6wieW??BDo z%oI4?*kJ>5y~?CQsA0d~lt_|7Y*{v{;qv~3O6>*K8)`+SN-iH3N&9cZ>D!&Q{9*2_ zEJ}klWG{R(rJwBMW)PZR$Wok-5uag3i{= zXP6)IvJuE(3EWLlxxkJOY`Y1cy+5POCaxuOEvz`~D(pcgu+T(7QF1B;(q?0Nkyo#C zvLRo<_vLD|gsxgGVq9qV@1iK=)D;0T_@XQf?SD?hcf=&W%x@qhvcJjavS{2s&Til? zeB9_UxmUx%W`v4p`YKOL$hsU@oP{uQXtW<#*dk)>V@6(8{FmG>Kl$kU;9nRwc2cZv zMx152jZ$M}Fm8w38A@31)-*G)*8^_TX$0)oPQYKXDpfD6vFSr-N{(Tt9HL3P% z7Wn#THVSvDH7RA6`{UV&Z{|}?4maZ@x_^jyvEOOrmlVIjLB((KJ=`eZzm|`$F<*O_ z9 zZvrU*oRi+Zl#qU(x4RyoGw0=pzu(I4a&p_%R~lx3H6|DKM(BNY%a_)&==Rd2GV;oy zKS#X>7P5#~N3h+@4qjT&X1%gsZ9NG&8TZTROW6T+SDgyticVO7m_8 z!>`-DWeW?JQb!Q-rc2F6LxaD!xMJ{Nch&3Q5@}2d`Yo%2&I+?nwl%xu2W+RJQ&W=c zT$*f6dVbrKciJX$EFAW71F2>|{wPMZgv;{Oot4MtzyUmF(e)1NU2g6D?uCvQsJqxO zNHN=zr=QBR8(Oi>k!Z}?K7q$*gXC2E(&@AHH4BB-`?<`Q)|APhH9+cZZ@c2RAMElT z#sq%%&%;uuQ$Nxz(0<$#`foKGewO-(3NmR0t=jPq&z3FN_z9&USi2t3;d+?J;^-*^7+E8>qS z`KE8AJw``}ri?uhaiv|)7C1-?mFb#BUAD{<8s|SVTF&dPn_$G!AyIdxceh1<_Lue} zv&+hXU1Zq=A1~bZq__>7N8dp|_S}x%KjEpmE<|}%F-`=`1BkDEn<=?ak*t>a89#T* zn0b~6KzOvRKS5*T?7Pd7*Q2_)?(I;HyRMofnEx*9wP?sVji1L&41IAPSqcw+chBLq zxwvY4Qc|D-Uaa;TCGH67QZUF>EeN*&`#S;}2;Z>11Rmud)+PnzBw!y`ZnmR6 z&9rIqw_hyhKcE(*T3M*aB#mUvf5r|r_LS*M3p>Puq*63IwB2>8b(RO6?t6YarUijE zlT+UDqyvc$8nhP(0!^atmog-o1Z}jIef-Jj zNSiyPoNBP=ceB#Dl6my6LKK#K?i^Woh??h3SE9~wmnoG#d)e!6Onn%*c18#b-t5;~ zOHPzqQyluE$I?0z02UP+(a`W-ERk&5jJaMzOH);5_FT}@>Kz#{u&%`!Nyg3BHJ9M` zR->niNWa7|VJ#bRcTMvLYr`v+HszlcreH-GkLvnPIsjdOc&O}>5zU8|l>@DsT> z=^dzfy$Bx&=-rd-h_z(w>RjlN>{?SD_LOB${h`5!r_KowFNR+p5mM1%DhMWsNF^|R zP@&UuSeZu4G&q!BOjdB)L&CA=;%bX}SrLK`kqteJwa456ME@`Y`oaHfDYj&3&s9ut ze614z%`QYqKj_MNl0L9_n(v|A=5n2qnY^NL<>TCm%`S_Uh9sAa$@^#N$Hbe%VZxm}iV1^$(suLL^ z_hlV+T2-_DUjFDgnqM(La}y@i6l@w-XZ3}haypQk)-mj0QP@$=g#^-JC5x6n{*fPK z;o|tcJSIL5Nj#%`xeemX>Ds;&ZH zmXV2!QV5NTiCOt0@Nt<*7O_3HQgEDrd2#}61b+x6KA#@TnFs?(d=D!U4jS<_L)9NO zskJq}*tJ-7kZEy^;ei z_hNN8q8Ifg2b(=rAYaXU*D07#Sj(~NhTtB|R0@J7)-7?7*qWfD z_HUKn_K&$n2iv-`?7aT7G(aFiYf6v0Rzur8pdNa7m4~x1!U3&_y=t+n`Mf@FX}L7v z@s&z0!Y{k9YDCQy(Vr`vb);dv*Z_svUl~egr-<9<$hC;%?X?Q-~26eFLU4)sm zoaZIi%;3pL0o<7}eR4aAqc?m4emDm%TTMH7%uk*fQcS1p9u=}|uhoa@t*6)MbpVx z$V)|^2XkYABy;62jH1M0rWi+Cmem!H&ZWs&7w=P|<|nn3<+fd%8*+$A&DnMsS#1l2=vm0b&^uG!EO z4lR22xHw?L1mL zs^-*&>lS*lK(6^8%+z=h!xOUnALG7LHLUU^w|?T}S1f4pn=78EDS-P*_Iys@Inz81 z-N{4%_!-3F7$d-5s#2>jYH$4TpX^B8ryt4(OD#WU5DJSW?U^fmIGhu>*ylUCpnI_Q zyu@!^P_Ltcv{+5z2#yN5)Sm6P!1B?aIO%g-soKIkIwq!j7YP zds_yzcIJljYgb%*abZi}igkAN^MU4 z`Rxa3#6NULF{+IhQZbg-=$4wKaN#cXj9}ly7s=Vi~|K#UtR^}t&j_d)SMiK_?=!s0^qr0BH$vfe52tU zQr#(FHMJWOcg!(i#ei!gMvUqmR75Ilt3!frZh0bytTR;h z$DPel9tyL~PH7|UX%~Gt1!aszX{wu=J z)h6{X@-X#U?S|XC&5AljD>t?={9C;Dr>H3t70brL!_I4y^t&f~m)0^Ac;>2Y$~|50 zy}1sX$YDAV*0Oy8)+FWTQp!blO!3?p;UPgCfT}!DA2GM{No{kM?g;gs*5PQM23%%)gpgOv@2i7VPyzSs|Z4y)@v0Spa@hT>6x$*GCm#&cYM||Y64Z^HtRL+iB2>Yg)u*RZ3Y*!X&aX^{?!&uO4{+GynOz`M-mhdz24l zK8JWdaQs2~VU?HdYsoo3wO_N@FBIqQ$CjnP>iCNn9XgsScHC{8JX^(whgT1(Gt3^} z_NMBrb{_a-!HP5adiAF{I=`H5FI>SJBRV#h@1j=z!FWO94Jg;XzS$6?;O5fzcPV~p zGp(O0;1qm?hjRms8}ce!cJEjFR4RER5IJ@q!4lSQAvuxY&4!4FWUDCS(xdVu+U=O~ zyxklLq8KLxaW2WElT6!bP=HxX=^n?-kDH_}o$y?y&c)(Dew<#h>IUM*#`Y{XQqZW@ zndR`DDOE)=zj(I-l51h|BZqsNrVOp_8R_Q${?k|a2B5uytOqI#Y)Azc+pF36tJ)a` zF^Eb3afW1cQdZMpqZ><6iADQbpr4Ew!Wv2sue1KNxM%@_^P_{GdF*C`kcF1oEx~rC3Y5!KhsCjKRY0HO^yjqPT0D? zXey$MVm75+bAG?IUC{xTci+=it;IBof8nMKe21;?%K4{^=Ty4brEegOP+4otUR;;{ zL>NT;bV!KLI>!OdNJ`l}?H^c|qq8a=%^ny^Ag&gBAo4(2aTkd>o_u7VFHx>WbjhwZ{fU!TQ`XPY50zCqV{F5({T=^O>&Y6cw^>1; z^9V{M%DkT#pi*{hn=K11q>xEvSyGvby&1h)LB4+D>4!+@k(Ao~r{UzaSd)fF2Q*Uo z&MzX8sMX9-3GcOU+F*K_*ee|u2lVD68S@X}R;+`b+}CXNLTVwa`#2{tMKypHtx->5 z)sP8l(fb&3i$#@9#W5C=w0#I2{{a`KN^NQ9d_73 zH&YY*_-+|O_^#GYjK}}fN%3vAl35A^-4!?J_b)#)}kWJ*1Q}a zfPe`F$PiB|_+sA9G^CQL@~9fH#!HO11{FonPb?pThDf~q{~BQ?9onn_X7zvY_iJ=~ zXU0^eydPQFg;t(oMAy?bi5MvX^SW8(RXy{%5v(lljgAkkq60W~D#m(G%rTTwzEG7{ zUOMeh5Nv`VW2UQCI;$l^1Xtp4E9CSFi^pt9kGQ3P;z@8qXVugGp1s^4H*>XLvXx@8 zU(med%jr{_Zg7lhAQVtGM_YV$9*nc68 zq&;NSOv!xWYflD?fk3o_nIHPhqj4xoKRsih!$r3>iz|~!Tk|Q$tEpo#r+o`y)hD|0 zAIa6U^v8%=P4S=CV^cD9DDs*VmaRU)<0G{qj)n)HZmY!{@Psbqtz6^0-Em#a-HN*S zw)yd{io0V72+8is|Fx5rDTKXubc$eweseXD1ol9v$~2t>roT{D6)L+U9} z%%zsr4ooX@WqQiIvtn=0$S4JS(6UKOW~`mqODn_O<=7v1K7QDpkO5Y>Zd}*Tz*O_` z+&ikRA1NXLHvGiGY`Myt{uL1~h4hxtvmftVaiG>)H>P9}2N9J#+xF{MTm?u@cg{EY zY@}pXTY<=Lhw(Xgnit@eeD$wiwd-F2(>Ya2X+eB5nm1%_kVot8uY5R@js_xT=cXqv z8VM4oH!OHgc*jIYmlV;;S#k>M?K*Dz$_G8cNQuc10xG#NRd(xXFf2`qu7hwlziJT> zaQF1rTxIxT-!)+Q2;WbAMo!km5~AxbldX(NM!Zu!|CQduR+&d_Pk)5 z*1t9duE*vME~~8c#!F3oahxbNE$!*3FDSAKPaEGtPP_9KP}YKJjK$Hlu0|l|f$kV9 zD?ERD>M5X$3yIRG)*Y>adiJgK{_NWv3ygSFoYZu~w~d!#MbzOVdPrG# zzyW&*hYPIdHEaJ!Nj4k{=C>r@vaD@7IE4N@{wgwMFz}ZK)2Kw_7i`w9;_zAB+x9>Sfa@bbbjYP|Ah z?{>RoWn}-}KG^x8Pc8a#W;W(=k4SFN&>BC?N%hEq8LAv%JC-=9Yr1t|JuTuE0KW@h zqa9@VeUq1E)q!ppAaBPWH4A!kM;6xey>GBUNG}FS60xypS$-?nnmKM35bVpn%(?chfZw-}{U%6oam#}!t5+*cX7g`XiE#P^PH>O zm`cY)foFT%BcujL^13p#f4XBb@cX`Lc2_kU&opHE#mLuJG5aKCGh^;ZI}PJT z@YMHYj9%8!(;8@F;4HexpRuh0N^I*6-4G7%>^gnS={DbEXXVqEGmkJ8tb#@w7RF#?T(_Z3KII_97&qUleUE()RHL`b%QW+;6oja z1WeoCHsq}twnD9v{6rT>z*&jq$sErHudl+ntm2QhPiGrj*!@>Zek5tL0U&)lEn>fB zF$ody`UvkII5dYLWi|tk+6X)&cpMDRa>jVH7_7pGxeP+jozhIbbcZ#}!|P)5h1NF~ z=-*j575llZg`nrMHNh(0oue*}Xq(B<^Q;XAT}aa8?{q92#a}%M3A47t!fx2o&A+Sd z86zCh%2e)Ham~rqs~LrD2=W06VFdew+@ymOr@JVeW>ZSrf^o`*sYAiC@C-9t%|+HA zjBF=`F02lLw{b(Rfi3LjrTGn~n^5X~G}a{dnp<{(qJ6frRey7--gRY_c&D=k3Ji+) zkjQmU+RklPwnp7%%~VIO-l4fvS9-Dh9`jgIJOch(D{lvZ1K4&|?#}Lj9J^YXkUYhJ>;d@w>?|Bri3kl4_dq{X)3*n(g5lF2*90%-a| zcFH^E`;-UtK9={d6ne72<+ESjNgPj%)aT>o%7UJ*K7?it<)<2}a_UIzSndV2U5|qX zFgu|>72x}T)4x^2?y`3hMvB9|cIXwW3(~|Z5JUs69N>d6$)QI{G*>@{?;Gd0npnh$ zUl5o2J=OgCqyMYn|Nrluq2QlLw>l)m56C(epTMiUS6#Wiw?FPylQ{t65akDcjys{( zQ_su(+WbLcUfkkQRnNEUQH;QM-EIU)RAk9b0bxSNISI$3E1D$q(wB(pj6~XrU2E3J z9qzzT=f}J1?l|b3Vzz5>9HRWGK-WY&*XLo|o`sPf@NN$Is#hGr^Wr~CI(!Ofa9X!!37 zEnyaGO%ti!B>pP%;0hg7~;8v*U9G$?OAF2-`Z&| zW-0k-54hNz9aPB@GVQKXo&~NknUdn$Zw=&804dsw-#JmSsPyzVtQbR}<9%=zsP(fi zVnoE2g9?G<@MokjVw|t#a~)}NB>0&#P1n_GK_7BOa!uYk*qo~Vw!TDidC$nlbCTeW zPQ#tPA^{mESQF@**G-xfChm@)FXjG}$9?*a@ld&|ZYpn>?pn^~zWj%F8G~<(AP!b( z)H28ce@a&^F)c|$Dye}o_^`${sB}!x-E1bR<&GDVWduvr$alt;p3)OmC#wZlquh#g z3IK_Wfa^8vF{8j5yH#L0g8uxo&$Cb^fTcqjCpb-ps}d+Q%XIuQenQmlY3v72nM&5_ zC&LW2!QlCodw5pW%$wBH^nHYk>q37j`pdE=9b)D98dkP@DL*!^`Hx2ygLT~|aIogW z=H)MmL&6&)f&^k{euTit@KYxcFzqd2sw~Cti)qUs^SSj-hOsS_Ylg)`L)IRNV_iAX z$F(sf3jV$`*+kIkj21QTx?o+gHKHuv+@Ee|(6G41S>bAQKLB^?ob~kXx;Aq&7GQk2 z``WQ}cBZGivWxE|8-u9mlBntPcgmEw#rQ9`$N}1Ooa@=4(!-1n@t?`7e(`fSFwiOm zzoWwq!>#fr|`%}xAj5`R_K9`c|sSRb`HB=E{q&KttUPPQrRBJRk&lD6@7 zyLf)6^WZhVe@Pb_{|<-o1Y_1SOB$pgXfoyULFV^WR0*!4M*}pkN#j@vv`eIr0Y@=#>jDR7nhN% z$X!es5^;VEXsLwrA~0SqJlXd?FcTwR)dO+IOaR?kQe@w=VQolY4w77DJ+pQPN1|Ln zJ}oZoxKx;oED$@~Chv8Kl2`!4yk4Ps?Pzu}->6sAS|b(QA|9R9Qj7RXjNEVmb=K%K zy2%!X@zxeOR||?cW4MAWU2*i>>OcW3L?DsKa(HZl5nTzt_@toP1lbK)p&-$Db*#Ue zxU1ktlBG8HA74%Y94rHHYd2FX2&TDRc@Z?0#dzgzk%$9K_)Oo-n?B*Z2eGaUd&vZ*m&wQ6%K`!9jVA|XA^HBT|qEBwIRi?m3y%ho~XohuNs4# zn+mv+)-{g^vShH$pN^}TbBU-YE9-U&tHp(77YA$Iu+~83(n< z?#^U2SLkW$VL8`x_K3-T;Cd-#8DW0~92XmRP17Wz7x5*6`HcJ-VLl@?r*XZlyxfzv zLvFlW3z6@-E7XYdr$ofT!!hUG0rG!M_-%-=1T~}$itSHCt_bLVwDrpA5`m)0I(IM)un+Weu95kqZUhe%)Mc_`LyqcxvjtCQM5Z?gv~N8Ipspcbk>dAuC5^YWeQL}@w;v<3 za33`c`{~_i@Qkw^4HBXc$CknZQ9~Y1s)Ho^el>Hlfg4l&=C)C$v0Z*@B=?a-gsg~7 z?IZ5YQc;uBW-s1g!f{bpE&>lIL?5l#T$>{mT3WYlyTr<9*qXI{Bj%Ka?NR49$Kh$F?U<=gCJ)ue z>LgZj9lF#IKzOO-O=x^yjw}kyMDevbI7>lOoYz&+(ht}FehACYVA{ECX)C1=qtgHu zl_ZTU=pYNo{~_oi3PXO@6$K|96l&7K*Gd5A$YjW@D53aj0bks{#nh|=qt~OpJY?mU zh#QwFhnD*xaDSp^mp&2Ybu6^ve>`Jt%Fm9mkwWqX{%w@oJ4$T1z(&fGY~J{ZwF6ZW z3^}-r7Hj8wD2^mODuDKFugX^ILS=Vwx^M>~DaVTk^}55uW9FXt2uYN#rQ>Nz>Ne_q z>F~FH+kJ)Yc?I0pe>$wFceR|9;3S>-|qv8mDzo;HM z?rwhd`&bLK|IrS8va~HW3_;2l8drBPB-mWyFpq~WHXPdfDqpiffG<`0+4D4;jn7eG z9N>}h?)F$ z+*~FY1D%j@DzO9i1%z+ZSHvA6i;z3_AQMtACbjB&M(zSole;!&W)ESw&+2-`)W(En zDBhu0U`mz+-ClDR)|q!kphH{FvKeW@QqeYG`+MchH*&UWV{;SZ~;NO%n!(OPK^R8Z-9;n)jhJd1No& zzV+^re>0F?(j$CayVGdse-ft0g58fJhK8l8D7y5jJSS8Y=i9?_npR>^X8V|OV#m6# zfI`;rm$M(+rE{9i&}RE2daibKR_Nj$FR?q*u|wz3S|) z)~kCY34e0w95WJvK;0|zpX#OY4lVd4x@mCfkvf*=AGy2zo~A>5Gj*_cY-p|dV5Q=R zy^sawahJoNknekPvcOu6-gbIW218y`PK*z-ka@Nvr{TV&fSHQ*Pq>X~O1C>)Dc*E@ z)M%~-S4X*6enU+5H(QSCTk%atJI_M!9x=O;rlMddh)8; zB+Jn+qGg> z->3^&vKNl7T-Oi6fU3YdYIjJVTx8l==~)H0Z;>Q|?RLZ6FO8-A#L6huTZsQ#7I4+l zoH=%=bNtkEsT&sP?F~JQEN(;mnfb_EcYtB4aN4g*p9ek71V}#V&ei^K4aBlZs-{TO z4GBjy+&q=p_JaEA57ysNe0ZQQIGj2Q?=b49wA?d}WVuQ1it4iZSnjnnS??VPM>zIjPfTkh{rV1=JsaShtW)4TV^!w~k|0<+~zmMDR) zs_i?v*Loxm!JH=TL2;L)uzhj>B>{uGqkkOryD<|HbBOI zbFI|jYHdVFXmDxSsBZ)fb>=Y<<+h(mYk8_A65yU5mCW&Nxc7W0n4tDXNuk$Su=>f& zYrKeDz9ta8uxa1dy1HU{liQ6^=+J75^cHs>rP8HB$u+1L3!-cOs{FoLRc{5^{z#ru zdcDI$<3I!Zljpr^WH}Kyty_cx`||8Y*}1qx98FA`cwmr!lytP!3vBnR%*P0jOx zk`^6mxQ4g=GK#1wu-{+=uSoi^TpPNrru2;OJy{}v$`$@PNB++Kf4x`=z4vP2k=H}7 zgY|cdp#o?yx&&sOejD3kPP^`eW$hfX;j%t|{V8mQ&Z3BwaQ z>!Qs;p8Za`Hx~y067V@%s{8*!XXT?~PBT!niHFCgSh_OmZM`%`0i7EPGzd#KL6QCmZM&Grk? zE_mFVSCOo&a!Z<1iN_Pf{$x+3BU#Emx-Q;d(3R%AnU}|BAZzMFwA^sl-?Da%49Nex zfb`&-s&aY0@lkpu)p&38)*;}cG)s4Z!&!|7G;ENdQv`kcZGLyTaqgN_ zq+&SlQfbq2F}YO{u)t}<;i=yp>&AIQ20xcW;eUGjA6#YR&c>EDzoyI+T@_9>_7Ov4 z>z-z_G23rtP+J zu#H7JU#>dHVzI2hRRP_ey4z@Z=q(Scn3ZdV2$^Q9T5LKhVM?m9;ytIawZ)w9C~3Oi z+^ENBD@~;bs6H#e`!1?vq4@r|y4!~U2MYrq3&VNe0>w*8^jqorV`j!9zaL_Yfx%nj zEvFxB(f!P>HX{5o^ljd=A077GHnm27_oudhP-w+4KRcRDIP|m*d8{P`Q~g2LP=Kgu zYhxFW%!W%jPP*Q!wRG=4oFdQ-L888F^g0h#wC9B-?u-vTBWe_M>+KaRip7P5yc1J+A2R#X&o0f&KaY7l zv)wHP8lwkIkj>w;L2%g#pYpi7+>cZP z9FHL+{O|<6(cfZX0iN`$1uP$?yMFATR(XoKZ2z6fxM(GNkutcu;zP9)3kPo1A5@tT zvl%GQ=9ZzM$G#S|L+qaowH`zC-Mav(lk`rkE$3aD4&b7x{9sS_FKW|Rbwcosm`I*j z=Y7kT7r|#RjFdKim52-rEde^t$PMN$dJnn4PSsJ}MHrRww8!z9&u;j2Lz;7?Fq0>+ zZAtbsMfcYWnKs%rDjMwDvQPq~IfsU$<^x)9h{d&fJwm*j|3zBvYV5)DIYnHh&N30> zqxI~e@j38+^{zq1+H%#K%j9-UWQ)g&C~ryhIaYd-RZ)M|YUj(>MIJR5H}tC<)%UqP z?RQB0envAUX4|UoZm5&h{Y~o??zHrF_>V2-q_Ca%XZnBIxsjDVjPUZMd-^&Yx6_q| zLwefxSc+5LJ@`K;IbIMeS7#rY@j+WlDjQ~jjdheWQ|Q0)1uai^xVrXYKS&Vm(d&&I zbDSo#166nm`h%xxIL4V8+$|Q?*!2)sd?)!@fBsrI;CmwA~6R4-~VTHJi2*XDzm6 zKUsSPVw=6<8;tN~FacFvUtR`XJ?`3?X}n+;1zZimo^}@fKJGF!Y`lphYQU5kXv3=f z#p+AWiqBIg!&W4Qb5^nT}^E$uZ}mwmi59?DT>s_{ds$Lp2d)ZsMgYH;;8@S zm)TgiHuK7XC%`&=%FumOE2NBz>V0<_(JuxKDSd}&y#(Y8-oJuqSK`7a<+vdcupY(d zZ*Cw1*foW#My+#&B&5yKgEJOoDL#P=fE6!DP&7t$K0qA~k<5FFj-SYYYs4v{tk6bB z?wjEI&h6FL46SO3dGeg{2A`_$e!0l5Fl2hyGdBhi{_u}7cCs=Hth>@n6j|E0NWVyQ zp3?R`a1KY)HB;(JCc^@^?uGt}w4{3mO?N`Tx`bNI{PO%&FjBSMytA2DDGn8{b%S5P zp}xRYWoAqbx-+01R2_0QNDu|8Km5xjJ$r4DAfM2cE4&CPgsEVcdyQ|A%JGfNG!*(z z-PNWAcd6ZWAm-GJ3TdM*v;ih_b(j4#cXrI_YK;JxYWNTVL}Uzs2^$ac(X~D7dKngT zC4VZo>tpb##hjDr@%h0|$oH(yc`R`O!1A9(`Fv!2^yg?T!LdJmRV8&PxU|&^gjZ)< zMb7PkcR`B0DYMNBzg%`&(WO2tKh5GQAhMFEtx>XFCSY|N@6;dUnzDiqJ8j}XiC~2`hQ0|$uqz( zh$B=(1aQP1-7#TT!Z0BX9A}6vJh?m(>}KNc*vky-!Xr!>Z%QZ})QgG+-@0fNLcT^d z_{Tm|P;B+PeLaBD7aQMK;PNF~P+6|u?ggB`?R(w_0^{zA*vk|vDRj4lTsY&ps}}lW zHLupi-xG!xB25APX*a|v^zPR@3}*h?iJ7#tJ}Ez|`ieCdNFWuLPn#SfW2C?B->rqR z{>2XSf3br~D`WIxMn_q|=z_m@6rBlPsQ;;6mJ*jsJfxg+mcwraW}<$1{r>?G(W-wj#GrZC$$!yC zrO+0(0J_$; zsg(5Y(*QmdFh%v1V)~2iH2>4X`XhO?QXe$#F|qtNV>6^z`0Jh7d&wo#Z*{|2mDSR- z!GC5xhonEe^Nd)(@Ce}I)IQMuD7)mhdw_qclU8v)kYAor;a!HSailMkrgosUF~>iy zy$nHziMXNqQmt1y>$zu3!=~z{DSttZtxRK$5LjymBE}|?@k~l-QQUv(KK7ow)<1P0 zAe#Qu`ib$zeUB%c<3#b{ucD?6C#Q^+cW?Otq>}!gCN^2{YR!Og&rR@Im$e*uWV^ny z(CcoG6MDOy+)q6P%)U7J`aJJk;SX_MHWu+3$7?7FgDAdrGWEAt_Z9c-;QOmiLe#&p zjIdMwzvCPzcs*OAhJPEZRR$FM_qpH|`Qb6EA?>Pw?1B>HY%;^q2|JR{!K=N z<(C!_BUkGHY?r5gLScZ)!KuJ)t?tc^{Z#|3KxQqN+bT1=OKzRmmVmc(CaoypNkve( z*`pb{omGnDKmZ>}&}AXcZYe72i1n`{!XXkYUnjrOGW6ty1y*4)KA|{3V}Kb~u+(Y6 zbEfqbnHI;n^H`pFkr2UBTNXp$b1k43U+F}lm)gFcNn_{*HS|+4TIVv#nIO5>U!oMi ztQz)4Ut2di%1Z}y?^`z$_kGC;1+cds>}P_{ZDN$KWER_gZg;Zxj_Hjnr*Mze8-mQgvVOJimloRHu>`a>uZaam)a}XPvI$GY^_O2hFZ^%M0+1$}5Rf z2AE8?l^_;LEGW)L= z{MUC0jt`eo8~)8vm&9gg{C<{(mS1B`Y;IzIwp5254Sy|hr#J!t+PuTT-r>5d-JIJq z(KQK26-o(I+@9ce`E{35j?!=gvjRmtb+-*>NwMjsLQ?&F>Xztq0b_6rG2)C14Qe!$ zJR-55I9k1!JMMVx(m21(t?;E!y++gk ze#dE{9;P3$vbNGS8%GV`#lPkYv+w)st5suX2)DY`D>2x$>`6IwZxTCVZZMsorX17d zThW8*?li2LiHb@-zlM}qJQteI+C-I|j1%?f^{%qU5~S365xq}ii;lQ>qWBUNMw#A% zZ1m=rD8m9NCLCEUptQ$4;A0!*n)Uf)YrX;DlNFh>*!hr3JDl|r5`{UB8hZ9)xGW|L z?uTP9pT{6K2q*RY6!39v(n%Nb@Y*@07A=UvIY{T%0KU&w-t+pWVe79q|AzE$`9#;Y z3@Lj4LXxv%cjP)LE!TCbe(peOw(<6;RUbZqy{L-1x{5zyFG(rv15iPF9$c=Oq8+9Uph6>B&0Y{*l7Dd@b`JhuGOk=$O=l%^e|G3eJ^?M>-!aWyq7x-Vbu7}2>4+FL9;0?@etv$* zWIQx84e2+q0@cfN#RBz#RqtC!u4KjML@fKtRO4AyKt`nuIaagp-LL zqzdK80lmLRGCBn~`HWuwrR�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

    C|;xeDTPY26!le~SP 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�=qHe>|1$vT1Re&D>{1W}iz7%8{L|=+O6%=t`mQ?@Ip@C!?2?r6V zWE!Fncd9QSkEj!j@D&jO<0vFC6-3q0cPj$AAmsoW>66z91yJ22cZ#CpTwNP9Ql>!{*~wc zFLe7W&;9S^xy7f4zjEFGf9ATytM@D4{VU)7E8qPq-~A(+|H^rPpYtBk&ab@pue|rK zy!Zced2isG-}abvQJfelw~6+gtnc=qtnYT2qJ1WIG8T{Z!g^qxz^4P~O$X?Q+^dT* zD;iaXD!@pHFq8oL^F_~uAvA`cxt5Ul!vM%6G4lNLcRwR_$dl4&W-K>q_`62D`1?%o z)+g{eTpUD9=Eve74?Zgf2YJ&4TmqjS0ru&CYD1D|;vORUC=n0v*4YKFYGQUe*|1P& zvy`k^x@YORff%FWiw+`)>7#~Z>1;t6U0bV4+7f*%&5^v5a~ zB`JXsqLd^+y&PJILP_kJXYFXGgx*@XuaD9gdUW^lhw3$NRnZ5ecibbeDyQDxW20^Q zEO}P;&PLAsj*p&iep8%8?v}QjVd|*4JLBsY+|JUdA%Xf%*)@{-3YQMII2=}n##=70 z*`fLM+$G}rOFnkmc9VBcH12pI`yg%Hl)4QA@|_Jke9rd2Pbh5j*Ka#aC$vs~-61C{6>mp^hTx#*WNf1``CKDxc(-lB=#Jy(<$*QcD(JO6%5ihj6J z#_rxhW=0yyduZ_;n^l)qhvL?q=kzI6sTzFs(NC?iF{++&<5;fn!;>|ecI>27oS3@P zQlX5vze!Q??yEIN+BT7UW?* ztLg5ODwR5&Pa+f2F~DlV8mVdqHXrX%-Q(oCUA`>(QaQD(-nLQ+_D4GCVtrsQn4IsC zPf8~ap9wJdu@;<>b+TaaIF@lSY^2t5_(&$+a&#GyRTM2EFiH#11rCyu0e&edDUCv5 zz2RA~=SUewr_UH>keHbGGX^aFUr-jns))RXSi}T}Nn&Jv@J9?{C9~IFJC}D6buLwF zacfY%zGjL;a?ilSQ}5!QzDTCbh`qgI*SKuy&C3Svay6k`k0*U!n=-GvNNB%&Y?|er zcl&najOJ!Tl`Us%!X%|W2WatY^QyNyRjkRJQ$i-`1zeqOU~8`ytnRULU9a-*%J3y6L+twca_-_45q4@g!AFBV_H21sUsE6Xxf} z&T^`E2~ZDxnRoAc&Ba}In^fJ7CN636>zB_@T&7qV!dpx^m+LCyifeslNikIhBk(DW^_2>V+K3J-VS~ zUs`@cLIbW>=weotMoa8xpybXp*{#I55Cer?P2IXDzNT`noZlQOdFjENV2QSIuL2)^ zeOsMrT$?a!Frk7UbfUpC&pJY~IXkPU)R~!`{Wo^`IQjXnqF$_= zl;`u6=a;_`Fo{coCU;$w4bBEo22sAdZ6;hH^ooZzb!+5Ky413rn z!j?rhw{eKDF}Ja@wl}x8o@Qmvw711a(ygp)Z6X=pA)qIh+3qKKOL%C4ot^%1&Vj}R zboPHnz@GvXFD_mLg1}mU83Q8*_KTQrIPzf*+nK}mA_Sz5LBIeQ+^dX1K+@kb7y-e* zU?G6VA%d%-P(xTWEb)UkV}xjwginsI&9b*~TX#OYt>v{-Wm$~Tg`z0~kKJ0PoT^+P zld*lrReVQW-jJG3nY6?5=kRn@{YQ$Iv!hOPmwjB4y)RQ}w{`!DTV!v-Lo*P)!mjcB=+O@vijQ&09TQ5spU{L93-#}08cV805Hy-~q zaGo80x?qt`oZA+R%SX{xqQd(Y*QJ!bZfT{y4^K#Q3)ZkVrk(cd4_o;7!`;~r z@VB)Xwq`lY_C(r+Uk|YEt!O-hGdScnx#vri=dn)LdZo3qg9_czbfdWwYv*Nk9dL>3 z2!Xwqq_+4)6&Cd$`_??D_pP>-2Ga{#cozx z+SB;-z0spd)3H&rEtK|6_~1I^yg38^vG`17yCe01nf3Bn%+0J;mdc6y3#yLAQU`0- zE!4PxYl`2I-xVrTM=vVpt$jEqYN7@PSR;NSJmI@a*->K!W;j(0n^V5QCY8qn*a=LR9m!_U1uTe=Ac*Gi z*@9#Q+hIG{2DZjq*;>O60NbtbqM|j76#sX3(0>Q-dkSL9AK&-fVzwm4Vq#m((`OCa zXB+sHG(FU$Oi}7-E^hWN5x|hjE9u+*+3Mu1Nv>Ot=FWqSewT>pTwL=yLt5#BA~yF; z#x>om)>BsR>KlmC#tkm+Sflf@gR-}v&Vbgq;WO#F?2WL}8)dH8!cY5Sw?^GHdEibf zOTW=>;%;GlB;7ZFI_^0J_hmt5Cd^&k9}MsMyzEX+Wv5=wvX3`a`(>(V9O^Mr<{nRp zSssxp#-@=6a-QFkN}E;qX=SmBhnlQ#&&uwAq(4wQbo^viNhrha-Bpha+)rOH_uo@G zIf;Nzyq5QP`iiXubo6l@h4R4JU<%+R6EfY@LkgBG5L^$7Nj&V-Aq3WPnn;b1W z`1!s9#yL(yA6L*iRf3$gl@Y$|vR%(w+bLZSiO`A8jV7(}-*0m1&ixg|#CK@?N{hquE(*UC9->*rzIrakOBR+4zXDS5UJs@y`b zFoZKs^>EYK#ha=vTE-Wy=R{NoqOVdQRln^IQ(T?~o-PgAq1~o~N?Iu>H>$IQRWS^6<r=eAHs*lgh z(Ko)iTRq_wW!>@y+o@rDYhRsND(||ug~r$Gfg4XJvF3%KCdjEN-uzuH_uY(>kwKN_ zmK~o~rC*ypr*l_aR_qZ6@0Rz;wTCoQB20S=^Gt1|615_(I8Em03g5_I!kxZOtZe`E zdg<|J`wj?ft0@f&4aOTSlAl3czi_TQQSD4+WtmUZyPS^2aT9)jxBP3<~8;CT(#(rT6JZDi&-C4BD7IlcQIZQs@C$g4>+7D%b|EYhpZ6V@5~pD178 zlu@vVevDh7y1(|2$D8rIuWPMhEB<&q`|^5&E0Hz3bXJdNqMgi3gEv(_*K0ps*2p-P zb8&pD z{2Tl8+oj4bJKRR8;azWKC?qpiFUy)gCAM@od1qI2*v-dzv8Nch5ZdIYJ7c4Ldhok$ia{3+T6=)2&pL$}?Ji=w#ChgB> zD4E}K+4}L>BWZ=(^OB3mrX31r^HmeiW;ykKc{X*fvaZh@lfo4KlktvKsdX8HQi&J5p`T73nU!Ot_Nn;*3fYN(p|ht;i0j8l0<=@$ECH+QAt-cjak z*0VLNci=LY(@yPFDw$}o^6J1TO!|fo;csqGYu9J3K66e_U>L4xa^jk?iM?U2!>no7 zmy~Zgs%=nwF!D8BcR`y8c~|(Fr-q@o^t@(LFB}hao`QMPoDyPr+u&JTsM2ir#LABn zZD)?4h2i(lsaMuaY6l6L zA;ZOoH$HwHvZr+LVOiwUx}3Dd-FLdZI>@HQswVr3mqw+&SR0WPUSYZNZs5*&wTUJs zZ@W45CYx}ZT^#B_jnc6G zyKNSe$A||rV{_>IWJX-PMYMneyNnV*!#0zxARP&B3FOc1k%WA>=(tXNyqgS4@xuqt ztkL6l3rOc@8?mUq^rGA+{>fSvw^{{J`g!u(RN5F@w!3aydNX-kW*sZs0ypzxJ-?Z= z;*Yb=opO!SYdsF`?PuR-)au(7<%F_UW-eXl?ibKHZp)IJT3*`yGhEkEZ3s(!$K`cLe~z}u z&Np*5iwPn#bY{l$^ZjzJhqv#~x zCzEkUx86$g>6-@grjXso$PRKB<<4I$Ioe)v@4ot)*`y$QtA)l|%gYChKR&`aLhRg% z;EZT4ueeH3?;nWp7};#NN2OIC8v_j!I#yS>dPx~+vu_@2UKqVZqUTBkxlM6&52r9Hzc#U4Z_fJ`04hq$87AXPOWvH z;e93Vhj}|fU$lL?R2cbD{n2BoI;RbTE)H!sUz3@!Y^$2~;4Wug~51 zcy8o^?(nCiC+>726)ie`u&l|$%Zpe2a&33yt0e;u-@N}?# zR{OHm6`|g}gG&?EJ>R8O?Zemu-{gG~8@j=%>aBLJbk~BU{Ir-Q8KcsUzC5+;le#RA zVP9{WA9J?2W%WN(4kq`Tmhj+*WrxqLG2Z%MXYS@cX{FyzUN+LfHDXf#q@Nar*>(+> z<>(UeaLzABx18y>ynn*k!lAb#N6kpwxV!3<_XEo(L*I&6ot|w8$&_|lYa)8yI=lC? zmsVG&e|={1ql*S-mxIf$=k~wUvT8teFUo3bKb3OJ!YW~At z)M5KdAO84Srg-QD%kIqsujHg1tXYz==%~h6d~|MXQSK|jWgCjet#>-OGkVJ9Z&yc* zduwf2=@-doKXcEBzmpPmXZ6XLRgSLqU1^%Q9p`$y8GG3-N7 zjb26mYlgI1_+9zqlZHo^FDIFj#J0!X$IiJ`Q{^=H-TPx^z7*Fy%rJtFsW(RuieMTM(oO*y6~>wMwiDGOQp3_5}Zrc?5xu*6mzFeACLnm(O+yym@uhqv<{R zkK32G|MV!kx!_{=O+E#l_Bs8_a{LSGzMnCf{N!`H*@TP1w}bN?K5ILvAnVw*aYvd3 z-&)soYZv?8f4zQX`deM*_g62ggg*|mZF|hBJ$vU3nzyif)B=zBuV%kCDC2PPg3s%& z7Q%1MN}CWZMXMLNF7EMS{AusXRnzO1jBuUv^X+SIoY5}s&@_;2J$dM>z*O&Jck}z- zkK6c<$4&iez1liEnp5J_>^8G8x#6RA^c&8O5@RZjs^McmfI4b?s#zUjwj1M(cB48n zeY9OR42uX?hnrIp?PF9#qtOQiIXnKAO+au)s?jvoY*&poCt6YhJ%8H1+f!wU5A=*- z5t@k9-sUluzL_?2WafYBRMr0icw`GC#9tMX9Rj0F7~m3p2Sb^)oKLKAjbe(e1iXw5W|y1@Ga1DjNP8tLoN6bwVV-r_8(-k#;2Riwx9@$$!1NnCfJKNsA8exOfZq+~5To53Bx(e`uSVx9kSKxpYjplZ z^3`blHJSjm!m}Y|gf-rhF#dm&@+r^18gQsUM8y9bljP*z!V(eTA7VA7%S{m$5|o~9 ziTBs`>fJL$3Ke{Hp;Yjtx?YlRFTJ+6Z-^lE)ax~&ER@m)nT^xRLctQ2l4duin9P4A zx2Mt~;#L2FAIJYd8LGbv`BPE;J6wN<>rX}CPdWeHyZ#Q>pNhbra{jw_{kd@cihY2?TnMgSq&BvnY_BS`>s>(o&O*<6(LeWw9rjRd!ptSzZG`9#57K z+NZo5mJq{+m=n~ImQ1rsVp?@ftV&Q(l{iAB9%8lH;fqE!L>-orU{y(SLGU>wHXu|8 zk$U&k_M(C=w3h_aC73HcJyi<&_a0Dy!IES*+th|6m|cdLP1bnxZ%Y?}GTqZlmyA4i zqUqe>L!YUry`QMPmhh3exnu>SNy!;1Tb#@>rY zHkI7k{mWI%udWzezd38E!zb#UU{Xis@w1YcWb-oxWQXjZrS)?e({67S|6yJ^yo&9gQ|x2szE`Y_>()MJ*cwTaPnBQjsBJ@a+e=5PO5 zH=^E9`)z*Z+T)J`)(EXW(sYcxF{^9N&Z{+frygyO*|u-+?ZrJi7v*=)fmQCO@2pav zI`m#nwab$4bD#PCQ^RYqxt|N$gl8vj?B+1KO^fnDElLtP%xcwP`rr;{^S-EAlX=Q_ zOkpgjnlF{6*mV@Y_Y5~=pJoZ`pk*W4~-)yIxH*exnRWl+jlaTd~kl0dzM$Ku4PHS zuJy8#L%Lo1gl|@69=Ld;%S4ZnrNX^`&MF&sBYEO;-sktCIh@FS?$euppNdc1Wy!sOuEG$EC3C2#lw0B0^clLPQ3yi*KeAM!tQd5T8zzYZOwehH^xv33!Z}<6v z49CZN$gr5}rG<9}tSfWtQM1iy_muWG0+yC+y5}8NxyIX6&xMwrBlEJb!9hYKU&1`;u8N^_jV%Z1&l&5-IDo z*rSubB@zt}{Jbe`^Evj+4D zU9tSo={43O-M+l(kZ07HOBcdA=6)EyHac)c+~#JhHxIDnR2$}aech_oCoQT+W~V!q zDb?lQC&X*zk4vjhH?v>zpX2VFD<)bG6s=!BwEx(ZtY1e<&P#o)E6N6c_r3k7_ORQ| zoEZk=iUYIH`lQ+Ydo4a$eY9^pWuET&IS&joW&1QsDc#m_?}3}u7uHzoq;-wv--rdv8{=K2ug~9DLo! zOxfp)_vddJd}7TQ$}&6ctbDnzZHnaBX=lz5L*$B24^<9#Ih$Q>dsB7b6yZUdTTlap6Jk|u(^XVGP}g5MaiHR6$xX8CO=y~+Nmhc znv!+sL~GMV)|GA+fH$w$H0){tk6*x4HNQM4sz@Wmwt><0N(T#rrd= z%005~f15nHYE@-SinYmx$X`q-t1@in1ZPJLfJO)ZJo%Bd@SPE+lK<<;j{uaCe^e>I z+XP9+*+e~Th$;LeoknRROaP2E_9HsV`|0#V;U~f-8dcZ`5*X(v5xr6`)##|QE>xq}Dq}?jjfS`J@uPxBmE}@_>Xb2{f+Q$o1vV~zRM2wm z0TTpCuh2_HLBo|RijuM|sYvy_pQPpLBI*TYOc|j>S+2x1T>KkkMzo6fflaH7y`a%)l=TCf zs8#3{n3gE}fv8~+t;TIBY9ubEqDBu^rJr6D6zu_iRHM))N+KnSzAHjfF0EdqjPrxU ziWrD`iAl;j>-Cf>%hl_3qT=}_8bb66KcdmDcvJ7}S^h!US zmMF(R0x6Z{%3}zOiQL!;ZO+-?nc-~$ViOS?L2MMUF~lYzHe|eq{>Rl1#_>irqGwbY zXE_G&{5JAyupv62Kl6Tw4jrNcoqCF0F|098UcaKfXOggy$l{ zb67?o`SChLCF3E^BEoZ6M)3I|JcmUD;)m!!crGG57ZIL|2+u{B%PZSRgucPql3zY-CiiwMtQF^<#^(Sh(>6p{8ocrGG57ZIL|2+u`?=OV&$ zksvw{o{I!&4<);yJ@gP4u01HyuVAglwJ*YR5#hPKhUL>DIw;Z}2+!qpD3>3?a}nXW zi11uQcrG%2+!f)n8ecA=L&339 zq&*Oxi^%$1M0hSDJQoq3i%NFGriBKc53ZjgJQrcViPM4bT;3nz{g8fz@LWWA4jX5D zehAM+CA(#9z+_Gv!gCSfxrp#wM0hSL*X&RrY%=n7MtCkFJQoq3%X`;cOc9=o$ogDF zcrGG5hs|rQ41(~SAnSA3{zB67&j&$xPGDb8sSUQy5F6jG2*PuM@SGq#Cj#HE2*PuM z@SGq#CkW5wy*W-B!gB(9mYkoG@zAdb!gHczH}rdK|BoO%CkW38!gGSG&k4eFg7BOm zJSPax3Bq%N@SGq#CkW4plHGD#lzX&tOc9>LW-i|zO2*4+5uOu-=WxJ)Q+XGIm5kG#sf>Ue657B|}98S0Sw8;9LDj6@w z3gJ0Lcuo>NMR-mTp3C=# zxH=;|rwGp}!gGqO&nd!litwBwJZA{c8Nzdh@SGt$hYL=eUWTmCnUe9)4;aF8hVYyr zJZA{cGlb_1;W;N7;W7l`$Iq`A!gIKUi=^c_$Pk{x1q41V!gIJ8iTEMyf$$s-!I8Ac z^MUZ3Av|XY&*9*iF9YE@LwL>*o->5!4B* zo->5!4BLU=AAJeLrjO9;ryC zd(v=)9>zrQ1@9;Cj=&;R2jh#vCdl(=rA-HQR@mVBhGOpvCYbX4w2>d!bkKJj+eFwV zRFn&oL_LhL3P0GwQ{3}_iKDy+-`EdECK!tw+n@&lE0i|49sh^TZZlev%r<97_?&u%)oBh{UewYL=bV|;SUAlx0G&no{_8ySN+429ScIrVk^INK4E2Wm- Xe~q*!!&i9IfGR7l)xz5hPb&UjD+ar@ literal 0 HcmV?d00001 diff --git a/README.md b/README.md new file mode 100644 index 00000000..53c52593 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ArenaROS + +Arena SDK development on ROS \ No newline at end of file diff --git a/catkin_ws/inc/image_encodings.h b/catkin_ws/inc/image_encodings.h new file mode 100644 index 00000000..e73619c8 --- /dev/null +++ b/catkin_ws/inc/image_encodings.h @@ -0,0 +1,216 @@ + +/********************************************************************* + * Software License Agreement (BSD License) + * + * Copyright (c) 2009, Willow Garage, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of the Willow Garage nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *********************************************************************/ + +#ifndef SENSOR_MSGS_IMAGE_ENCODINGS_H +#define SENSOR_MSGS_IMAGE_ENCODINGS_H + +#include +#include +#include + +namespace sensor_msgs { +namespace image_encodings { +const std::string RGB8 = "rgb8"; +const std::string RGBA8 = "rgba8"; +const std::string RGB16 = "rgb16"; +const std::string RGBA16 = "rgba16"; +const std::string BGR8 = "bgr8"; +const std::string BGRA8 = "bgra8"; +const std::string BGR16 = "bgr16"; +const std::string BGRA16 = "bgra16"; +const std::string MONO8 = "mono8"; +const std::string MONO16 = "mono16"; +const std::string CONFIDENCE16 = "confidence16"; +const std::string COORD3D_ABC16 = "coord3d_abc16"; +const std::string COORD3D_ABCY16 = "coord3d_abcy16"; + +// OpenCV CvMat types +const std::string TYPE_8UC1 = "8UC1"; +const std::string TYPE_8UC2 = "8UC2"; +const std::string TYPE_8UC3 = "8UC3"; +const std::string TYPE_8UC4 = "8UC4"; +const std::string TYPE_8SC1 = "8SC1"; +const std::string TYPE_8SC2 = "8SC2"; +const std::string TYPE_8SC3 = "8SC3"; +const std::string TYPE_8SC4 = "8SC4"; +const std::string TYPE_16UC1 = "16UC1"; +const std::string TYPE_16UC2 = "16UC2"; +const std::string TYPE_16UC3 = "16UC3"; +const std::string TYPE_16UC4 = "16UC4"; +const std::string TYPE_16SC1 = "16SC1"; +const std::string TYPE_16SC2 = "16SC2"; +const std::string TYPE_16SC3 = "16SC3"; +const std::string TYPE_16SC4 = "16SC4"; +const std::string TYPE_32SC1 = "32SC1"; +const std::string TYPE_32SC2 = "32SC2"; +const std::string TYPE_32SC3 = "32SC3"; +const std::string TYPE_32SC4 = "32SC4"; +const std::string TYPE_32FC1 = "32FC1"; +const std::string TYPE_32FC2 = "32FC2"; +const std::string TYPE_32FC3 = "32FC3"; +const std::string TYPE_32FC4 = "32FC4"; +const std::string TYPE_64FC1 = "64FC1"; +const std::string TYPE_64FC2 = "64FC2"; +const std::string TYPE_64FC3 = "64FC3"; +const std::string TYPE_64FC4 = "64FC4"; + +// Bayer encodings +const std::string BAYER_RGGB8 = "bayer_rggb8"; +const std::string BAYER_BGGR8 = "bayer_bggr8"; +const std::string BAYER_GBRG8 = "bayer_gbrg8"; +const std::string BAYER_GRBG8 = "bayer_grbg8"; +const std::string BAYER_RGGB16 = "bayer_rggb16"; +const std::string BAYER_BGGR16 = "bayer_bggr16"; +const std::string BAYER_GBRG16 = "bayer_gbrg16"; +const std::string BAYER_GRBG16 = "bayer_grbg16"; + +// Miscellaneous +// This is the UYVY version of YUV422 codec http://www.fourcc.org/yuv.php#UYVY +// with an 8-bit depth +const std::string YUV422 = "yuv422"; + +// Prefixes for abstract image encodings +const std::string ABSTRACT_ENCODING_PREFIXES[] = { + "8UC", "8SC", "16UC", "16SC", "32SC", "32FC", "64FC"}; + +// Utility functions for inspecting an encoding string +static inline bool isColor(const std::string &encoding) { + return encoding == RGB8 || encoding == BGR8 || encoding == RGBA8 || + encoding == BGRA8 || encoding == RGB16 || encoding == BGR16 || + encoding == RGBA16 || encoding == BGRA16; +} + +static inline bool isMono(const std::string &encoding) { + return encoding == MONO8 || encoding == MONO16 || encoding == CONFIDENCE16; +} + +static inline bool isBayer(const std::string &encoding) { + return encoding == BAYER_RGGB8 || encoding == BAYER_BGGR8 || + encoding == BAYER_GBRG8 || encoding == BAYER_GRBG8 || + encoding == BAYER_RGGB16 || encoding == BAYER_BGGR16 || + encoding == BAYER_GBRG16 || encoding == BAYER_GRBG16; +} + +static inline bool hasAlpha(const std::string &encoding) { + return encoding == RGBA8 || encoding == BGRA8 || encoding == RGBA16 || + encoding == BGRA16; +} + +static inline int numChannels(const std::string &encoding) { + // First do the common-case encodings + if (encoding == MONO8 || encoding == MONO16 || encoding == CONFIDENCE16) + return 1; + if (encoding == BGR8 || encoding == RGB8 || encoding == BGR16 || + encoding == RGB16) + return 3; + if (encoding == BGRA8 || encoding == RGBA8 || encoding == BGRA16 || + encoding == RGBA16) + return 4; + if (encoding == BAYER_RGGB8 || encoding == BAYER_BGGR8 || + encoding == BAYER_GBRG8 || encoding == BAYER_GRBG8 || + encoding == BAYER_RGGB16 || encoding == BAYER_BGGR16 || + encoding == BAYER_GBRG16 || encoding == BAYER_GRBG16) + return 1; + + // Now all the generic content encodings + // TODO: Rewrite with regex when ROS supports C++11 + for (size_t i = 0; i < sizeof(ABSTRACT_ENCODING_PREFIXES) / + sizeof(*ABSTRACT_ENCODING_PREFIXES); + i++) { + std::string prefix = ABSTRACT_ENCODING_PREFIXES[i]; + if (encoding.substr(0, prefix.size()) != prefix) + continue; + if (encoding.size() == prefix.size()) + return 1; // ex. 8UC -> 1 + int n_channel = + atoi(encoding.substr(prefix.size(), + encoding.size() - prefix.size()) + .c_str()); // ex. 8UC5 -> 5 + if (n_channel != 0) { + return n_channel; // valid encoding string + } + } + + if (encoding == YUV422) + return 2; + + throw std::runtime_error("Unknown encoding " + encoding); + return -1; +} + +static inline int bitDepth(const std::string &encoding) { + if (encoding == MONO16 || encoding == CONFIDENCE16 || + encoding == COORD3D_ABC16 || encoding == COORD3D_ABCY16) + return 16; + if (encoding == MONO8 || encoding == BGR8 || encoding == RGB8 || + encoding == BGRA8 || encoding == RGBA8 || encoding == BAYER_RGGB8 || + encoding == BAYER_BGGR8 || encoding == BAYER_GBRG8 || + encoding == BAYER_GRBG8) + return 8; + + if (encoding == MONO16 || encoding == BGR16 || encoding == RGB16 || + encoding == BGRA16 || encoding == RGBA16 || encoding == BAYER_RGGB16 || + encoding == BAYER_BGGR16 || encoding == BAYER_GBRG16 || + encoding == BAYER_GRBG16) + return 16; + + // Now all the generic content encodings + // TODO: Rewrite with regex when ROS supports C++11 + for (size_t i = 0; i < sizeof(ABSTRACT_ENCODING_PREFIXES) / + sizeof(*ABSTRACT_ENCODING_PREFIXES); + i++) { + std::string prefix = ABSTRACT_ENCODING_PREFIXES[i]; + if (encoding.substr(0, prefix.size()) != prefix) + continue; + if (encoding.size() == prefix.size()) + return atoi(prefix.c_str()); // ex. 8UC -> 8 + int n_channel = + atoi(encoding.substr(prefix.size(), + encoding.size() - prefix.size()) + .c_str()); // ex. 8UC10 -> 10 + if (n_channel != 0) + return atoi(prefix.c_str()); // valid encoding string + } + + if (encoding == YUV422) + return 8; + + throw std::runtime_error("Unknown encoding " + encoding); + return -1; +} +} // namespace image_encodings +} // namespace sensor_msgs + +#endif diff --git a/catkin_ws/src/CMakeLists.txt b/catkin_ws/src/CMakeLists.txt new file mode 120000 index 00000000..581e61db --- /dev/null +++ b/catkin_ws/src/CMakeLists.txt @@ -0,0 +1 @@ +/opt/ros/kinetic/share/catkin/cmake/toplevel.cmake \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/.gitignore b/catkin_ws/src/arena_camera/.gitignore new file mode 100644 index 00000000..3f94b114 --- /dev/null +++ b/catkin_ws/src/arena_camera/.gitignore @@ -0,0 +1,19 @@ +############# +## folders ## +############# +build +cmake-* + +############# +## files ## +############# +*.*~ +*.*user.* +*.user +*.autosave +CATKIN_IGNORE +*.orig +*~ +*.idea +*.pyc +*.save diff --git a/catkin_ws/src/arena_camera/CMakeLists.txt b/catkin_ws/src/arena_camera/CMakeLists.txt new file mode 100644 index 00000000..8d6a28f3 --- /dev/null +++ b/catkin_ws/src/arena_camera/CMakeLists.txt @@ -0,0 +1,263 @@ +cmake_minimum_required(VERSION 2.8.3) + +# +# DEV NOTE +# - relative path are relative to this file +# + +project(arena_camera) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# flags for all C++ targets +add_definitions("-std=gnu++11") + + +# ----------------------------------------------------------------------------- +# +# CATKIN +# +# ----------------------------------------------------------------------------- +# +set(CATKIN_COMPONENTS + actionlib + camera_control_msgs + camera_info_manager + cv_bridge + diagnostic_updater + image_geometry + image_transport + roscpp + roslint + roslaunch + sensor_msgs +) + +## Find catkin macros and libraries +## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) +## is used, also find other catkin packages +find_package( + catkin REQUIRED + COMPONENTS + ${CATKIN_COMPONENTS} +) + +# http://wiki.ros.org/catkin/CMakeLists.txt#catkin_package.28.29 +catkin_package( + INCLUDE_DIRS include + LIBRARIES ${PROJECT_NAME} + CATKIN_DEPENDS ${CATKIN_COMPONENTS} +) + +# ----------------------------------------------------------------------------- +# +# ROS +# +# ----------------------------------------------------------------------------- +# +set(ROSLINT_CPP_OPTS + "--extensions=cpp,h,hpp" "--filter=-runtime/references,-readability/todo,-build/include_what_you_use" +) + +# check c/c++ static checking. More http://wiki.ros.org/roslint +roslint_cpp() # all .h .cpp files + +# closest doc (http://wiki.ros.org/rosbuild/CMakeLists#rosbuild_add_roslaunch_check) +# more https://answers.ros.org/question/200359/purpose-of-roslaunch_add_file_check/ +roslaunch_add_file_check(launch) + +# ----------------------------------------------------------------------------- +# +# ARENA +# TODO +# Make the dirs read from Arena_SDK.conf +# +# ----------------------------------------------------------------------------- +# +# ADDS : +# - ${_ARENA_ROOT} +# - ${Arena_INCLUDE_DIRS} +# - ${Arena_LIBRARIES} +# - ${Arena_LIBRARIES_DIRS} +set(_ARENA_ROOT "$ENV{ARENA_ROOT}") +set(Arena_INCLUDE_DIRS + "${_ARENA_ROOT}/GenICam/library/CPP/include" +) +set(Arena_LIBRARIES + "arena" + "save" + "gentl" + "GenApi_gcc421_v3_0" + "GCBase_gcc421_v3_0" +) +set(Arena_LIBRARIES_DIRS +"${_ARENA_ROOT}/lib64/" +"${_ARENA_ROOT}/GenICam/library/lib/Linux64_x64/" +) + +# ----------------------------------------------------------------------------- +# +# GENERAL +# +# ----------------------------------------------------------------------------- +# + +# headers for compiler +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${catkin_INCLUDE_DIRS} + ${Arena_INCLUDE_DIRS} +) + +link_directories( + ${Arena_LIBRARIES_DIRS} +) + + +# ----------------------------------------------------------------------------- +# +# ARENA_CAMERA +# +# ----------------------------------------------------------------------------- +# + +# create a virtual tree for this project +# generates *.o +# https://cmake.org/cmake/help/latest/command/add_library.html +add_library( + ${PROJECT_NAME} + src/${PROJECT_NAME}_node.cpp + src/${PROJECT_NAME}_parameter.cpp + src/${PROJECT_NAME}.cpp + src/encoding_conversions.cpp +) + +# catkin first then arena_camera +# the first argument must be created before using add_library +# https://cmake.org/cmake/help/latest/command/add_dependencies.html +add_dependencies( + ${PROJECT_NAME} + ${catkin_EXPORTED_TARGETS} +) + +target_link_libraries( + ${PROJECT_NAME} + ${catkin_LIBRARIES} + ${Arena_LIBRARIES} +) + +# ----------------------------------------------------------------------------- +# +# ARENA_CAMERA_NODE +# +# ----------------------------------------------------------------------------- +# + +# compile the cpp and create an executable +add_executable( + ${PROJECT_NAME}_node # runs by rosrun + src/main.cpp +) + +target_link_libraries( + ${PROJECT_NAME}_node + ${catkin_LIBRARIES} + ${Arena_LIBRARIES} + ${PROJECT_NAME} +) + +# ----------------------------------------------------------------------------- +# +# WRITE_DEVICE_USER_ID_TO_CAMERA +# +# ----------------------------------------------------------------------------- +# + +# compile the cpp and create an executable +add_executable( + write_device_user_id_to_camera + src/write_device_user_id_to_camera.cpp +) + +target_link_libraries( + write_device_user_id_to_camera + ${catkin_LIBRARIES} + ${Arena_LIBRARIES} + ${PROJECT_NAME} +) + +# This macro ensures modules and global scripts declared therein get installed +# See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html +catkin_python_setup() + + +# ----------------------------------------------------------------------------- +# +# INSTALL +# +# ----------------------------------------------------------------------------- +# + +install( + DIRECTORY + launch/ + DESTINATION + ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch + FILES_MATCHING PATTERN "*.launch" +) + +install( + DIRECTORY + config/ + DESTINATION + ${CATKIN_PACKAGE_SHARE_DESTINATION}/config + FILES_MATCHING PATTERN "*.yaml" +) + +install( + PROGRAMS + scripts/file_sequencer.py + scripts/grab_and_save_image_action_server.py + scripts/result_bag_to_action.py + scripts/sequence_to_file.py + scripts/toggle_camera + DESTINATION + ${CATKIN_PACKAGE_SHARE_DESTINATION} +) + +install( + TARGETS + ${PROJECT_NAME} + ${PROJECT_NAME}_node + write_device_user_id_to_camera + LIBRARY DESTINATION + ${CATKIN_PACKAGE_LIB_DESTINATION} + RUNTIME DESTINATION + ${CATKIN_PACKAGE_BIN_DESTINATION} +) + +install( + DIRECTORY + include/${PROJECT_NAME}/ + DESTINATION + ${CATKIN_PACKAGE_INCLUDE_DESTINATION} + FILES_MATCHING PATTERN + "*.h" + PATTERN "internal" EXCLUDE +) + +## Testing ## +# All Jenkins-Tests are now in the arena_camera_tests-pkg +############# + +############### +## QtCreator ## +############### +# entry for QtCreator to show all files +file(GLOB children ${CMAKE_CURRENT_SOURCE_DIR}/*) +foreach(child ${children}) + if(IS_DIRECTORY ${child}) + file(GLOB_RECURSE dir_files "${child}/*") + list(APPEND ${PROJECT_NAME}_extra_files ${dir_files}) + endif() +endforeach() +#add_custom_target(dummy_${PROJECT_NAME} SOURCES ${${PROJECT_NAME}_extra_files}) \ No newline at end of file diff --git a/LICENSE b/catkin_ws/src/arena_camera/LICENSE similarity index 100% rename from LICENSE rename to catkin_ws/src/arena_camera/LICENSE diff --git a/README.rst b/catkin_ws/src/arena_camera/README.rst similarity index 74% rename from README.rst rename to catkin_ws/src/arena_camera/README.rst index d5377a7d..b0ab36c0 100644 --- a/README.rst +++ b/catkin_ws/src/arena_camera/README.rst @@ -1,23 +1,18 @@ ==== -**New Official Version of Driver available** +**ROS-Driver for Lucid Cameras** ==== -The new official driver (an extended version of this package with some bug-fixes and new functionality) is available at https://github.com/basler/pylon-ros-camera. +**developed by Magazino GmbH, using the arena Software Camera Suite by Lucid AG** -==== -**ROS-Driver for Basler Cameras** -==== -**developed by Magazino GmbH, using the pylon Software Camera Suite by Basler AG** +This package offers many functions of the Lucid arena API inside the ROS-Framwork. -This package offers many functions of the Basler pylon API inside the ROS-Framwork. - -The package supports Baslers USB 3.0, GigE as well as the DART cameras. +The package supports Lucids USB 3.0, GigE as well as the DART cameras. Images can continuously be published over *\/image\_raw* or the *\/image\_rect* topic. The latter just in case the intrinsic calibration matrices are provided through the **camera_info_url** parameter. -The camera-characteristic parameter such as hight, width, projection matrices and camera_frame were published over the *\/camera\_info* topic. +The camera-characteristic parameter such as height, width, projection matrices and camera_frame were published over the *\/camera\_info* topic. Furthermore an action-based image grabbing with desired exposure, gain, gamma and / or brightness is provided. Hence one can grab a sequence of images with above target settings as well as a single image. @@ -34,29 +29,9 @@ The package opens either a predefined camera (using a given 'device_user_id' par ****** **Installation** ****** -The package has been tested for ROS-Indigo and ROS-Kinetic. - -The pylon_camera-pkg requires the pylonSDK to be installed on your system. Please download and install the pylon debian package for your architecture from: - -``https://www.baslerweb.com/de/support/downloads/downloads-software/`` - -In order to build the package, you need to configure rosdep (i.e. the ROS command-line tool for checking and installing system dependencies for ROS packages) such that -it knows how to resolve this dependency. This can be achieved by executing the following commands: - -``sudo sh -c 'echo "yaml https://raw.githubusercontent.com/magazino/pylon_camera/indigo-devel/rosdep/pylon_sdk.yaml " > /etc/ros/rosdep/sources.list.d/15-plyon_camera.list'`` - -``rosdep update`` - -Then, clone the pylon_camera-pkg, and the camera_control_msgs-pkg and install the pylon SDK in your catkin_ws: - -``cd ~/catkin_ws/src/ && git clone https://github.com/magazino/pylon_camera.git && git clone https://github.com/magazino/camera_control_msgs.git`` - -``rosdep install --from-paths . --ignore-src --rosdistro=$ROS_DISTRO -y`` - -Build the pylon_camera package as you would build a standard ROS-package unsing p.e. - -``cd ~/catkin_ws && catkin_make`` +The package has been tested for ROS-Kinetic. +Please check the attached pdf for installation instructions. | ****** @@ -111,7 +86,7 @@ The following settings do **NOT** have to be set. Each camera has default values The average intensity value of the images. It depends the exposure time as well as the gain setting. If '**exposure**' is provided, the interface will try to reach the desired brightness by only varying the gain. (What may often fail, because the range of possible exposure values is many times higher than the gain range). If '**gain**' is provided, the interface will try to reach the desired brightness by only varying the exposure time. If '**gain**' AND '**exposure**' are given, it is not possible to reach the brightness, because both are assumed to be fix. - **brightness_continuous** - Only relevant, if '**brightness**' is set: The brightness_continuous flag controls the auto brightness function. If it is set to false, the brightness will only be reached once. Hence changing light conditions lead to changing brightness values. If it is set to true, the given brightness will be reached continuously, trying to adapt to changing light conditions. This is only possible for values in the possible auto range of the pylon API which is e.g. [50 - 205] for acA2500-14um and acA1920-40gm + Only relevant, if '**brightness**' is set: The brightness_continuous flag controls the auto brightness function. If it is set to false, the brightness will only be reached once. Hence changing light conditions lead to changing brightness values. If it is set to true, the given brightness will be reached continuously, trying to adapt to changing light conditions. This is only possible for values in the possible auto range of the arena API which is e.g. [50 - 205] for acA2500-14um and acA1920-40gm - **exposure_auto & gain_auto** Only relevant, if '**brightness**' is set: If the camera should try to reach and / or keep the brightness, hence adapting to changing light conditions, at least one of the following flags must be set. If both are set, the interface will use the profile that tries to keep the gain at minimum to reduce white noise. The exposure_auto flag indicates, that the desired brightness will be reached by adapting the exposure time. The gain_auto flag indicates, that the desired brightness will be reached by adapting the gain. @@ -129,16 +104,16 @@ The following settings do **NOT** have to be set. Each camera has default values **Usage** ****** -The pylon_camera_node can be started over the launch file which includes a config file with desired parameters as frame rate or exposure time +The arena_camera_node can be started over the launch file which includes a config file with desired parameters as frame rate or exposure time -``roslaunch pylon_camera pylon_camera_node.launch`` or ``rosrun pylon_camera pylon_camera_node`` +``roslaunch arena_camera arena_camera_node.launch`` or ``rosrun arena_camera arena_camera_node`` Images were only published if another node connects to the image topic. The published images can be seen using the image_view node from the image_pipeline stack: -``rosrun image_view image_view image:=/pylon_camera_node/image_raw`` +``rosrun image_view image_view image:=/arena_camera_node/image_raw`` ****** **Questions** ****** -Please provide your questions via http://answers.ros.org/questions/ and tag them with **pylon_camera** +Please provide your questions via http://answers.ros.org/questions/ and tag them with **arena_camera** diff --git a/config/default.yaml b/catkin_ws/src/arena_camera/config/default.yaml similarity index 96% rename from config/default.yaml rename to catkin_ws/src/arena_camera/config/default.yaml index 6e517ac6..0657e87a 100644 --- a/config/default.yaml +++ b/catkin_ws/src/arena_camera/config/default.yaml @@ -1,5 +1,8 @@ +# NOTE +# USED BY the package .launch files + # The tf frame under which the images were published -camera_frame: pylon_camera +camera_frame: arena_camera # The DeviceUserID of the camera. If empty, the first camera found in the # device list will be used @@ -76,7 +79,7 @@ frame_rate: 5.0 # Hence changing light conditions lead to changing brightness values. # If it is set to true, the given brightness will be reached continuously, # trying to adapt to changing light conditions. This is only possible for -# values in the possible auto range of the pylon API which is e.g. [50 - 205] +# values in the possible auto range of the arena API which is e.g. [50 - 205] # for acA2500-14um and acA1920-40gm # brightness_continuous: true @@ -90,9 +93,8 @@ frame_rate: 5.0 # reached by adapting the exposure time. # The gain_auto flag indicates, that the desired brightness will be # reached by adapting the gain. -# exposure_auto: true -# gain_auto: true - +exposure_auto: true +gain_auto: true ########################################################################## # The timeout while searching the exposure which is connected to the diff --git a/catkin_ws/src/arena_camera/include/arena_camera/Arena.h b/catkin_ws/src/arena_camera/include/arena_camera/Arena.h new file mode 100644 index 00000000..5b4e9d19 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/Arena.h @@ -0,0 +1,89 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ +#pragma once + +#include "ISystem.h" + +namespace Arena +{ +/** + * @fn ISystem* OpenSystem() + * + * @return + * - Type: ISystem* + * - The system object + * + * OpenSystem initializes the Arena SDK and retrieves the system + * object (Arena::ISystem). The system must be closed or memory will leak. + * + * \code{.cpp} + * // opening and closing a system + * { + * Arena::ISystem* pSystem = Arena::OpenSystem(); + * // ... + * Arena::CloseSystem(pSystem); + * } + * \endcode + * + * @warning + * - Only one system may be opened at a time + * - System must be closed + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::ISystem + * - Arena::OpenSystem + * - Arena::CloseSystem + */ +ARENA_API ISystem* OpenSystem(); + +/** + * @fn void CloseSystem(ISystem* pSystem) + * + * @param pSystem + * - Type: ISystem* + * - The system object + * + * @return + * - none + * + * CloseSystem cleans up the system (Arena::ISystem) and + * deinitializes the Arena SDK, deallocating all memory. + * + * @warning + * - The system must be closed + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::ISystem + */ +ARENA_API void CloseSystem(ISystem* pSystem); + +/** + * uint64_t CalculateMaximumNumberOfBuffers(size_t payloadSize) + * + * @param payloadSize + * - Type: size_t + * - Unit: bytes + * - Payload size of an image + * + * @return + * - Type: uint64_t + * - Maximum number of buffers + * + * CalculateMaximumNumberOfBuffers calculates the number of + * buffers it would take to fill 80% of the available memory. + */ +ARENA_API size_t CalculateMaximumNumberOfBuffers(size_t payloadSize); +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/ArenaApi.h b/catkin_ws/src/arena_camera/include/arena_camera/ArenaApi.h new file mode 100644 index 00000000..b8c29217 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/ArenaApi.h @@ -0,0 +1,50 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ +#pragma once + +#if (defined _WIN32 || defined _WIN64) +#ifdef ARENA_EXPORTS +#define ARENA_API __declspec(dllexport) +#else +#define ARENA_API __declspec(dllimport) +#endif +#else +#define ARENA_API +#endif + +#if defined __linux__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif +#include +#include +#include + +#if defined __linux__ +#pragma GCC diagnostic pop +#endif + +#include "Arena.h" +#include "ArenaDefs.h" +#include "DeviceInfo.h" +#include "FeatureStream.h" +#include "GenApiCustom.h" +#include "IBuffer.h" +#include "IChunkData.h" +#include "IDevice.h" +#include "IImage.h" +#include "ImageFactory.h" +#include "ISystem.h" +#include "PFNC.h" +#include "PFNCCustom.h" diff --git a/catkin_ws/src/arena_camera/include/arena_camera/Buffer.h b/catkin_ws/src/arena_camera/include/arena_camera/Buffer.h new file mode 100644 index 00000000..8f52aa0f --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/Buffer.h @@ -0,0 +1,95 @@ +#pragma once +#include "IBuffer.h" +#include "TLDataStream.h" + +namespace Arena +{ + template + T GetInfo(TLDataStream* pDataStream, GenTL::BUFFER_HANDLE hBuffer, GenTL::BUFFER_INFO_CMD cmd) + { + // check for logical errors + if (!pDataStream) + { + THROW_TYPE( + InvalidArgumentException, + "DataStream is null"); + } + + if (!hBuffer) + { + THROW_TYPE( + InvalidArgumentException, + "Buffer is null"); + } + + // get uint64_t value + T value = 0; + size_t valueLen = sizeof(value); + GenTL::INFO_DATATYPE type; + + GenTL::GC_ERROR err = pDataStream->GetBufferInfo(hBuffer, cmd, &type, &value, &valueLen); + if (err != GenTL::GC_ERR_SUCCESS) + { + THROW_DEFAULT( + err, + "Unable to get buffer information", + "DSGetBufferInfo"); + } + + return value; + } + + template + void Cleanup(T*& pVal) + { + if (pVal != NULL) + { + delete pVal; + pVal = NULL; + } + } + + class ARENA_TEST_API Buffer : public IBuffer + { + public: + Buffer(TLDataStream* pStream, GenTL::BUFFER_HANDLE hBuffer); + Buffer(Buffer* pBuffer); + Buffer(); + virtual ~Buffer(); + + //IBuffer methods + virtual const uint8_t* GetData(); + virtual size_t GetSizeFilled(); + virtual size_t GetPayloadSize(); + virtual size_t GetSizeOfBuffer(); + virtual bool HasChunkData(); + virtual size_t GetPayloadType(); + virtual bool IsIncomplete(); + virtual uint64_t GetFrameId(); + virtual bool HasImageData(); + virtual IChunkData* AsChunkData(); + virtual IImage* AsImage(); + virtual bool DataLargerThanBuffer(); + virtual bool VerifyCRC(); + + virtual GenTL::BUFFER_HANDLE + GetBufferHandle(); + friend ImageFactory; + + protected: + TLDataStream* m_pStream; + GenTL::BUFFER_HANDLE m_hBuffer; + + // TODO: These should all be GenApi::CPtr types once we implement a GenTL nodemap + uint8_t* m_pData; + size_t* m_pSizeFilled; + size_t* m_pPayloadSize; + size_t* m_pSizeOfBuffer; + bool8_t* m_pHasChunkData; + size_t* m_pPayloadType; + bool8_t* m_pIsIncomplete; + uint64_t* m_pFrameId; + bool8_t* m_pHasImageData; + bool8_t* m_pDataLargerThanBuffer; + }; +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/Device.h b/catkin_ws/src/arena_camera/include/arena_camera/Device.h new file mode 100644 index 00000000..53ed0cc4 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/Device.h @@ -0,0 +1,108 @@ +#pragma once +#include "IDevice.h" +#include "Port.h" +#include "TLDevice.h" +#include "TLDataStream.h" +#include "TLEvent.h" +#include "StreamInfo.h" +#include "Interface.h" +#include +#include + +#include + +namespace Arena +{ + class ARENA_TEST_API Device : public IDevice + { + public: + Device(TLDevice* pDevice, Interface* pIfInfo); + virtual ~Device(); + + virtual GenApi::INodeMap* GetNodeMap(); + virtual GenApi::INodeMap* GetTLDeviceNodeMap(); + virtual GenApi::INodeMap* GetTLStreamNodeMap(); + virtual GenApi::INodeMap* GetTLStreamNodeMap(int streamNumber = 0); + virtual GenApi::INodeMap* GetTLInterfaceNodeMap(); + + virtual void StartStream(size_t numBuffers = 10); + virtual void StopStream(); + virtual IImage* GetImage(uint64_t timeout); + virtual IBuffer* GetBuffer(uint64_t timeout); + virtual void RequeueBuffer(IBuffer* pImage); + + virtual void StartStream(int streamNumber, size_t numBuffers); + virtual void StopStream(int streamNumber); + virtual IImage* GetImage(int streamNumber, uint64_t timeout); + virtual IBuffer* GetBuffer(int streamNumber, uint64_t timeout); + virtual void RequeueBuffer(int streamNumber, IBuffer* pImage); + + virtual void InitializeEvents(); + virtual void DeinitializeEvents(); + virtual void WaitOnEvent(uint64_t timeout); + + virtual void SendActionCommand(uint32_t deviceKey, uint32_t groupKey, uint32_t groupMask, uint64_t actionTime); + + virtual bool IsConnected(); + + virtual void ResetInterface() + { + std::unique_lock l(m_interfaceMtx); + m_pIfInfo = nullptr; + } + + virtual Interface* GetInterface() + { + std::unique_lock l(m_interfaceMtx); + return m_pIfInfo; + } + +#if defined(_ARENA_UNIT_TEST_API) + Device(TLDevice* pDevice, TLBase* pDeviceRemote, Interface* pifInfo, std::vector streams, std::vector hEventNewBuffers, GenApi::INodeMap* pNodeMap, GenApi::INodeMap* pDeviceNodeMap, GenApi::CChunkAdapterGEV* pChunkAdapter, GenApi::CNodeMapFactory& chunkDataNodeMapFactory) : + m_pDevice(pDevice), + m_pDeviceRemote(pDeviceRemote), + m_pIfInfo(pifInfo), + m_pTLDeviceNodeMap(pDeviceNodeMap), + m_pDeviceNodeMap(pNodeMap), + m_pTLDevicePort(pDevice), + m_pDevicePort(pDeviceRemote), + m_streams(streams), + m_hEventNewBuffers(hEventNewBuffers), + m_pChunkAdapter(pChunkAdapter), + m_chunkDataNodeMapFactory(chunkDataNodeMapFactory) + { + } + + void nullifyNodeMap() + { + m_pTLDeviceNodeMap = NULL; + m_pDeviceNodeMap = NULL; + } +#endif + + protected: + Device(); + + TLDevice* m_pDevice; + TLBase* m_pDeviceRemote; + Interface* m_pIfInfo; + + GenApi::INodeMap* m_pTLDeviceNodeMap; + GenApi::INodeMap* m_pDeviceNodeMap; + + Port m_pTLDevicePort; + Port m_pDevicePort; + + std::vector m_streams; + std::vector m_hEventNewBuffers; + + TLEvent* m_hEventRemoteDevice; + GenApi::CEventAdapter* m_pEventAdapter; + + GenApi::CChunkAdapterGEV* m_pChunkAdapter; + GenApi::CNodeMapFactory m_chunkDataNodeMapFactory; + + std::mutex m_interfaceMtx; + std::mutex m_streamMutex; + }; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/Image.h b/catkin_ws/src/arena_camera/include/arena_camera/Image.h new file mode 100644 index 00000000..b6f93174 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/Image.h @@ -0,0 +1,59 @@ +#pragma once +#include "IImage.h" +#include "Buffer.h" + +namespace Arena +{ + class TLDataStream; + class ARENA_TEST_API Image : public IImage, virtual public Buffer + { + public: + Image(TLDataStream* pStream, GenTL::BUFFER_HANDLE hBuffer); + Image(Image* pImage); + Image(); + virtual ~Image(); + + //IBuffer methods + virtual const uint8_t* GetData(); + virtual size_t GetSizeFilled(); + virtual size_t GetPayloadSize(); + virtual size_t GetSizeOfBuffer(); + virtual bool HasChunkData(); + virtual IChunkData* AsChunkData(); + virtual size_t GetPayloadType(); + virtual bool IsIncomplete(); + virtual uint64_t GetFrameId(); + virtual bool HasImageData(); + virtual IImage* AsImage(); + virtual bool DataLargerThanBuffer(); + virtual bool VerifyCRC(); + + //IImage methods + virtual size_t GetWidth(); + virtual size_t GetHeight(); + virtual size_t GetOffsetX(); + virtual size_t GetOffsetY(); + virtual uint64_t GetPixelFormat(); + virtual uint64_t GetTimestamp(); + virtual size_t GetPaddingX(); + virtual size_t GetPaddingY(); + virtual int32_t GetPixelEndianness(); + virtual uint64_t GetTimestampNs(); + virtual size_t GetBitsPerPixel(); + + friend ImageFactory; + + protected: + // TODO These should all be GenApi::CPtr types once we implement a GenTL nodemap + size_t* m_pWidth; + size_t* m_pHeight; + size_t* m_pOffsetX; + size_t* m_pOffsetY; + uint64_t* m_pPixelFormat; + uint64_t* m_pTimestamp; + size_t* m_pPaddingX; + size_t* m_pPaddingY; + int32_t* m_pPixelEndianness; + uint64_t* m_pTimestampNs; + }; +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/System.h b/catkin_ws/src/arena_camera/include/arena_camera/System.h new file mode 100644 index 00000000..33aeb19c --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/System.h @@ -0,0 +1,74 @@ +#pragma once +#include "ISystem.h" +#include "TLSystem.h" +#include "TLInterface.h" +#include "Interface.h" +#include "Port.h" +#include "PrivateGlobals.h" + +#include + +namespace Arena +{ + class ARENA_TEST_API System : public ISystem + { + public: + static ISystem* Open(); + static void Close(); + + System(TLSystem* system); + + virtual ~System(); + + virtual bool UpdateDevices(uint64_t timeout = 100); + + virtual bool UpdateDevices(InterfaceInfo ifaceInfo, uint64_t timeout = 100); + + virtual std::vector GetDevices(); + + virtual std::vector GetInterfaces(); + + virtual IDevice* CreateDevice(DeviceInfo info); + + virtual void DestroyDevice(IDevice* pDevice); + + virtual GenApi::INodeMap* GetTLSystemNodeMap(); + + virtual GenApi::INodeMap* GetTLInterfaceNodeMap(DeviceInfo devInfo); + + virtual void ForceIp(uint64_t macAddress, uint64_t ipAddress, uint64_t subnetMask, uint64_t defaultGateway); + + // used in testing +#if defined(_ARENA_UNIT_TEST_API) + System(TLSystem* system, std::vector interfaces, GenApi::INodeMap* pNodeMap) : + m_pSystem(system), + m_interfaces(interfaces), + m_pSystemNodeMap(pNodeMap), + m_pPort(system) + { + OpenImageFactory(); + } + + void nullifyNodeMap() + { + m_pSystemNodeMap = NULL; + } +#endif + + private: + System(); + + TLSystem* m_pSystem; + std::vector m_interfaces; + + GenApi::INodeMap* m_pSystemNodeMap; + Port m_pPort; + + //protects interface list from changing while accessing it + std::mutex m_ifaceListMtx; + + // protects updated devices from being called from two threads at same time + std::mutex m_updateDevsMtx; + + }; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/arena_camera.h b/catkin_ws/src/arena_camera/include/arena_camera/arena_camera.h new file mode 100644 index 00000000..3feb9dd2 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/arena_camera.h @@ -0,0 +1,494 @@ +/****************************************************************************** + * Software License Agreement (BSD License) + * + * Copyright (C) 2016, Magazino GmbH. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of Magazino GmbH nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef ARENA_CAMERA_ARENA_CAMERA_H +#define ARENA_CAMERA_ARENA_CAMERA_H + +#include +#include +#include + +#include +#include + +namespace arena_camera +{ +// Number of channels for image encoding +#define CHANNEL_MONO8 1 +#define CHANNEL_RGB8 3 + +/** + * The ArenaCamera base class. Create a new instance using the static create() functions. + */ +class ArenaCamera +{ +public: + /** + * Create a new ArenaCamera instance based on the DeviceUserID of the camera. + * @param device_user_id Arena DeviceUserID. If the string is empty, the + * first camera that could be found is returned. + * @return new ArenaCamera instance or NULL if the camera was not found. + */ + static ArenaCamera* create(const std::string& device_user_id); + + /** + * Configures the camera according to the software trigger mode. + * @return true if all the configuration could be set up. + */ + virtual bool registerCameraConfiguration() = 0; + + /** + * Opens the desired camera, the communication starts from now on. + * @return true if the camera could be opened. + */ + virtual bool openCamera() = 0; + + /** + * Returns the connection state of the camera device. + * @return true if the camera device removal from the PC has been detected. + */ + virtual bool isCamRemoved() = 0; + + /** + * Configure the sequencer exposure times. + * @param exposure_times the list of exposure times. + * @return true if all parameters could be sent to the camera. + */ + virtual bool setupSequencer(const std::vector& exposure_times) = 0; + + /** + * Configures the camera according to the provided ros parameters. + * This will use the device specific parameters as e.g. the mtu size for + * GigE-Cameras + * @param parameters The ArenaCameraParameter set to use + * @return true if all parameters could be sent to the camera. + */ + virtual bool applyCamSpecificStartupSettings(const ArenaCameraParameter& parameters) = 0; + + /** + * Initializes the internal parameters of the ArenaCamera instance. + * @param parameters The ArenaCameraParameter set to use + * @return true if all parameters could be sent to the camera. + */ + virtual bool startGrabbing(const ArenaCameraParameter& parameters) = 0; + + /** + * Grab a camera frame and copy the result into image + * @param image reference to the output image. + * @return true if the image was grabbed successfully. + */ + virtual bool grab(std::vector& image) = 0; + + /** + * Grab a camera frame and copy the result into image + * @param image pointer to the image buffer. + * Caution: Make sure the buffer is initialized correctly! + * @return true if the image was grabbed successfully. + */ + virtual bool grab(uint8_t* image) = 0; + + /** + * @brief sets shutter mode for the camera (rolling or global_reset) + * @param mode + * @return + */ + virtual bool setShutterMode(const arena_camera::SHUTTER_MODE& mode) = 0; + + /** + * Update area of interest in the camera image + * @param target_roi the target roi + * @param reached_roi the roi that could be set + * @return true if the targeted roi could be reached + */ + virtual bool setROI(const sensor_msgs::RegionOfInterest target_roi, sensor_msgs::RegionOfInterest& reached_roi) = 0; + + /** + * Sets the target horizontal binning_x factor + * @param target_binning_x the target horizontal binning_x factor. + * @param reached_binning_x the reached horizontal binning_x factor. + * @return false if a communication error occurred or true otherwise. + */ + virtual bool setBinningX(const size_t& target_binning_x, size_t& reached_binning_x) = 0; + + /** + * Sets the target vertical binning_y factor + * @param target_binning_y the target vertical binning_y factor. + * @param reached_binning_y the reached vertical binning_y factor. + * @return false if a communication error occurred or true otherwise. + */ + virtual bool setBinningY(const size_t& target_binning_y, size_t& reached_binning_y) = 0; + + /** + * Detects the supported image pixel encodings of the camera an stores + * them in a vector. + * @return a list of strings describing the supported encodings in GenAPI + * language. + */ + virtual std::vector detectAvailableImageEncodings() = 0; + + /** + * Sets the desired image pixel encoding (channel meaning, ordering, size) + * taken from the list of strings in include/sensor_msgs/image_encodings.h + * The supported encodings are 'mono8', 'bgr8', 'rgb8', 'bayer_bggr8', + * 'bayer_gbrg8', 'bayer_rggb8' and 'yuv422' + * @param target_ros_endcoding: string describing the encoding. + * @return false if a communication error occurred or true otherwise. + */ + virtual bool setImageEncoding(const std::string& target_ros_encoding) = 0; + + /** + * Sets the exposure time in microseconds + * @param target_exposure the desired exposure time to set in microseconds. + * @param reached_exposure time in microseconds + * @return false if a communication error occurred or true otherwise. + */ + virtual bool setExposure(const float& target_exposure, float& reached_exposure) = 0; + + /** + * Sets autoflash active for the specified lines + * @param flash_on_lines map from line e.g. 1 or 2 to a boolean to + activate or deactivate the autoflash for this line . + * @return false if a communication error occurred or true otherwise. + */ + virtual bool setAutoflash(const std::map flash_on_lines) = 0; + /** + * Sets the gain in percent independent of the camera type + * @param target_gain the target gain in percent. + * @param reached_gain the reached gain in percent. + * @return false if a communication error occurred or true otherwise. + */ + virtual bool setGain(const float& target_gain, float& reached_gain) = 0; + + /** + * Sets the target gamma value + * @param target_gamma the target gamma value. + * @param reached_gamma the reached gamma value. + * @return false if a communication error occurred or true otherwise. + */ + virtual bool setGamma(const float& target_gamma, float& reached_gamma) = 0; + + /** + * Sets the target brightness + * Setting the exposure time to -1 enables the AutoExposureContinuous mode. + * Setting the exposure time to 0 disables the AutoExposure function. + * If the target exposure time is not in the range of Arena's auto target brightness range + * the extended brightness search is started. + * @param target_brightness is the desired brightness. Range is [1...255]. + * @param current_brightness is the current brightness with the given settings. + * @param exposure_auto flag which indicates if the target_brightness + * should be reached adapting the exposure time + * @param gain_auto flag which indicates if the target_brightness should be + * reached adapting the gain. + * @return true if the brightness could be reached or false otherwise. + */ + virtual bool setBrightness(const int& target_brightness, const float& current_brightness, const bool& exposure_auto, + const bool& gain_auto) = 0; + + /** + * @brief Detects and counts the number of user-settable-outputs the cam + * provides. This might be zero for some cameras. The size affects + * the number of 'set' ros-services the camera_node will provide. + * A vector which length equals the number of user-settable outputs + * will be generated. Hence e.g. output '1' can be accessed via + * user_output_selector_enums_.at(1). + * @return the UserOutputSelector enum list + */ + virtual std::vector detectAndCountNumUserOutputs() = 0; + + /** + * @brief setUserOutput sets the digital output + * @param output_id + * @param value goal value for output + * @return true if value was set + */ + virtual bool setUserOutput(const int& output_id, const bool& value) = 0; + + /** + * Returns the current x offset setting. + * @return the horizontal x offset setting. + */ + virtual size_t currentOffsetX() = 0; + + /** + * Returns the current y offset setting. + * @return the horizontal y offset setting. + */ + virtual size_t currentOffsetY() = 0; + + /** + * Returns the current roi setting. + * @return the roi setting. + */ + virtual sensor_msgs::RegionOfInterest currentROI() = 0; + + /** + * Returns the current horizontal binning_x setting. + * @return the horizontal binning_x setting. + */ + virtual size_t currentBinningX() = 0; + + /** + * Returns the current vertical binning_y setting. + * @return the vertical binning_y setting. + */ + virtual size_t currentBinningY() = 0; + + /** + * Get the camera image encoding according to sensor_msgs::image_encodings + * The supported encodings are 'mono8', 'bgr8', 'rgb8', 'bayer_bggr8', + * 'bayer_gbrg8', 'bayer_rggb8' and 'yuv422' + * @return the current ros image pixel encoding. + */ + virtual std::string currentROSEncoding() const = 0; + + /** + * Get the number of bytes per pixel + * @return number of bytes per pixel + */ + virtual int imagePixelDepth() const = 0; + + /** + * Returns the current exposure time in microseconds. + * @return the exposure time in microseconds. + */ + virtual float currentExposure() = 0; + + /** + * Returns the current auto exposure time lower limit + * @return the current auto exposure time lower limit + */ + virtual float currentAutoExposureTimeLowerLimit() = 0; + + /** + * Returns the current auto exposure time upper limit + * @return the current auto exposure time upper limit + */ + virtual float currentAutoExposureTimeUpperLimit() = 0; + + /** + * Returns the current gain in percent. + * @return the gain time percent. + */ + virtual float currentGain() = 0; + + /** + * Returns the current auto gain lower limit + * @return the current auto gain lower limit + */ + virtual float currentAutoGainLowerLimit() = 0; + + /** + * Returns the current auto gain upper limit + * @return the current auto gain upper limit + */ + virtual float currentAutoGainUpperLimit() = 0; + /** + * Returns the current gamma value. + * @return the gamma value. + */ + virtual float currentGamma() = 0; + + /** + * Checks if the camera currently tries to regulate towards a target brightness. + * This can either be done by arena for the range [50 - 205] or the own extendended binary search one + * for the ranges [1 - 49] and [206 - 254]. + * @return true if the brightness-search is running + */ + virtual bool isBrightnessSearchRunning() = 0; + + /** + * Checks if the auto brightness function from the Arena API is enabled. + * @return true if AutoExposure is set to AutoExposureContinuous or AutoExposureOnce. + */ + virtual bool isArenaAutoBrightnessFunctionRunning() = 0; + + /** + * Disables all currently running brightness search methods in case that + * the desired brightness is reached or a timeout occurred + */ + virtual void disableAllRunningAutoBrightessFunctions() = 0; + + /** + * Enables the continuous auto exposure mode + */ + virtual void enableContinuousAutoExposure() = 0; + + /** + * Enables the continuous auto gain mode + */ + virtual void enableContinuousAutoGain() = 0; + + /** + * Get the camera type. Currently supported cameras are USB, DART and GigE + * @return camera type as string + */ + virtual std::string typeName() const = 0; + + /** + * Minimum possible increment between two possible exposure values + * @return the minimum possible increment between two possible exposure values + */ + virtual float exposureStep() = 0; + + /** + * Getter for the device user id of the used camera + * @return the device_user_id + */ + const std::string& deviceUserID() const; + + /** + * Getter for the image height + * @return number of rows in the image + */ + const size_t& imageRows() const; + + /** + * Getter for the image width + * @return number of columns in the image + */ + const size_t& imageCols() const; + + /** + * Getter for the is_ready_ flag. This is set in case that the + * grab-result-pointer of the first acquisition contains valid data. + * Hence this is the current state of the interface + * @return true if the interface is ready + */ + const bool& isReady() const; + + /** + * Returns the number of digital user outputs, which can be set by the + * camera. Might be zero for some cameras. The size affects the number of + * 'set' ros-services the camera node will provide + * @return number of digital user outputs + */ + std::size_t numUserOutputs() const; + + /** + * Returns the image size in bytes + * @return the image size in bytes + */ + const size_t& imageSize() const; + + /** + * Get the maximum achievable frame rate + * @return float + */ + virtual float maxPossibleFramerate() = 0; + + /** + * Checks if the camera has the auto exposure feature. + * @return true if the camera supports auto exposure. + */ + const bool& hasAutoExposure() const; + + /** + * Max allowed delta between target and reached brightness + * @return the allowed tolerance. + */ + const float& maxBrightnessTolerance() const; + + /** + * Getter for the sequencer exposure times. + * @return the list of exposure times + */ + const std::vector& sequencerExposureTimes() const; + + virtual ~ArenaCamera(); + +protected: + /** + * Protected default constructor. + */ + ArenaCamera(); + + /** + * Enables the extended brightness search. + * @param brightness target brightness + * @return true after reaching the target brightness. + */ + virtual bool setExtendedBrightness(const int& target_brightness, const float& current_brightness) = 0; + + /** + * The DeviceUserID of the found camera + */ + std::string device_user_id_; + + /** + * Number of image rows. + */ + size_t img_rows_; + + /** + * Number of image columns. + */ + size_t img_cols_; + + /** + * The size of the image in number of bytes. + */ + size_t img_size_byte_; + + /** + * The max time a single grab is allowed to take. This value should always + * be greater then the max possible exposure time of the camera + */ + float grab_timeout_; + + /** + * Flag which is set in case that the grab-result-pointer of the first + * acquisition contains valid data + */ + bool is_ready_; + + /** + * Max allowed delta between target and reached brightness + */ + const float max_brightness_tolerance_; + + /** + * Exposure times to use when in sequencer mode. + */ + std::vector seq_exp_times_; + + /** + * Vector containing all available user outputs. + */ + std::vector user_output_selector_enums_; + + /** + * Vector that contains the available image_encodings the camera supports. + * The strings describe the GenAPI encoding. + */ + std::vector available_image_encodings_; +}; + +} // namespace arena_camera + +#endif // ARENA_CAMERA_ARENA_CAMERA_H diff --git a/catkin_ws/src/arena_camera/include/arena_camera/arena_camera.h.gch b/catkin_ws/src/arena_camera/include/arena_camera/arena_camera.h.gch new file mode 100644 index 0000000000000000000000000000000000000000..fb4d6e254edbbe98fb753a4d4d46342a681ebd2c GIT binary patch literal 8555 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 literal 0 HcmV?d00001 diff --git a/catkin_ws/src/arena_camera/include/arena_camera/arena_camera_node.h b/catkin_ws/src/arena_camera/include/arena_camera/arena_camera_node.h new file mode 100644 index 00000000..02693f86 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/arena_camera_node.h @@ -0,0 +1,393 @@ +/****************************************************************************** + * Software License Agreement (BSD License) + * + * Copyright (C) 2016, Magazino GmbH. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of Magazino GmbH nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef ARENA_CAMERA_ARENA_CAMERA_NODE_H +#define ARENA_CAMERA_ARENA_CAMERA_NODE_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace arena_camera +{ +typedef actionlib::SimpleActionServer GrabImagesAS; + +/** +* The ROS-node of the arena_camera interface +*/ +class ArenaCameraNode +{ +public: + ArenaCameraNode(); + virtual ~ArenaCameraNode(); + + /** + * initialize the camera and the ros node. + * calls ros::shutdown if an error occurs. + */ + void init(); + + /** + * spin the node + */ + virtual void spin(); + + /** + * Getter for the frame rate set by the launch script or from the ros parameter + * server + * @return the desired frame rate. + */ + const double& frameRate() const; + + /** + * Getter for the tf frame. + * @return the camera frame. + */ + const std::string& cameraFrame() const; + +protected: + /** + * Creates the camera instance and starts the services and action servers. + * @return false if an error occurred + */ + bool initAndRegister(); + + /** + * Start the camera and initialize the messages + * @return + */ + bool startGrabbing(); + + bool setImageEncoding(const std::string& ros_encoding); + + /** + * Initializing of img_rect_pub_, grab_img_rect_as_ and the pinhole_model_, + * in case that a valid camera info has been set + * @return + */ + void setupRectification(); + + /** + * Returns the total number of subscribers on any advertised image topic. + */ + uint32_t getNumSubscribers() const; + + /** + * Returns the number of subscribers for the raw image topic + */ + uint32_t getNumSubscribersRaw() const; + + /** + * Returns the number of subscribers for the rect image topic + */ + uint32_t getNumSubscribersRect() const; + + /** + * Grabs an image and stores the image in img_raw_msg_ + * @return false if an error occurred. + */ + virtual bool grabImage(); + + /** + * Fills the ros CameraInfo-Object with the image dimensions + */ + virtual void setupInitialCameraInfo(sensor_msgs::CameraInfo& cam_info_msg); + + /** + * Update area of interest in the camera image + * @param target_roi the target roi + * @param reached_roi the roi that could be set + * @return true if the targeted roi could be reached + */ + bool setROI(const sensor_msgs::RegionOfInterest target_roi, sensor_msgs::RegionOfInterest& reached_roi); + + /** + * Update the horizontal binning_x factor to get downsampled images + * @param target_binning_x the target horizontal binning_x factor + * @param reached_binning_x the horizontal binning_x factor that could be + * reached + * @return true if the targeted binning could be reached + */ + bool setBinningX(const size_t& target_binning_x, size_t& reached_binning_x); + + /** + * Update the vertical binning_y factor to get downsampled images + * @param target_binning_y the target vertical binning_y factor + * @param reached_binning_y the vertical binning_y factor that could be + * reached + * @return true if the targeted binning could be reached + */ + bool setBinningY(const size_t& target_binning_y, size_t& reached_binning_y); + + /** + * Service callback for updating the cameras binning setting + * @param req request + * @param res response + * @return true on success + */ + bool setBinningCallback(camera_control_msgs::SetBinning::Request& req, + camera_control_msgs::SetBinning::Response& res); + + /** + * Service callback for updating the cameras roi setting + * @param req request + * @param res response + * @return true on success + */ + bool setROICallback(camera_control_msgs::SetROI::Request& req, camera_control_msgs::SetROI::Response& res); + + bool setExposureValue(const float& target_exposure, float& reached_exposure); + + /** + * Update the exposure value on the camera + * @param target_exposure the targeted exposure + * @param reached_exposure the exposure that could be reached + * @return true if the targeted exposure could be reached + */ + bool setExposure(const float& target_exposure, float& reached_exposure); + + /** + * Service callback for setting the exposure + * @param req request + * @param res response + * @return true on success + */ + bool setExposureCallback(camera_control_msgs::SetExposure::Request& req, + camera_control_msgs::SetExposure::Response& res); + + /** + * Sets the target brightness which is the intensity-mean over all pixels. + * If the target exposure time is not in the range of Arena's auto target + * brightness range the extended brightness search is started. + * The Auto function of the Arena-API supports values from [50 - 205]. + * Using a binary search, this range will be extended up to [1 - 255]. + * @param target_brightness is the desired brightness. Range is [1...255]. + * @param current_brightness is the current brightness with the given settings. + * @param exposure_auto flag which indicates if the target_brightness + * should be reached adapting the exposure time + * @param gain_auto flag which indicates if the target_brightness should be + * reached adapting the gain. + * @return true if the brightness could be reached or false otherwise. + */ + bool setBrightness(const int& target_brightness, int& reached_brightness, const bool& exposure_auto, + const bool& gain_auto); + + /** + * Service callback for setting the brightness + * @param req request + * @param res response + * @return true on success + */ + bool setBrightnessCallback(camera_control_msgs::SetBrightness::Request& req, + camera_control_msgs::SetBrightness::Response& res); + + bool setGainValue(const float& target_gain, float& reached_gain); + + /** + * Update the gain from the camera to a target gain in percent + * @param target_gain the targeted gain in percent + * @param reached_gain the gain that could be reached + * @return true if the targeted gain could be reached + */ + bool setGain(const float& target_gain, float& reached_gain); + + /** + * Service callback for setting the desired gain in percent + * @param req request + * @param res response + * @return true on success + */ + bool setGainCallback(camera_control_msgs::SetGain::Request& req, camera_control_msgs::SetGain::Response& res); + + bool setGammaValue(const float& target_gamma, float& reached_gamma); + + /** + * Update the gamma from the camera to a target gamma correction value + * @param target_gamma the targeted gamma + * @param reached_gamma the gamma that could be reached + * @return true if the targeted gamma could be reached + */ + bool setGamma(const float& target_gamma, float& reached_gamma); + + /** + * Service callback for setting the desired gamma correction value + * @param req request + * @param res response + * @return true on success + */ + bool setGammaCallback(camera_control_msgs::SetGamma::Request& req, camera_control_msgs::SetGamma::Response& res); + + /** + * Callback that puts the camera to sleep + * @param req request + * @param res response + * @return true on success + */ + bool setSleepingCallback(camera_control_msgs::SetSleeping::Request& req, + camera_control_msgs::SetSleeping::Response& res); + + /** + * Returns true if the camera was put into sleep mode + * @return true if in sleep mode + */ + bool isSleeping(); + + /** + * Generates the subset of points on which the brightness search will be + * executed in order to speed it up. The subset are the indices of the + * one-dimensional image_raw data vector. The base generation is done in a + * recursive manner, by calling genSamplingIndicesRec + * @return indices describing the subset of points + */ + void setupSamplingIndices(std::vector& indices, std::size_t rows, std::size_t cols, + int downsampling_factor); + + /** + * This function will recursively be called from above setupSamplingIndices() + * to generate the indices of pixels given the actual ROI. + * @return indices describing the subset of points + */ + void genSamplingIndicesRec(std::vector& indices, const std::size_t& min_window_height, + const cv::Point2i& start, const cv::Point2i& end); + + /** + * Calculates the mean brightness of the image based on the subset indices + * @return the mean brightness of the image + */ + float calcCurrentBrightness(); + + /** + * Callback for the grab images action + * @param goal the goal + */ + void grabImagesRawActionExecuteCB(const camera_control_msgs::GrabImagesGoal::ConstPtr& goal); + + /** + * Callback for the grab rectified images action + * @param goal the goal + */ + void grabImagesRectActionExecuteCB(const camera_control_msgs::GrabImagesGoal::ConstPtr& goal); + + /** + * This function can also be called from the derived ArenaCameraOpenCV-Class + */ + camera_control_msgs::GrabImagesResult grabImagesRaw(const camera_control_msgs::GrabImagesGoal::ConstPtr& goal, + GrabImagesAS* action_server); + + void initCalibrationMatrices(sensor_msgs::CameraInfo& info, const cv::Mat& D, const cv::Mat& K); + + /** + * Callback that sets the digital user output + * @param output_id the ID of the user output to set + * @param req request + * @param res response + * @return true on success + */ + bool setUserOutputCB(int output_id, camera_control_msgs::SetBool::Request& req, + camera_control_msgs::SetBool::Response& res); + + /** + * Callback that activates the digital user output to + be used as autoflash + * @param output_id the ID of the user output to set + * @param req request + * @param res response + * @return true on success + */ + bool setAutoflash(const int output_id, camera_control_msgs::SetBool::Request& req, + camera_control_msgs::SetBool::Response& res); + + ros::NodeHandle nh_; + ArenaCameraParameter arena_camera_parameter_set_; + ros::ServiceServer set_binning_srv_; + ros::ServiceServer set_roi_srv_; + ros::ServiceServer set_exposure_srv_; + ros::ServiceServer set_gain_srv_; + ros::ServiceServer set_gamma_srv_; + ros::ServiceServer set_brightness_srv_; + ros::ServiceServer set_sleeping_srv_; + std::vector set_user_output_srvs_; + + ArenaCamera* arena_camera_; + + image_transport::ImageTransport* it_; + image_transport::CameraPublisher img_raw_pub_; + + ros::Publisher* img_rect_pub_; + image_geometry::PinholeCameraModel* pinhole_model_; + + GrabImagesAS grab_imgs_raw_as_; + GrabImagesAS* grab_imgs_rect_as_; + + sensor_msgs::Image img_raw_msg_; + cv_bridge::CvImage* cv_bridge_img_rect_; + + camera_info_manager::CameraInfoManager* camera_info_manager_; + + std::vector sampling_indices_; + std::array brightness_exp_lut_; + + bool is_sleeping_; + boost::recursive_mutex grab_mutex_; + + /// diagnostics: + diagnostic_updater::Updater diagnostics_updater_; + void diagnostics_timer_callback_(const ros::TimerEvent&); + ros::Timer diagnostics_trigger_; + void create_diagnostics(diagnostic_updater::DiagnosticStatusWrapper& stat); + void create_camera_info_diagnostics(diagnostic_updater::DiagnosticStatusWrapper& stat); +}; + +} // namespace arena_camera + +#endif // ARENA_CAMERA_ARENA_CAMERA_NODE_H diff --git a/catkin_ws/src/arena_camera/include/arena_camera/arena_camera_parameter.h b/catkin_ws/src/arena_camera/include/arena_camera/arena_camera_parameter.h new file mode 100644 index 00000000..4290d111 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/arena_camera_parameter.h @@ -0,0 +1,335 @@ +/****************************************************************************** + * Software License Agreement (BSD License) + * + * Copyright (C) 2016, Magazino GmbH. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of Magazino GmbH nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef ARENA_CAMERA_ARENA_CAMERA_PARAMETER_H +#define ARENA_CAMERA_ARENA_CAMERA_PARAMETER_H + +#include +#include +#include + +namespace arena_camera +{ +enum SHUTTER_MODE +{ + SM_ROLLING = 0, + SM_GLOBAL = 1, + SM_GLOBAL_RESET_RELEASE = 2, + SM_DEFAULT = -1, +}; + +/** +*Parameter class for the ArenaCamera +*/ +class ArenaCameraParameter +{ +public: + ArenaCameraParameter(); + + virtual ~ArenaCameraParameter(); + + /** + * Read the parameters from the parameter server. + * If invalid parameters can be detected, the interface will reset them + * to the default values. + * @param nh the ros::NodeHandle to use + */ + void readFromRosParameterServer(const ros::NodeHandle& nh); + + /** + * Getter for the device_user_id_ set from ros-parameter server + */ + const std::string& deviceUserID() const; + + /** + * Setter for the device_user_id_ to the class and as well + * the ros-parameter server + */ + void adaptDeviceUserId(const ros::NodeHandle& nh, const std::string& device_user_id); + + /** + * Getter for the string describing the shutter mode + */ + std::string shutterModeString() const; + + /** + * Getter for the camera_frame_ set from ros-parameter server + */ + const std::string& cameraFrame() const; + + /** + * Getter for the frame_rate_ read from ros-parameter server + */ + const double& frameRate() const; + + /** + * Getter for the image_encoding_ read from ros-parameter server + */ + const std::string& imageEncoding() const; + + /** + * Setter for the frame_rate_ initially set from ros-parameter server + * The frame rate needs to be updated with the value the camera supports + */ + void setFrameRate(const ros::NodeHandle& nh, const double& frame_rate); + + /** + * Getter for the camera_info_url set from ros-parameter server + */ + const std::string& cameraInfoURL() const; + + /** + * Setter for the camera_info_url_ if a new CameraInfo-Msgs Object is + * provided via the SetCameraInfo-service from the CameraInfoManager + */ + void setCameraInfoURL(const ros::NodeHandle& nh, const std::string& camera_info_url); + +public: + /** Binning factor to get downsampled images. It refers here to any camera + * setting which combines rectangular neighborhoods of pixels into larger + * "super-pixels." It reduces the resolution of the output image to + * (width / binning_x) x (height / binning_y). + * The default values binning_x = binning_y = 0 are considered the same + * as binning_x = binning_y = 1 (no subsampling). + */ + size_t binning_x_; + size_t binning_y_; + + /** + * Flags which indicate if the binning factors are provided and hence + * should be set during startup + */ + bool binning_x_given_; + bool binning_y_given_; + + bool image_encoding_given_; + + /** + * Factor that describes the image downsampling to speed up the exposure + * search to find the desired brightness. + * The smallest window height is img_rows/downsampling_factor + */ + int downsampling_factor_exp_search_; + + // ####################################################################### + // ###################### Image Intensity Settings ###################### + // ####################################################################### + // The following settings do *NOT* have to be set. Each camera has default + // values which provide an automatic image adjustment + // If one would like to adjust image brightness, it is not + // ####################################################################### + + /** + * The exposure time in microseconds to be set after opening the camera. + */ + double exposure_; + + /** + * Flag which indicates if the exposure time is provided and hence should + * be set during startup + */ + bool exposure_given_; + + /** + * The target gain in percent of the maximal value the camera supports + * For USB-Cameras, the gain is in dB, for GigE-Cameras it is given in so + * called 'device specific units'. + */ + double gain_; + + /** + * Flag which indicates if the gain value is provided and hence should be + * set during startup + */ + bool gain_given_; + + /** + * Gamma correction of pixel intensity. + * Adjusts the brightness of the pixel values output by the camera's sensor + * to account for a non-linearity in the human perception of brightness or + * of the display system (such as CRT). + */ + double gamma_; + + /** + * Flag which indicates if the gamma correction value is provided and + * hence should be set during startup + */ + bool gamma_given_; + + /** + * The average intensity value of the images. It depends on the exposure + * time as well as the gain setting. If 'exposure' is provided, the + * interface will try to reach the desired brightness by only varying the + * gain. (What may often fail, because the range of possible exposure + * values is many times higher than the gain range). + * If 'gain' is provided, the interface will try to reach the desired + * brightness by only varying the exposure time. If gain AND exposure are + * given, it is not possible to reach the brightness, because both are + * assumed to be fix. + */ + int brightness_; + + /** + * Flag which indicates if the average brightness is provided and hence + * should be set during startup + */ + bool brightness_given_; + + /** + * Only relevant, if 'brightness' is set as ros-parameter: + * The brightness_continuous flag controls the auto brightness function. + * If it is set to false, the brightness will only be reached once. + * Hence changing light conditions lead to changing brightness values. + * If it is set to true, the given brightness will be reached continuously, + * trying to adapt to changing light conditions. This is only possible for + * values in the possible auto range of the arena API which is + * e.g. [50 - 205] for acA2500-14um and acA1920-40gm + */ + bool brightness_continuous_; + /** + * Only relevant, if 'brightness' is given as ros-parameter: + * If the camera should try to reach and / or keep the brightness, hence + * adapting to changing light conditions, at least one of the following + * flags must be set. If both are set, the interface will use the profile + * that tries to keep the gain at minimum to reduce white noise. + * The exposure_auto flag indicates, that the desired brightness will + * be reached by adapting the exposure time. + * The gain_auto flag indicates, that the desired brightness will be + * reached by adapting the gain. + */ + bool exposure_auto_; + bool gain_auto_; + // ####################################################################### + + /** + * The timeout while searching the exposure which is connected to the + * desired brightness. For slow system this has to be increased. + */ + double exposure_search_timeout_; + + /** + * The exposure search can be limited with an upper bound. This is to + * prevent very high exposure times and resulting timeouts. + * A typical value for this upper bound is ~2000000us. + */ + double auto_exp_upper_lim_; + + /** + * The MTU size. Only used for GigE cameras. + * To prevent lost frames the camera has to be configured + * with the MTU size the network card supports. A value greater 3000 + * should be good (1500 for RaspberryPI) + */ + int mtu_size_; + + /** + * The inter-package delay in ticks. Only used for GigE cameras. + * To prevent lost frames it should be greater 0. + * For most of GigE-Cameras, a value of 1000 is reasonable. + * For GigE-Cameras used on a RaspberryPI this value should be set to 11772 + */ + int inter_pkg_delay_; + + /** + Shutter mode + */ + SHUTTER_MODE shutter_mode_; + + /** + * Flag that indicates if the camera has been calibrated and the intrinsic + * calibration matrices are available + */ + bool has_intrinsic_calib_; + + /** + * Flag that indicates if the camera has a flash connected which should be on on exposure + * Only supported for GigE cameras. Default: false + */ + bool auto_flash_; + /** + * Flag that indicates if the camera, when using auto_flash == true, a flash connected on line 2 which should be on on + * exposure + * Only supported for GigE cameras. Default: true + */ + bool auto_flash_line_2_; + /** + * Flag that indicates if the camera has, when using auto_flash == true, a flash connected on line 3 which should be + * on on exposure + * Only supported for GigE cameras. Default: true + */ + bool auto_flash_line_3_; + +protected: + /** + * Validates the parameter set found on the ros parameter server. + * If invalid parameters can be detected, the interface will reset them + * to the default values. + * @param nh the ros::NodeHandle to use + */ + void validateParameterSet(const ros::NodeHandle& nh); + + /** + * The tf frame under which the images were published + */ + std::string camera_frame_; + + /** + * The DeviceUserID of the camera. If empty, the first camera found in the + * device list will be used + */ + std::string device_user_id_; + + /** + * The desired publisher frame rate if listening to the topics. + * This parameter can only be set once at startup + * Calling the GrabImages-Action can result in a higher framerate + */ + double frame_rate_; + + /** + * The CameraInfo URL (Uniform Resource Locator) where the optional + * intrinsic camera calibration parameters are stored. This URL string will + * be parsed from the CameraInfoManager: + * http://wiki.ros.org/camera_info_manager + */ + std::string camera_info_url_; + + /** + * The encoding of the pixels -- channel meaning, ordering, size taken + * from the list of strings in include/sensor_msgs/image_encodings.h + * The supported encodings are 'mono8', 'bgr8', 'rgb8', 'bayer_bggr8', + * 'bayer_gbrg8', 'bayer_rggb8' and 'yuv422' + */ + std::string image_encoding_; +}; + +} // namespace arena_camera + +#endif // ARENA_CAMERA_ARENA_CAMERA_PARAMETER_H diff --git a/include/pylon_camera/encoding_conversions.h b/catkin_ws/src/arena_camera/include/arena_camera/encoding_conversions.h similarity index 93% rename from include/pylon_camera/encoding_conversions.h rename to catkin_ws/src/arena_camera/include/arena_camera/encoding_conversions.h index 221f4e0f..ad933214 100644 --- a/include/pylon_camera/encoding_conversions.h +++ b/catkin_ws/src/arena_camera/include/arena_camera/encoding_conversions.h @@ -27,12 +27,12 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -#ifndef PYLON_CAMERA_ENCODING_CONVERSIONS_H -#define PYLON_CAMERA_ENCODING_CONVERSIONS_H +#ifndef ARENA_CAMERA_ENCODING_CONVERSIONS_H +#define ARENA_CAMERA_ENCODING_CONVERSIONS_H #include -namespace pylon_camera +namespace arena_camera { namespace encoding_conversions @@ -54,8 +54,5 @@ namespace encoding_conversions bool genAPI2Ros(const std::string& gen_api_enc, std::string& ros_enc); } // namespace encoding_conversions -} // namespace pylon_camera -#endif // PYLON_CAMERA_ENCODING_CONVERSIONS_H - - - +} // namespace arena_camera +#endif // ARENA_CAMERA_ENCODING_CONVERSIONS_H diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.h new file mode 100644 index 00000000..89d10eb2 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.h @@ -0,0 +1,89 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ +#pragma once + +#include "ISystem.h" + +namespace Arena +{ + /** + * @fn ISystem* OpenSystem() + * + * @return + * - Type: ISystem* + * - The system object + * + * OpenSystem initializes the Arena SDK and retrieves the system + * object (Arena::ISystem). The system must be closed or memory will leak. + * + * \code{.cpp} + * // opening and closing a system + * { + * Arena::ISystem* pSystem = Arena::OpenSystem(); + * // ... + * Arena::CloseSystem(pSystem); + * } + * \endcode + * + * @warning + * - Only one system may be opened at a time + * - System must be closed + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::ISystem + * - Arena::OpenSystem + * - Arena::CloseSystem + */ + ARENA_API ISystem* OpenSystem(); + + /** + * @fn void CloseSystem(ISystem* pSystem) + * + * @param pSystem + * - Type: ISystem* + * - The system object + * + * @return + * - none + * + * CloseSystem cleans up the system (Arena::ISystem) and + * deinitializes the Arena SDK, deallocating all memory. + * + * @warning + * - The system must be closed + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::ISystem + */ + ARENA_API void CloseSystem(ISystem* pSystem); + + /** + * uint64_t CalculateMaximumNumberOfBuffers(size_t payloadSize) + * + * @param payloadSize + * - Type: size_t + * - Unit: bytes + * - Payload size of an image + * + * @return + * - Type: uint64_t + * - Maximum number of buffers + * + * CalculateMaximumNumberOfBuffers calculates the number of + * buffers it would take to fill 80% of the available memory. + */ + ARENA_API size_t CalculateMaximumNumberOfBuffers(size_t payloadSize); +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.rc b/catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.rc new file mode 100644 index 00000000..8771e5e0 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.rc @@ -0,0 +1,111 @@ +// clang-format off +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,1,0,0 + PRODUCTVERSION 0,1,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Lucid Vision Labs, Inc." + VALUE "FileDescription", "Arena C++ Interface" + VALUE "FileVersion", "0.1.0.0" + VALUE "InternalName", "Arena.dll" + VALUE "LegalCopyright", "Copyright (C) 2017" + VALUE "OriginalFilename", "Arena.dll" + VALUE "ProductName", "Arena SDK" + VALUE "ProductVersion", "0.1.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (Canada) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENC) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_CAN +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (Canada) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.vcxproj b/catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.vcxproj new file mode 100644 index 00000000..4b021193 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/Arena.vcxproj @@ -0,0 +1,431 @@ + + + + + Debug_Test + Win32 + + + Debug_Test + x64 + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {4FC0B0FB-DEA9-49ED-9F24-E6BD4815ECC3} + Win32Proj + Arena + 8.1 + + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)OutputDirectory\Windows\$(Platform)$(Configuration)\ + $(ProjectName)d_$(PlatformToolset) + + + true + $(SolutionDir)OutputDirectory\Windows\$(Platform)$(Configuration)\ + $(ProjectName)d_$(PlatformToolset) + + + $(ProjectName)d_$(PlatformToolset) + true + $(SolutionDir)OutputDirectory\Windows\$(Platform)$(Configuration)\ + + + $(ProjectName)d_$(PlatformToolset) + true + $(SolutionDir)OutputDirectory\Windows\$(Platform)$(Configuration)\ + + + false + $(SolutionDir)OutputDirectory\Windows\$(Platform)$(Configuration)\ + $(ProjectName)_$(PlatformToolset) + + + false + $(SolutionDir)OutputDirectory\Windows\$(Platform)$(Configuration)\ + $(ProjectName)_$(PlatformToolset) + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ARENA_EXPORTS;%(PreprocessorDefinitions) + true + include\;..\..\googletest-master\googlemock\include;%(AdditionalIncludeDirectories) + true + + + Windows + true + $(SolutionDir)OutputDirectory\Windows\lib\$(TargetName).lib + %(AdditionalDependencies) + ..\..\googletest-master\googlemock\msvc\2013\Debug\;%(AdditionalLibraryDirectories) + + + + + + + + + + + Use + Level3 + Disabled + WIN32;_ARENA_UNIT_TEST_API;_EXPORT_ARENA_UNIT_TEST_API;_DEBUG;_WINDOWS;_USRDLL;ARENA_EXPORTS;%(PreprocessorDefinitions) + true + include\;..\..\googletest-master\googlemock\include;%(AdditionalIncludeDirectories) + true + true + + + Windows + true + $(SolutionDir)OutputDirectory\Windows\lib\$(TargetName).lib + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + + + + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ARENA_EXPORTS;%(PreprocessorDefinitions) + true + include\;%(AdditionalIncludeDirectories) + true + false + false + + + Windows + true + $(SolutionDir)OutputDirectory\Windows\lib64\$(TargetName).lib + %(AdditionalDependencies) + ..\..\googletest-master\googlemock\msvc\2013\gmock\x64\Debug\;%(AdditionalLibraryDirectories) + + + + + + + + + + Use + Level3 + Disabled + WIN32;_EXPORT_ARENA_UNIT_TEST_API;_DEBUG;_ARENA_UNIT_TEST_API;_WINDOWS;_USRDLL;ARENA_EXPORTS;%(PreprocessorDefinitions) + true + include\;%(AdditionalIncludeDirectories) + true + false + false + + + Windows + true + $(SolutionDir)OutputDirectory\Windows\lib64\$(TargetName).lib + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ARENA_EXPORTS;%(PreprocessorDefinitions) + true + include;%(AdditionalIncludeDirectories) + true + + + Windows + true + true + true + $(SolutionDir)OutputDirectory\Windows\lib\$(TargetName).lib + %(AdditionalLibraryDirectories) + %(AdditionalDependencies) + + + + + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ARENA_EXPORTS;%(PreprocessorDefinitions) + true + include;%(AdditionalIncludeDirectories) + true + + + Windows + true + true + true + $(SolutionDir)OutputDirectory\Windows\lib64\$(TargetName).lib + %(AdditionalLibraryDirectories) + %(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/ArenaApi.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/ArenaApi.h new file mode 100644 index 00000000..b8c29217 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/ArenaApi.h @@ -0,0 +1,50 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ +#pragma once + +#if (defined _WIN32 || defined _WIN64) +#ifdef ARENA_EXPORTS +#define ARENA_API __declspec(dllexport) +#else +#define ARENA_API __declspec(dllimport) +#endif +#else +#define ARENA_API +#endif + +#if defined __linux__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif +#include +#include +#include + +#if defined __linux__ +#pragma GCC diagnostic pop +#endif + +#include "Arena.h" +#include "ArenaDefs.h" +#include "DeviceInfo.h" +#include "FeatureStream.h" +#include "GenApiCustom.h" +#include "IBuffer.h" +#include "IChunkData.h" +#include "IDevice.h" +#include "IImage.h" +#include "ImageFactory.h" +#include "ISystem.h" +#include "PFNC.h" +#include "PFNCCustom.h" diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/ArenaDefs.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/ArenaDefs.h new file mode 100644 index 00000000..6fd66cfe --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/ArenaDefs.h @@ -0,0 +1,126 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ + + /** + * @file ArenaDefs.h + * This file defines global Arena enums. + */ + +#pragma once + +namespace Arena +{ + /** + * @typedef ENumBufferFlags + * + * The ENumBufferFlags predefined number of buffers options for the + * stream. + * + * The enum values and their descriptions: + * - NumBuffersAuto + * - Value: 0xFFFFFFFF + * - Description: Auto calculate numBuffers based on max throughput + */ + typedef enum _ENumBufferFlags + { + NumBuffersAuto = 0xFFFFFFFF /*!< Auto calculate numBuffers based on max throughput */ + } ENumBufferFlags; + + /** + * @typedef EBufferPayloadType + * + * The EBufferPayloadType enum represents the different types of GVSP + * data that can be acquired by the acquisition engine. This enum is returned + * from: + * - buffers (Arena::IBuffer::GetPayloadType) + * - images (Arena::IImage::GetPayloadType) + * - chunk data (Arena::IChunkData::GetPayloadType) + * + * The enum values and their descriptions: + * - BufferPayloadTypeImage + * - Value: 0x0001 + * - Description: Image data only + * - BufferPayloadTypeImageExtendedChunk + * - Value: 0x4001 + * - Description: Image data extended with chunk data + * - BufferPayloadTypeChunkData + * - Value: 0x0004 + * - Description: Chunk data only; image data may be present as chunk + * + * @see + * - Arena::IBuffer::GetPayloadType + * - Arena::IImage::GetPayloadType + * - Arena::IChunkData::GetPayloadType + */ + typedef enum _EBufferPayloadType + { + BufferPayloadTypeImage = 0x0001, /*!< Image data only */ + BufferPayloadTypeImageExtendedChunk = 0x4001, /*!< Image data extended with chunk data */ + BufferPayloadTypeChunkData = 0x0004 /*!< Chunk data only; image data may be present as chunk */ + } EBufferPayloadType; + + /** + * @typedef EPixelEndianness + * + * The EPixelEndianness enum represents the endianness of an image's + * multi-byte pixels. This enum is returned from images + * (Arena::IImage::GetPixelEndianness). + * + * The enum values and their descriptions: + * - PixelEndiannessUnknown + * - Value: 0 + * - Description: Unknown pixel endianness + * - PixelEndiannessLittle + * - Value: 1 + * - Description: Little endian + * - PixelEndiannessBig + * - Value: 2 + * - Description: Big endian + * + * @see + * - Arena::IImage::GetPixelEndianness + */ + typedef enum _EPixelEndianness + { + PixelEndiannessUnknown = 0, /*!< Unknown pixel endianness */ + PixelEndiannessLittle = 1, /*!< Little endian */ + PixelEndiannessBig = 2, /*!< Big endian */ + } EPixelEndianness; + + /** + * @typedef EBayerAlgorithm + * + * The EBayerAlgorithm enum represents different algorithms for + * interpreting bayer patterns. Provide this enum when converting an image + * from any bayer pattern (Arena::ImageFactory::Create). + * + * The enum values and their descriptions + * - DirectionalInterpolation + * - Description: Algorithm that averages nearest neighbours (faster) + * - AdaptiveHomogeneityDirected + * - Description: Adaptive algorithm that uses directional data (slower, + * more accurate coloring) + * - _UndefinedAlgorithm + * - Description: Object not yet initialized + * + * @see + * - Arena::ImageFactory::Create + */ + typedef enum _EBayerAlgorithm + { + DirectionalInterpolation, /*!< Algorithm that averages nearest neighbours (faster) */ + AdaptiveHomogeneityDirected, /*!< Adaptive algorithm that uses directional data (slower, more accurate coloring) */ + _UndefinedAlgorithm /*!< Undefined algorithm */ + } EBayerAlgorithm; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/Buffer.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/Buffer.h new file mode 100644 index 00000000..8f52aa0f --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/Buffer.h @@ -0,0 +1,95 @@ +#pragma once +#include "IBuffer.h" +#include "TLDataStream.h" + +namespace Arena +{ + template + T GetInfo(TLDataStream* pDataStream, GenTL::BUFFER_HANDLE hBuffer, GenTL::BUFFER_INFO_CMD cmd) + { + // check for logical errors + if (!pDataStream) + { + THROW_TYPE( + InvalidArgumentException, + "DataStream is null"); + } + + if (!hBuffer) + { + THROW_TYPE( + InvalidArgumentException, + "Buffer is null"); + } + + // get uint64_t value + T value = 0; + size_t valueLen = sizeof(value); + GenTL::INFO_DATATYPE type; + + GenTL::GC_ERROR err = pDataStream->GetBufferInfo(hBuffer, cmd, &type, &value, &valueLen); + if (err != GenTL::GC_ERR_SUCCESS) + { + THROW_DEFAULT( + err, + "Unable to get buffer information", + "DSGetBufferInfo"); + } + + return value; + } + + template + void Cleanup(T*& pVal) + { + if (pVal != NULL) + { + delete pVal; + pVal = NULL; + } + } + + class ARENA_TEST_API Buffer : public IBuffer + { + public: + Buffer(TLDataStream* pStream, GenTL::BUFFER_HANDLE hBuffer); + Buffer(Buffer* pBuffer); + Buffer(); + virtual ~Buffer(); + + //IBuffer methods + virtual const uint8_t* GetData(); + virtual size_t GetSizeFilled(); + virtual size_t GetPayloadSize(); + virtual size_t GetSizeOfBuffer(); + virtual bool HasChunkData(); + virtual size_t GetPayloadType(); + virtual bool IsIncomplete(); + virtual uint64_t GetFrameId(); + virtual bool HasImageData(); + virtual IChunkData* AsChunkData(); + virtual IImage* AsImage(); + virtual bool DataLargerThanBuffer(); + virtual bool VerifyCRC(); + + virtual GenTL::BUFFER_HANDLE + GetBufferHandle(); + friend ImageFactory; + + protected: + TLDataStream* m_pStream; + GenTL::BUFFER_HANDLE m_hBuffer; + + // TODO: These should all be GenApi::CPtr types once we implement a GenTL nodemap + uint8_t* m_pData; + size_t* m_pSizeFilled; + size_t* m_pPayloadSize; + size_t* m_pSizeOfBuffer; + bool8_t* m_pHasChunkData; + size_t* m_pPayloadType; + bool8_t* m_pIsIncomplete; + uint64_t* m_pFrameId; + bool8_t* m_pHasImageData; + bool8_t* m_pDataLargerThanBuffer; + }; +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/ChunkData.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/ChunkData.h new file mode 100644 index 00000000..2ba5ed95 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/ChunkData.h @@ -0,0 +1,41 @@ +#pragma once +#include "IChunkData.h" +#include "Buffer.h" +#include + +namespace Arena +{ + class TLDataStream; + class ARENA_TEST_API ChunkData : public IChunkData, virtual public Buffer + { + public: + ChunkData(TLDataStream* pStream, GenTL::BUFFER_HANDLE hBuffer, GenApi::INodeMap* pChunkNodeMap); + ChunkData(ChunkData* pChunkData); + ChunkData(); + virtual ~ChunkData(); + + //IBuffer methods + virtual const uint8_t* GetData(); + virtual size_t GetSizeFilled(); + virtual size_t GetPayloadSize(); + virtual size_t GetSizeOfBuffer(); + virtual bool HasChunkData(); + virtual IChunkData* AsChunkData(); + virtual size_t GetPayloadType(); + virtual bool IsIncomplete(); + virtual uint64_t GetFrameId(); + virtual bool HasImageData(); + virtual IImage* AsImage(); + virtual bool DataLargerThanBuffer(); + virtual bool VerifyCRC(); + + //IImage methods + virtual GenApi::INode* GetChunk(GenICam::gcstring name); + + friend ImageFactory; + + protected: + GenApi::CChunkAdapterGEV* m_pChunkAdapter; + GenApi::INodeMap* m_pChunkNodeMap; + }; +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/Device.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/Device.h new file mode 100644 index 00000000..53ed0cc4 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/Device.h @@ -0,0 +1,108 @@ +#pragma once +#include "IDevice.h" +#include "Port.h" +#include "TLDevice.h" +#include "TLDataStream.h" +#include "TLEvent.h" +#include "StreamInfo.h" +#include "Interface.h" +#include +#include + +#include + +namespace Arena +{ + class ARENA_TEST_API Device : public IDevice + { + public: + Device(TLDevice* pDevice, Interface* pIfInfo); + virtual ~Device(); + + virtual GenApi::INodeMap* GetNodeMap(); + virtual GenApi::INodeMap* GetTLDeviceNodeMap(); + virtual GenApi::INodeMap* GetTLStreamNodeMap(); + virtual GenApi::INodeMap* GetTLStreamNodeMap(int streamNumber = 0); + virtual GenApi::INodeMap* GetTLInterfaceNodeMap(); + + virtual void StartStream(size_t numBuffers = 10); + virtual void StopStream(); + virtual IImage* GetImage(uint64_t timeout); + virtual IBuffer* GetBuffer(uint64_t timeout); + virtual void RequeueBuffer(IBuffer* pImage); + + virtual void StartStream(int streamNumber, size_t numBuffers); + virtual void StopStream(int streamNumber); + virtual IImage* GetImage(int streamNumber, uint64_t timeout); + virtual IBuffer* GetBuffer(int streamNumber, uint64_t timeout); + virtual void RequeueBuffer(int streamNumber, IBuffer* pImage); + + virtual void InitializeEvents(); + virtual void DeinitializeEvents(); + virtual void WaitOnEvent(uint64_t timeout); + + virtual void SendActionCommand(uint32_t deviceKey, uint32_t groupKey, uint32_t groupMask, uint64_t actionTime); + + virtual bool IsConnected(); + + virtual void ResetInterface() + { + std::unique_lock l(m_interfaceMtx); + m_pIfInfo = nullptr; + } + + virtual Interface* GetInterface() + { + std::unique_lock l(m_interfaceMtx); + return m_pIfInfo; + } + +#if defined(_ARENA_UNIT_TEST_API) + Device(TLDevice* pDevice, TLBase* pDeviceRemote, Interface* pifInfo, std::vector streams, std::vector hEventNewBuffers, GenApi::INodeMap* pNodeMap, GenApi::INodeMap* pDeviceNodeMap, GenApi::CChunkAdapterGEV* pChunkAdapter, GenApi::CNodeMapFactory& chunkDataNodeMapFactory) : + m_pDevice(pDevice), + m_pDeviceRemote(pDeviceRemote), + m_pIfInfo(pifInfo), + m_pTLDeviceNodeMap(pDeviceNodeMap), + m_pDeviceNodeMap(pNodeMap), + m_pTLDevicePort(pDevice), + m_pDevicePort(pDeviceRemote), + m_streams(streams), + m_hEventNewBuffers(hEventNewBuffers), + m_pChunkAdapter(pChunkAdapter), + m_chunkDataNodeMapFactory(chunkDataNodeMapFactory) + { + } + + void nullifyNodeMap() + { + m_pTLDeviceNodeMap = NULL; + m_pDeviceNodeMap = NULL; + } +#endif + + protected: + Device(); + + TLDevice* m_pDevice; + TLBase* m_pDeviceRemote; + Interface* m_pIfInfo; + + GenApi::INodeMap* m_pTLDeviceNodeMap; + GenApi::INodeMap* m_pDeviceNodeMap; + + Port m_pTLDevicePort; + Port m_pDevicePort; + + std::vector m_streams; + std::vector m_hEventNewBuffers; + + TLEvent* m_hEventRemoteDevice; + GenApi::CEventAdapter* m_pEventAdapter; + + GenApi::CChunkAdapterGEV* m_pChunkAdapter; + GenApi::CNodeMapFactory m_chunkDataNodeMapFactory; + + std::mutex m_interfaceMtx; + std::mutex m_streamMutex; + }; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/DeviceInfo.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/DeviceInfo.h new file mode 100644 index 00000000..90fb50df --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/DeviceInfo.h @@ -0,0 +1,566 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2019, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ +#pragma once +#include + +namespace Arena +{ + /** + * @class DeviceInfo + * + * Device information objects contain device discovery information used to + * find, filter, and create devices. + * + * A list of device information objects is retrieved and maintained in the + * system (Arena::ISystem). A device information object provides a device's + * discovery information: + * - model and vendor names + * - serial number + * - IP and MAC addresses + * - subnet mask + * + * Discovery information is acquired prior to device creation and used + * primarily to filter and find specific devices. A std::vector of + * Arena::DeviceInfo objects is retrieved from the system (via + * Arena::ISystem::GetDevices). They are then individually passed back to the + * system to create devices (via Arena::ISystem::CreateDevice). + * + * The list of DeviceInfos is returned as a std::vector in order to make use + * of STL filter and search tools. The following code snippet demonstrates + * searching for a device with a specific serial number: + * + * \code{.cpp} + * // searching for a specific device + * { + * GenICam::gcstring serialToFind = "12345"; + * std::vector devs = pSystem->GetDevices(); + * + * auto it = std::find_if(begin(devs), end(devs), [&serialToFind](Arena::DeviceInfo deviceInfo) + * { + * return deviceInfo.SerialNumber() == serialToFind; + * }); + * } + * \endcode + * + * @see + * - Arena::ISystem + * - Arena::DeviceInfo + */ + class ARENA_API DeviceInfo + { + public: + + /** + * @fn virtual GenICam::gcstring ModelName() + * + * @return + * - Type: GenICam::gcstring + * - Model name of the device + * + * ModelName gets the model name of a device. + * + * Model names are used to differentiate between products. Below are a + * few examples of Lucid Vision model names: + * - PHX050S-MC + * - PHX032S-CC + * - TRI032S-MC + * + * The model name returned by this getter is the same as the one received + * in the GigE Vision discovery acknowledgement. + */ + virtual GenICam::gcstring ModelName(); + + /** + * @fn virtual GenICam::gcstring VendorName() + * + * @return + * - Type: GenICam::gcstring + * - Vendor name of the device + * + * VendorName gets the vendor/manufacturer name of a device. + * Vendor names differentiate between device vendors/manufacturers. Lucid + * devices return 'Lucid Vision Labs'. + * + * The vendor name returned by this getter is the same as the one + * received in the GigE Vision discovery acknowledgement. + */ + virtual GenICam::gcstring VendorName(); + + /** + * @fn virtual GenICam::gcstring SerialNumber() + * + * @return + * - Type: GenICam::gcstring + * - Serial number of the device + * + * SerialNumber gets the serial number of a device. A serial + * number differentiates between devices. Each Lucid device has a unique + * serial number. Lucid serial numbers are numeric, but the serial + * numbers of other vendors may be alphanumeric. + * + * The serial number returned by this getter is the same as the one + * received in the GigE Vision discovery acknowledgement. + * + * @warning + * - Serial numbers from different manufacturers may overlap + */ + virtual GenICam::gcstring SerialNumber(); + + /** + * @fn virtual uint32_t IpAddress() + * + * @return + * - Type: uint32_t + * - IP address of the device as an integer + * + * IpAddress gets the IP address of a device on the network, + * returning it as its raw integer value. + * + * The GigE Vision specification only allows for IPv4 IP addresses. The + * IP address is represented by the lower 32 bits of the integer + * returned. Both IpAddress and IpAddressStr represent + * the same IP address. Where IpAddress returns the value in its + * raw integer format, IpAddressStr returns it as a more + * human-readable string: + * - IpAddress : 0xA9FE0101 + * - IpAddressStr : 169.254.1.1 + * + * A device may have its IP address, subnet mask, and default gateway + * assigned by LLA or DHCP, set as persistent, or temporarily forced. + * They can be checked through the main node map + * (Arena::IDevice::GetNodeMap, 'GevCurrentIPAddress', + * 'GevCurrentSubnetMask', 'GevCurrentDefaultGateway'). + * + * DHCP (Arena::IDevice::GetNodeMap, 'GevCurrentIPConfigurationDHCP') and + * IP persistence (Arena::IDevice::GetNodeMap, + * 'GevCurrentIPConfigurationPersistentIP') can be enabled/disabled + * through the node map. If both are enabled, a device will default to + * its persistent IP settings. If neither, it will default to LLA + * (Arena::IDevice::GetNodeMap, 'GevCurrentIPConfigurationLLA'), which cannot + * be disabled. + * + * In order to configure a device to use a persistent IP configuration, + * not only must IP persistence be enabled (Arena::IDevice::GetNodeMap, + * 'GevCurrentIPConfigurationPersistentIP'), but the IP address + * (Arena::IDevice::GetNodeMap, 'GevPersistentIPAddress'), subnet mask + * (Arena::IDevice::GetNodeMap, 'GevPersistentSubnetMask'), and default + * gateway (Arena::IDevice::GetNodeMap, 'GevPersistentDefaultGateway') + * must be set. + * + * Forcing an IP (Arena::IDevice::ForceIP) temporarily changes an IP + * address, subnet mask, and default gateway of a device. A forced IP + * configuration will reset on a device reboot + * (Arena::IDevice::GetNodeMap, 'DeviceReset'). + * + * The IP address returned by this getter is the same as the one received + * in the GigE Vision discovery acknowledgement. + * + * @warning + * - Represents same information as Arena::DeviceInfo::IpAddressStr + * - A persistent IP may be quicker to enumerate than DHCP, which + * should be faster than LLA + * + * @see + * - Arena::IDevice::GetNodeMap + * - Arena::IDevice::ForceIP + * - Arena::DeviceInfo::IpAddressStr + */ + virtual uint32_t IpAddress(); + + /** + * @fn virtual GenICam::gcstring IpAddressStr() + * + * @return + * - Type: GenICam::gcstring + * - IP address of the device as a string + * + * IpAddressStr gets the IP address of a device on the network, + * returning it as a string. + * + * The GigE Vision specification only allows for IPv4 IP addresses. Both + * IpAddress and IpAddressStr represent the same IP + * address. Where IpAddress returns the value in its raw integer + * format, IpAddressStr returns it as a more human-readable + * string: + * - IpAddress : 0xA9FE0101 + * - IpAddressStr : 169.254.1.1 + * + * A device may have its IP address, subnet mask, and default gateway + * assigned by LLA or DHCP, set as persistent, or temporarily forced. + * They can be checked through the main node map + * (Arena::IDevice::GetNodeMap, 'GevCurrentIPAddress', + * 'GevCurrentSubnetMask', 'GevCurrentDefaultGateway'). + * + * DHCP (Arena::IDevice::GetNodeMap, 'GevCurrentIPConfigurationDHCP') and + * IP persistence (Arena::IDevice::GetNodeMap, + * 'GevCurrentIPConfigurationPersistentIP') can be enabled/disabled + * through the node map. If both are enabled, a device will default to + * its persistent IP settings. If neither, it will default to LLA + * (Arena::IDevice::GetNodeMap, 'GevCurrentIPConfigurationLLA'), which cannot + * be disabled. + * + * In order to configure a device to use a persistent IP configuration, + * not only must IP persistence be enabled (Arena::IDevice::GetNodeMap, + * 'GevCurrentIPConfigurationPersistentIP'), but the IP address + * (Arena::IDevice::GetNodeMap, 'GevPersistentIPAddress'), subnet mask + * (Arena::IDevice::GetNodeMap, 'GevPersistentSubnetMask'), and default + * gateway (Arena::IDevice::GetNodeMap, 'GevPersistentDefaultGateway') + * must be set. + * + * Forcing an IP (Arena::IDevice::ForceIP) temporarily changes an IP + * address, subnet mask, and default gateway of a device. A forced IP + * configuration will reset on a device reboot + * (Arena::IDevice::GetNodeMap, 'DeviceReset'). + * + * The IP address returned by this getter is the same as the one received + * in the GigE Vision discovery acknowledgement, but as a dot-separated + * string. + * + * @warning + * - Represents same information as Arena::DeviceInfo::IpAddress + * - A persistent IP may be quicker to enumerate than DHCP, which + * should be faster than LLA + * + * @see + * - Arena::IDevice::GetNodeMap + * - Arena::IDevice::ForceIP + * - Arena::DeviceInfo::IpAddress + */ + virtual GenICam::gcstring IpAddressStr(); + + /** + * @fn virtual uint32_t SubnetMask() + * + * @return + * - Type: uint32_t + * - Subnet mask of the device as an integer + * + * SubnetMask gets the subnet mask of a device on the network, + * returning it as its raw integer value. + * + * The GigE Vision specification only allows for IPv4 subnet masks. The + * subnet mask is represented by the lower 32 bits of the integer + * returned. Both SubnetMask and SubnetMaskStr + * represent the same subnet mask. Where SubnetMask returns the + * value in its raw integer format, SubnetMaskStr returns it as + * a more human-readable string: + * - SubnetMask : 0xFFFF0000 + * - SubnetMaskStr : 255.255.0.0 + * + * A device may have its IP address, subnet mask, and default gateway + * assigned by LLA or DHCP, set as persistent, or temporarily forced. + * They can be checked through the main node map + * (Arena::IDevice::GetNodeMap, 'GevCurrentIPAddress', + * 'GevCurrentSubnetMask', 'GevCurrentDefaultGateway'). + * + * DHCP (Arena::IDevice::GetNodeMap, 'GevCurrentIPConfigurationDHCP') and + * IP persistence (Arena::IDevice::GetNodeMap, + * 'GevCurrentIPConfigurationPersistentIP') can be enabled/disabled + * through the node map. If both are enabled, a device will default to + * its persistent IP settings. If neither, it will default to LLA + * (Arena::IDevice::GetNodeMap, 'GevCurrentIPConfigurationLLA'), which cannot + * be disabled. + * + * In order to configure a device to use a persistent IP configuration, + * not only must IP persistence be enabled (Arena::IDevice::GetNodeMap, + * 'GevCurrentIPConfigurationPersistentIP'), but the IP address + * (Arena::IDevice::GetNodeMap, 'GevPersistentIPAddress'), subnet mask + * (Arena::IDevice::GetNodeMap, 'GevPersistentSubnetMask'), and default + * gateway (Arena::IDevice::GetNodeMap, 'GevPersistentDefaultGateway') + * must be set. + * + * Forcing an IP (Arena::IDevice::ForceIP) temporarily changes an IP + * address, subnet mask, and default gateway of a device. A forced IP + * configuration will reset on a device reboot + * (Arena::IDevice::GetNodeMap, 'DeviceReset'). + * + * The subnet mask returned by this getter is the same as the one + * received in the GigE Vision discovery acknowledgement. + * + * @warning + * - Represents same information as Arena::DeviceInfo::SubnetMaskStr + * - A persistent IP may be quicker to enumerate than DHCP, which + * should be faster than LLA + * + * @see + * - Arena::IDevice::GetNodeMap + * - Arena::IDevice::ForceIP + * - Arena::DeviceInfo::SubnetMaskStr + */ + virtual uint32_t SubnetMask(); + + /** + * @fn virtual GenICam::gcstring SubnetMaskStr() + * + * @return + * - Type: GenICam::gcstring + * - Subnet mask of the device as a string + * + * SubnetMaskStr gets the subnet mask of a device on the + * network, returning it as a string. + * + * The GigE Vision specification only allows for IPv4 subnet masks. Both + * SubnetMask and SubnetMaskStr represent the same + * subnet mask. Where SubnetMask returns the value in its raw + * integer format, SubnetMaskStr returns it as a more + * human-readable string: + * - SubnetMask : 0xFFFF0000 + * - SUbnetMaskStr : 255.255.0.0 + * + * A device may have its IP address, subnet mask, and default gateway + * assigned by LLA or DHCP, set as persistent, or temporarily forced. + * They can be checked through the main node map + * (Arena::IDevice::GetNodeMap, 'GevCurrentIPAddress', + * 'GevCurrentSubnetMask', 'GevCurrentDefaultGateway'). + * + * DHCP (Arena::IDevice::GetNodeMap, 'GevCurrentIPConfigurationDHCP') and + * IP persistence (Arena::IDevice::GetNodeMap, + * 'GevCurrentIPConfigurationPersistentIP') can be enabled/disabled + * through the node map. If both are enabled, a device will default to + * its persistent IP settings. If neither, it will default to LLA + * (Arena::IDevice::GetNodeMap, 'GevCurrentIPConfigurationLLA'), which cannot + * be disabled. + * + * In order to configure a device to use a persistent IP configuration, + * not only must IP persistence be enabled (Arena::IDevice::GetNodeMap, + * 'GevCurrentIPConfigurationPersistentIP'), but the IP address + * (Arena::IDevice::GetNodeMap, 'GevPersistentIPAddress'), subnet mask + * (Arena::IDevice::GetNodeMap, 'GevPersistentSubnetMask'), and default + * gateway (Arena::IDevice::GetNodeMap, 'GevPersistentDefaultGateway') + * must be set. + * + * Forcing an IP (Arena::IDevice::ForceIP) temporarily changes an IP + * address, subnet mask, and default gateway of a device. A forced IP + * configuration will reset on a device reboot + * (Arena::IDevice::GetNodeMap, 'DeviceReset'). + * + * The subnet mask returned by this getter is the same as the one + * received in the GigE Vision discovery acknowledgement, but as a + * dot-separated string. + * + * @warning + * - Represents same information as Arena::DeviceInfo::SubnetMask + * - A persistent IP may be quicker to enumerate than DHCP, which + * should be faster than LLA + * + * @see + * - Arena::IDevice::GetNodeMap + * - Arena::IDevice::ForceIP + * - Arena::DeviceInfo::SubnetMask + */ + virtual GenICam::gcstring SubnetMaskStr(); + + /** + * @fn virtual uint32_t DefaultGateway() + * + * @return + * - Type: uint32_t + * - The default gateway + * + * DefaultGateway retrieves the default gateway of the device. + */ + virtual uint32_t DefaultGateway(); + + /** + * @fn virtual GenICam::gcstring DefaultGatewayStr() + * + * @return + * - Type: GenICam::gcstring + * - The default gateway + * + * DefaultGateway retrieves the default gateway of the device as + * a string. + */ + virtual GenICam::gcstring DefaultGatewayStr(); + + /** + * @fn virtual uint64_t MacAddress() + * + * @return + * - Type: uint64_t + * - MAC address of the device as an integer + * + * MacAddress gets the MAC address of a device on the network, + * returning it as its raw integer value. + * + * Both MacAddress and MacAddressStr represent the same + * MAC address. Where MacAddress returns the value in its raw + * integer format, MacAddressStr returns it as a more + * human-readable string: + * - MacAddress : 0x1C0FAF010101 + * - MacAddressStr : 1C:0F:AF:01:01:01 + * + * The MAC address returned by this getter is the same as the one + * received in the GigE Vision discovery acknowledgement. + * + * @warning + * - Represents same information as Arena::DeviceInfo::MacAddressStr + * + * @see + * - Arena::DeviceInfo::MacAddressStr + */ + virtual uint64_t MacAddress(); + + /** + * @fn virtual GenICam::gcstring MacAddressStr() + * + * @return + * - Type: GenICam::gcstring + * - MAC address of the device as a string + * + * MacAddressStr gets the MAC address of a device on the + * network, returning it as a string. + * + * Both MacAddress and MacAddressStr represent the same + * MAC address. Where MacAddress returns the value in its raw + * integer format, MacAddressStr returns it as a more + * human-readable string: + * - MacAddress : 0x1C0FAF010101 + * - MacAddressStr : 1C:0F:AF:01:01:01 + * + * The MAC address returned by this getter is the same as the one + * received in the GigE Vision discovery acknowledgement. + * + * @warning + * - Represents same information as Arena::DeviceInfo::MacAddress + * + * @see + * - Arena::DeviceInfo::MacAddress + */ + virtual GenICam::gcstring MacAddressStr(); + + /** + * @fn virtual GenICam::gcstring UserDefinedName() + * + * @return + * - Type: GenICam::gcstring + * - User-defined name of a device + * + * UserDefinedName gets the user-defined name of a device. If + * supported, it is a customizable string with a maximum of 16 bytes that + * can be used to identify a device (Arena::IDevice::GetNodeMap, + * 'DeviceUserID'). + * + * The user-defined name returned by this getter is the same as the one + * received in the GigE Vision discovery acknowledgement. + * + * @warning + * - Not necessarily supported + * + * @see + * - Arena::IDevice::GetNodeMap + */ + virtual GenICam::gcstring UserDefinedName(); + + /** + * @fn virtual bool IsDHCPConfigurationEnabled() + * + * @return + * - Type: bool + * - True if DHCP enabled + * - Otherwise, false + * + * IsDHCPConfigurationEnabled retrieves whether DHCP is enabled + * on the device. + */ + virtual bool IsDHCPConfigurationEnabled(); + + /** + * @fn virtual bool IsPersistentIpConfigurationEnabled() + * + * @return + * - Type: bool + * - True if persistent IP enabled + * - Otherwise, false + * + * IsPersistentIpConfigurationEnabled retrieves whether + * persistent IP is enabled on the device. + */ + virtual bool IsPersistentIpConfigurationEnabled(); + + /** + * @fn virtual bool IsLLAConfigurationEnabled() + * + * @return + * - Type: bool + * - True if LLA enabled + * - Otherwise, false + * + * IsLLAConfigurationEnabled retrieves whether LLA is enabled on + * the device. + */ + virtual bool IsLLAConfigurationEnabled(); + + /** + * @fn virtual GenICam::gcstring DeviceVersion() + * + * @return + * - Type: GenICam::gcstring + * - Firmware version of the device + * + * DeviceVersion retrieves the version of the device currently + * running on the device. For Lucid devices, this refers to firmware + * version. + */ + virtual GenICam::gcstring DeviceVersion(); + + /** + * @fn DeviceInfo() + * + * An empty constructor + * + * @warning + * - Does not sufficiently initialize DeviceInfo + */ + DeviceInfo(); + + /** + * @fn DeviceInfo(const DeviceInfo& deviceInfo) + * + * A copy constructor + * + * @param deviceInfo + * - Type: const DeviceInfo& + * - Device information object to copy + */ + DeviceInfo(const DeviceInfo& deviceInfo); + + /** + * @fn virtual ~DeviceInfo() + * + * A destructor + */ + virtual ~DeviceInfo(); + + /** + * @fn virtual DeviceInfo& operator=(DeviceInfo deviceInfo) + * + * A copy assignment operator + * + * @param deviceInfo + * - Type: DeviceInfo + * - Device information object to copy + * + * @return + * - Type: DeviceInfo& + * - Copied device information object + */ + virtual DeviceInfo& operator=(DeviceInfo deviceInfo); + + protected: + + friend class System; + void* m_pData; + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/DeviceInfoType.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/DeviceInfoType.h new file mode 100644 index 00000000..6c085be7 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/DeviceInfoType.h @@ -0,0 +1,32 @@ +#pragma once + +#include "stdafx.h" + +namespace Arena +{ + class ARENA_TEST_API DeviceInfo_t + { + + public: + DeviceInfo_t(){}; + ~DeviceInfo_t(){}; + + GenICam::gcstring m_modelName; + GenICam::gcstring m_vendorName; + GenICam::gcstring m_serialNumber; + GenICam::gcstring m_id; + GenICam::gcstring m_userDefName; + + uint32_t m_ip; + uint32_t m_mask; + uint64_t m_mac; + uint32_t m_gateway; + + bool m_dhcpEnabled; + bool m_persistentIpEnabled; + bool m_llaEnabled; + + uint64_t m_ifaceMac; + GenICam::gcstring m_deviceVersion; + }; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/Exceptions.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/Exceptions.h new file mode 100644 index 00000000..cdbe1bb7 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/Exceptions.h @@ -0,0 +1,102 @@ +#pragma once + +#include "Base/GCException.h" + +inline const char* __str(const char* str) +{ + return str; +} + +inline const char* __str(std::string str) +{ + return str.c_str(); +} + +#define GEN_ERR "Unable to get error from GenTL layer." +#define GEN_SIZE 2048 + +// message strings +#define MSG(type, code, message, file, function) \ + (std::string(#type) + " (" + #code "): " + message + " (" + file + ", " + function + ")").c_str() + +#define MSG_GENTL(type, code, message, file, function, gentl, gentlMessage) \ + (std::string(#type) + " (" + #code + "): " + __str(message) + " (" + file + ", " + function + "; GenTL::" + gentl + "); " + gentlMessage).c_str() + +#define MSG_NO_CODE(type, message, file, function) \ + (std::string(#type) + ": " + __str(message) + " (" + file + ", " + function + ")").c_str() + +// case statements +#define CASE(code, type, message, gentl, gentlMessage) \ + case GenTL::code: \ + throw GenICam::type( \ + (strcmp(gentl,"") == 0 ? MSG(type, code, message, __FILE__, __FUNCTION__) : MSG_GENTL(type, code, message, __FILE__, __FUNCTION__, gentl, gentlMessage)), \ + __FILE__, \ + __LINE__); + +#define GENTL_MSG \ + std::string gentlStr = ""; \ + { \ + size_t size = GEN_SIZE; \ + char pGentlMsg[GEN_SIZE]; \ + GenTL::GC_ERROR lastErr = GenTL::GC_ERR_SUCCESS; \ + GenTL::GC_ERROR err = GenTL::GCGetLastError(&lastErr, pGentlMsg, &size); \ + gentlStr = pGentlMsg; \ + if (err != GenTL::GC_ERR_SUCCESS) \ + { \ + gentlStr = GEN_ERR; \ + } \ + } + +// throws +#define THROW_DEFAULT(err, message, gentl) \ + GENTL_MSG; \ + switch (err) \ + { \ + CASE(GC_ERR_NOT_INITIALIZED, LogicalErrorException, message, gentl, gentlStr); \ + CASE(GC_ERR_NOT_IMPLEMENTED, LogicalErrorException, message, gentl, gentlStr); \ + CASE(GC_ERR_RESOURCE_IN_USE, LogicalErrorException, message, gentl, gentlStr); \ + CASE(GC_ERR_ACCESS_DENIED, AccessException, message, gentl, gentlStr); \ + CASE(GC_ERR_INVALID_HANDLE, InvalidArgumentException, message, gentl, gentlStr); \ + CASE(GC_ERR_INVALID_ID, InvalidArgumentException, message, gentl, gentlStr); \ + CASE(GC_ERR_NO_DATA, RuntimeException, message, gentl, gentlStr); \ + CASE(GC_ERR_INVALID_PARAMETER, InvalidArgumentException, message, gentl, gentlStr); \ + CASE(GC_ERR_IO, RuntimeException, message, gentl, gentlStr); \ + CASE(GC_ERR_TIMEOUT, TimeoutException, message, gentl, gentlStr); \ + CASE(GC_ERR_ABORT, RuntimeException, message, gentl, gentlStr); \ + CASE(GC_ERR_INVALID_BUFFER, InvalidArgumentException, message, gentl, gentlStr); \ + CASE(GC_ERR_NOT_AVAILABLE, LogicalErrorException, message, gentl, gentlStr); \ + CASE(GC_ERR_INVALID_ADDRESS, InvalidArgumentException, message, gentl, gentlStr); \ + CASE(GC_ERR_BUFFER_TOO_SMALL, InvalidArgumentException, message, gentl, gentlStr); \ + CASE(GC_ERR_INVALID_INDEX, OutOfRangeException, message, gentl, gentlStr); \ + CASE(GC_ERR_PARSING_CHUNK_DATA, RuntimeException, message, gentl, gentlStr); \ + CASE(GC_ERR_INVALID_VALUE, InvalidArgumentException, message, gentl, gentlStr); \ + CASE(GC_ERR_RESOURCE_EXHAUSTED, RuntimeException, message, gentl, gentlStr); \ + CASE(GC_ERR_OUT_OF_MEMORY, RuntimeException, message, gentl, gentlStr); \ + CASE(GC_ERR_BUSY, RuntimeException, message, gentl, gentlStr); \ + default: \ + if (strcmp(gentl,"") == 0) \ + throw GenICam::GenericException(MSG(GenericException, GC_ERR_ERROR, message, __FILE__, __FUNCTION__), __FILE__, __LINE__); \ + else \ + throw GenICam::GenericException(MSG_GENTL(GenericException, GC_ERR_ERROR, message, __FILE__, __FUNCTION__, gentl, gentlStr), __FILE__, __LINE__); \ + } + +// throw special instruction +#define THROW_IF(err, code, type, message, gentl) \ + if (err == code) \ + { \ + GENTL_MSG; \ + throw GenICam::type( \ + MSG_GENTL(type, code, message, __FILE__, __FUNCTION__, gentl, gentlStr), \ + __FILE__, \ + __LINE__); \ + } + +// throw non-gentl +#define THROW_TYPE(type, message) \ + throw GenICam::type( \ + MSG_NO_CODE(type, message, __FILE__, __FUNCTION__), \ + __FILE__, \ + __LINE__); + +#define THROW_NO_GENTL(err, message) \ + THROW_DEFAULT(err, message, ""); diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/FeatureStream.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/FeatureStream.h new file mode 100644 index 00000000..6ee85cdb --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/FeatureStream.h @@ -0,0 +1,274 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ +#pragma once + +namespace Arena +{ + /** + * @class FeatureStream + * + * A FeatureStream object allows a device to stream features to and + * from files, sharing values amongst one another. Features can be either + * written from the device's node map (GenApi::INodeMap) to a file or read + * from a file to the node map. + * + * \code{.cpp} + * // saving all streamable features from a device to a file + * { + * Arena::FeatureStream featureStream(pNodeMap); + * featureStream.Write("fileName.txt"); + * } + * // loading all streamable features from a file to a device + * { + * Arena::FeatureStream featureStream(pNodeMap); + * featureStream.Read("fileName.txt"); + * } + * \endcode + * + * Feature streams can stream all streamable features on a device or a select + * few. By default, a feature stream will not have had any features selected + * and will save or load all available streamable features. For a more precise + * list, features may be selected (Arena::FeatureStream::Select, cascading I/O + * operator <<). + * + * \code{.cpp} + * // saving just width and height to a file + * // selecting width and height with the output operator + * { + * Arena::FeatureStream featureStream(pNodeMap); + * featureStream << "Width" << "Height"; + * featureStream.Write("fileName.txt"); + * } + * \endcode + * + * Not all features are streamable. If unsure, check feature streamability + * (GenApi::INode::IsStreamable). + * + * \code{.cpp} + * // checking for streamability, only selecting if streamable + * { + * if (pNode->IsStreamable()) + * { + * featureStream << pNode->GetName(); + * } + * } + * \endcode + * + * @warning + * - Not all features are streamable + * + * @see + * - Arena::FeatureStream + */ + class ARENA_API FeatureStream + { + public: + + /** + * @fn FeatureStream(GenApi::INodeMap* pNodeMap) + * + * @param pNodeMap + * - Type: GenApi::INodeMap* + * - Pointer to the node map to stream + * + * The constructor builds a feature stream from a device's node map + * (GenApi::INodeMap). This node map cannot be changed later; another feature + * stream must be created to stream another node map. + * + * By default, a feature stream will have a flag set to stream all streamable + * features. A subset of streamable features may be selected + * (Arena::FeatureStream::Select), reversing the flag. If a non-streamable + * feature is selected, the call will throw and the flag will remain as it + * originally was. + * + * @see + * - Arena::FeatureStream::Select + */ + FeatureStream(GenApi::INodeMap* pNodeMap); + + /** + * @fn virtual void Write(const GenICam::gcstring fileName = "features.txt") + * + * @param fileName + * - Type: const GenICam::gcstring + * - Default: features.txt + * - Name of the file to write to + * + * @return + * - none + * + * Write streams all selected features from the node map + * (GenApi::INodeMap) to a file. + * + * By default, a feature stream will have a flag set to stream all streamable + * features. A subset of streamable features may be selected + * (Arena::FeatureStream::Select), reversing the flag. If a non-streamable + * feature is selected, the call will throw and the flag will remain as it + * originally was. + * + * Essentially, if no features have been selected, Write will stream + * all available streamable features from the node map to the file; if one or + * more features have been selected, only the selected features will be + * streamed. + * + * @warning + * - May throw GenICam::GenericException or other derived exception + * - May throw exception related to file access permissions + * + * @see + * - Arena::FeatureStream::Select + */ + virtual void Write(const GenICam::gcstring fileName = "features.txt"); + + /** + * @fn virtual void Read(const GenICam::gcstring fileName = "features.txt") + * + * @param fileName + * - Type: const GenICam::gcstring + * - Default: features.txt + * - Name of the file to read from + * + * @return + * - none + * + * Read streams all selected features from a file to the node map + * (GenApi::INodeMap). + * + * Calling Read streams all features from a file. Selecting features + * (Arena::FeatureStream::Select) only applies to Write . + * + * @warning + * - May throw GenICam::GenericException or other derived exception + * - May throw exception related to file access permissions + * - Selecting features only applies to write + * + * @see + * - Arena::FeatureStream::Select + */ + virtual void Read(const GenICam::gcstring fileName = "features.txt"); + + /** + * @fn virtual void Select(GenICam::gcstring featureName) + * + * @param featureName + * - Type: GenICam::gcstring + * - Name of the feature to select + * + * @return + * - none + * + * Select adds a single streamable feature to the list of selected + * features to stream. If called for the first time, Select also + * sets an internal 'select-all' flag to false. + * + * By default, a feature stream will have a flag set to stream all streamable + * features. A subset of streamable features may be selected, reversing the + * flag. If a non-streamable feature is selected, the call will throw and the + * flag will remain as it originally was. + * + * Trying to select a non-streamable feature will throw. If unsure, check + * streamability: + * + * \code{.cpp} + * // checking for streamability, only selecting if streamable + * { + * if (pNode->IsStreamable()) + * { + * featureStream.Select(pNode->GetName()); + * } + * } + * \endcode + * + * @warning + * - Provides same functionality as << operator + * - Selecting features only applies to write operation + * - Throws GenICam::InvalidArgumentException if selected feature not + * streamable + * - May throw child of GenICam::GenericExceptio + */ + virtual void Select(GenICam::gcstring featureName); + + /** + * @fn virtual FeatureStream& operator<<(GenICam::gcstring featureName) + * + * @param featureName + * - Type: GenICam::gcstring + * - Name of the feature to select + * + * @return + * - Type: FeatureStream& + * - Same feature stream object, allowing for cascading + * + * The cascading I/O operator (<<) adds a single streamable feature + * to the list of selected features to stream. If called for the first time, + * it also sets an internal 'select-all' flag to false. + * + * By default, a feature stream has a flag set to stream all streamable + * features. A subset of streamable features may be selected + * (Arena::FeatureStream::Select), reversing the flag. If a non-streamable + * feature is selected, the call will throw and the flag will remain as it + * originally was. + * + * Trying to select a non-streamable feature will throw. If unsure, check + * streamability: + * + * \code{.cpp} + * // checking for streamability, only selecting if streamable + * { + * if (pNode->IsStreamable()) + * { + * featureStream << pNode->GetName(); + * } + * } + * \endcode + * + * The operator's ability to cascade calls allows selecting items to be + * linked together, one after the other. + * + * \code{.cpp} + * // cascading selection calls with the << operator before streaming to file + * { + * Arena::FeatureStream featureStream(pNodeMap); + * featureStream << "Width" << "Height" << "OffsetX" << "OffsetY"; + * featureStream.Write(); + * } + * \endcode + * + * @warning + * - Provides same functionality as Arena::FeatureStream::Select with the + * added bonus of cascading calls + * - Selecting features only applies to write operation + * - Throws GenICam::InvalidArgumentException if selected feature not + * streamable + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::FeatureStream + * - Arena::FeatureStream::Select + */ + virtual FeatureStream& operator<<(GenICam::gcstring featureName); + + /** + * @fn virtual ~FeatureStream() + * + * A destructor + */ + virtual ~FeatureStream(); + + protected: + + FeatureStream(); + void* m_pData; + }; +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/FeatureStreamType.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/FeatureStreamType.h new file mode 100644 index 00000000..6b2947f9 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/FeatureStreamType.h @@ -0,0 +1,15 @@ +#pragma once + +namespace Arena +{ + class FeatureStream_t + { + public: + FeatureStream_t(){}; + virtual ~FeatureStream_t(){}; + + GenApi::CFeatureBag m_featureBag; + GenApi::INodeMap* m_pNodeMap; + bool m_selectAll; + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/GTestAPI.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/GTestAPI.h new file mode 100644 index 00000000..45689cb8 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/GTestAPI.h @@ -0,0 +1,29 @@ +#pragma once + +#if defined(_WIN32) || defined(WIN64) +// Windows 32-bit and 64-bit +#ifdef _ARENA_UNIT_TEST_API + +#ifdef _EXPORT_ARENA_UNIT_TEST_API + +#pragma warning(disable : 4251) //needs to have dll-interface to be used by clients of class + +#define ARENA_TEST_API __declspec(dllexport) + +#else //if not _EXPORT_ARENA_UNIT_TEST_API + +#define ARENA_TEST_API __declspec(dllimport) + +#endif // end _EXPORT_ARENA_UNIT_TEST_API + +#else //_ARENA_UNIT_TEST_API + +#define ARENA_TEST_API + +#endif //_ARENA_UNIT_TEST_API + +#else //if not _WIN32 and WIN64 + +#define ARENA_TEST_API + +#endif // end defined(_WIN32) || defined(WIN64) \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/GenApiCustom.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/GenApiCustom.h new file mode 100644 index 00000000..897093d2 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/GenApiCustom.h @@ -0,0 +1,285 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ +#pragma once + +namespace Arena +{ + /** + * @class EInterfaceTypeClass + * + * EInterfaceTypeClass is a static class based on GenApi classes of + * the same purpose. It translates from an interface type enum + * (GenApi::EInterfaceType) to a string and back. For example, the following + * enum value can be translated to and from the following string + * representation: + * - enum: GenApi::EInterfaceType::intfIValue + * - string: "intfIValue" + * + * @warning + * - Similar classes are found mostly under the GenApi namespace + */ + class ARENA_API EInterfaceTypeClass + { + public: + + /** + * @fn static bool FromString(const GenICam::gcstring& ValueStr, GenApi::EInterfaceType* pValue) + * + * @param ValueStr + * - Type: const GenICam::gcstring& + * - String representation to translate + * + * @param pValue + * - Type: GenApi::EInterfaceType* + * - Out parameter + * - Pointer to translated enum value + * + * @return + * - Type: bool + * - True if successful + * - Otherwise, false + * + * FromString translates a string representation of an interface + * type enum to its enum value. The second parameter is an out parameter. + */ + static bool FromString(const GenICam::gcstring& ValueStr, GenApi::EInterfaceType* pValue); + + /** + * @fn static void ToString(GenICam::gcstring& ValueStr, GenApi::EInterfaceType Value) + * + * @param ValueStr + * - Type: GenICam::gcstring& + * - Pass-by-reference out parameter + * - Translated string representation + * - "intfIUnknown" on failure + * + * @param Value + * - Type: GenApi::EInterfaceType + * - Enum value to translate + * + * @return + * - none + * + * ToString translates from an interface type enum value to its + * string representation. The first parameter is an out parameter. + */ + static void ToString(GenICam::gcstring& ValueStr, GenApi::EInterfaceType Value); + + /** + * @fn static GenICam::gcstring ToString(GenApi::EInterfaceType Value) + * + * @param Value + * - Type: GenApi::EInterfaceType + * - Enum value to translate + * + * @return + * - Type: GenICam::gcstring + * - Translated string representation + * - "intfIUnknown" on failure + * + * ToString translates from an interface type enum value to its + * string representation. + */ + static GenICam::gcstring ToString(GenApi::EInterfaceType Value); + + private: + + // static class implementation + // constructor inaccessible + EInterfaceTypeClass() {}; + }; + + /** + * @class EIncModeClass + * + * EIncModeClass is a static class based on GenApi classes of the + * same purpose. It translates from an increment mode enum (GenApi::EIncMode) + * to a string and back. For example, the following enum value can be + * translated to and from the following string representation: + * - enum: GenApi::fixedIncrement + * - string: "fixedIncrement" + * + * @warning + * - Similar classes are mostly found under the GenApi namespace + */ + class ARENA_API EIncModeClass + { + public: + + /** + * @fn static bool FromString(const GenICam::gcstring& ValueStr, GenApi::EIncMode* pValue) + * + * @param ValueStr + * - Type: const GenICam::gcstring& + * - String representation to translate + * + * @param pValue + * - Type: GenApi::EIncMode* + * - Out parameter + * - Pointer to translated enum value + * + * @return + * - Type: bool + * - True if successful + * - Otherwise, false + * + * FromString translates a string representation of an increment + * mode enum to its value. The second parameter is an out parameter. + */ + static bool FromString(const GenICam::gcstring& ValueStr, GenApi::EIncMode* pValue); + + /** + * @fn static void ToString(GenICam::gcstring& ValueStr, GenApi::EIncMode Value) + * + * @param ValueStr + * - Type: GenICam::gcstring& + * - Pass-by-reference out parameter + * - Translated string representation + * - "intfIUnknown" on failure + * + * @param Value + * - Type: GenApi::EIncMode + * - Enum value to translate + * + * @return + * - none + * + * ToString translates from an increment mode enum value to its + * string representation. The first parameter is an out parameter. + */ + static void ToString(GenICam::gcstring& ValueStr, GenApi::EIncMode Value); + + /** + * @fn static GenICam::gcstring ToString(GenApi::EIncMode Value) + * + * @param Value + * - Type: GenApi::EInterfaceType + * - Enum value to translate + * + * @return + * - Type: GenICam::gcstring + * - Translated string representation + * - "intfIUnknown" on failure + * + * ToString translates from an increment mode enum value to its + * string representation. + */ + static GenICam::gcstring ToString(GenApi::EIncMode Value); + + private: + + // static class implementation + // constructor inaccessible + EIncModeClass() {}; + }; + + /** + * @fn template T GetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& Name) + * + * @param pNodeMap + * - Type: GenApi::INodeMap* + * - A node map + * + * @param Name + * - Type: const GenICam::gcstring& + * - Node name + * + * @return + * - Type: template T + * - Value of the node + * - Template type representation + * - Accepts int64_t, double, bool, GenICam::gcstring + * - Integer nodes use int64_t + * - Float nodes use double + * - Boolean nodes use bool + * - String, enumeration nodes use GenICam::gcstring + * + * GetNodeValue retrieves a node and gets its value. + * + * @warning + * - appropriate for integer, float, boolean, string, and enumeration nodes + * - accepts int64_t for integer nodes + * - accepts double for float nodes + * - accepts bool for boolean nodes + * - accepts GenICam::gcstring for string, enumeration nodes + */ + template + ARENA_API T GetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& Name); + + extern template ARENA_API bool GetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iBooleanNodeName); + extern template ARENA_API double GetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iFloatNodeName); + extern template ARENA_API int64_t GetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iIntegerNodeName); + extern template ARENA_API GenICam::gcstring GetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iEnumerationNodeName); + ARENA_API void GetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iRegisterNodeName, uint8_t* pOutputBuffer, int64_t& outputBufferLength); + + /** + * @fn template void SetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& Name, const T& Value) + * + * @param pNodeMap + * - Type: GenApi::INodeMap* + * - A node map + * + * @param Name + * - Type: const GenICam::gcstring& + * - Node name + * + * @param Value + * - Type: const template T& + * - Value to set + * - Template type representation + * - Accepts int64_t, double, bool, GenICam::gcstring: + * - Integer nodes use int64_t + * - Float nodes use double + * - Boolean nodes use bool + * - String, enumeration nodes use GenICam::gcstring* + * + * @return + * - none + * + * SetNodeValue retrieves a node from a node map and sets its value. + * + * @warning + * - appropriate for integer, float, boolean, string, and enumeration nodes + * - accepts int64_t for integer nodes + * - accepts double for float nodes + * - accepts bool for boolean nodes + * - accepts GenICam::gcstring for string, enumeration nodes + */ + template + ARENA_API void SetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& Name, const T& Value); + + extern template ARENA_API void SetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iBooleanNodeName, const bool& valueToSet); + extern template ARENA_API void SetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iFloatNodeName, const double& valueToSet); + extern template ARENA_API void SetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iIntegerNodeName, const int64_t& valueToSet); + extern template ARENA_API void SetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iEnumerationNodeName, const GenICam::gcstring& valueToSet); + ARENA_API void SetNodeValue(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iRegisterNodeName, const uint8_t* pBuffer, int64_t& length); + + /** + * @fn void ExecuteNode(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iCommandNodeName) + * + * @param pNodeMap + * - Type: GenApi::INodeMap* + * - A node map + * + * @param iCommandNodeName + * - Type: const GenICam::gcstring& + * - Node name + * + * ExecuteNode retrieves a node from a node map and executes it. + * + * @warning + * - only appropriate for command nodes + */ + ARENA_API void ExecuteNode(GenApi::INodeMap* pNodeMap, const GenICam::gcstring& iCommandNodeName); +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/GenTL.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/GenTL.h new file mode 100644 index 00000000..49df6f90 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/GenTL.h @@ -0,0 +1,793 @@ +/**************************************************************************** +(c) 2004-2015 by GenICam GenTL Subcommittee + +License: This file is published under the license of the EMVA GenICam Standard Group. +A text file describing the legal terms is included in your installation as 'license.txt'. +If for some reason you are missing this file please contact the EMVA or visit the website +(http://www.genicam.org) for a full copy. + +THIS SOFTWARE IS PROVIDED BY THE EMVA GENICAM STANDARD GROUP "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE EMVA GENICAM STANDARD GROUP +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +******************************************************************************/ + +/* \file GenTL.h + * \brief GenICam Transport Layer Client Interface + * \version 1.5 + * \author GenTL Subcommittee + * \date 2015 + * + * \par Version history + * \li Version 0.1.0 First official version from the meeting in Pilsen + * \li Version 0.1.1 rst (SI) 0.4.160 + * - added _E_INTERFACE_INFO_CMD_LIST_T_ enum name + * - added _E_DEVICE_ACCCESS_STATUS_TYPE_T_ to be used + * with the /a IFGetDeviceInfo and DevGetInfo + * - rename of the enum value DEVICE_INFO_ACCESSMODE to + * DEVICE_INFO_ACCESS_STATUS which now refers to + * _E_DEVICE_ACCCESS_STATUS_TYPE_T_ + * - added Timeout parameter to update interface list and + * device list. + * \li Version 0.1.2 - change datatype of timeout parameter of + * TLUpdateInterfaceList and IFUpdateDeviceList to + * uint64_t to match with the timeout in the event object. + * - changed all enums to have a typedef to uint32_t + * with them to allow custom ids + * - changed type of string constants to be char * instead + * of gcstring + * \li Version 0.1.3 rst (SI), cbi (IDS) 0.4.163 + * - adjusted parameter names to be closer aligned with + * the standard text + * - changed typedefs for enums from uint32_t to int32_t + * - removed default parameter + * - added parameter name to DevGetPort function + * \li Version 0.1.4 jb (LV) + * - fixes to align the file with standard text + * - make the file self-contained, independent on GenApi + * \li Version 0.1.5 rst (SI) cbi (IDS) jb (LV) tho (MVTec) + * - Adjust it for Linux + * - Cosmetics + * \li Version 1.0 rst (SI) cbi (IDS) jb (LV) tho (MVTec) + * - Adjust for Standard 1.0 + * - Make it plain C compliant + * - Cosmetics + * \li Version 1.2 rst (SI) jb (LV) tho (MVTec) + * - Adjust for Standard 1.2 + * - adjust packing + * - Cosmetics + * \li Version 1.3 (Stemmer, Leutron, Matrix Vision, MVTec, MathWorks) + * - Adjust for Standard 1.3 + * - added chunk handling + * - added Mac OS X + * - Cosmetics + * \li Version 1.3.1 (MathWorks) + * - Spelling corrections in comments + * \li Version 1.4 GenTL Subcommittee + * \li Version 1.5 GenTL Subcommittee + * - Changed namespace to GenTL + * - Changes for GenTL 1.5, Please refer to the GenTL spec + * for a list of changes. + */ + + +#ifndef GC_TLI_CLIENT_H_ +#define GC_TLI_CLIENT_H_ 1 + +#ifndef GC_USER_DEFINED_TYPES + /* The types should be the same as defined in GCTypes.h from GenApi. But in + * case you do not have this header the necessary types are defined here. */ +# if defined(_WIN32) +# if defined(_MSC_VER) && _MSC_VER >= 1600 /* VS2010 provides stdint.h */ +# include +# elif !defined _STDINT_H && !defined _STDINT + /* stdint.h is usually not available under Windows */ +typedef unsigned char uint8_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +# endif +# else +# include +# endif + +# ifdef __cplusplus +typedef bool bool8_t; +# else +typedef uint8_t bool8_t; +# endif +#endif /* GC_DEFINE_TYPES */ + +#include + +/* Function declaration modifiers */ +#if defined (_WIN32) +# ifndef GCTLI_NO_DECLSPEC_STATEMENTS +# ifdef GCTLIDLL +# define GC_IMPORT_EXPORT __declspec(dllexport) +# else +# define GC_IMPORT_EXPORT __declspec(dllimport) +# endif +# else +# define GC_IMPORT_EXPORT +# endif /* # ifndef GCTLI_NO_DECLSPEC_STATEMENTS */ +# if defined (_M_IX86) || defined (__i386__) +# define GC_CALLTYPE __stdcall +# else +# define GC_CALLTYPE /* default */ +# endif +# ifndef EXTERN_C +# define EXTERN_C extern "C" +# endif + +#elif defined (__GNUC__) && (__GNUC__ >= 4) && (defined (__linux__) || defined (__APPLE__)) +# define GC_IMPORT_EXPORT __attribute__((visibility("default"))) +# if defined (__i386__) +# define GC_CALLTYPE __attribute__((stdcall)) +# else +# define GC_CALLTYPE /* default */ +# endif +# ifndef EXTERN_C +# define EXTERN_C extern "C" +# endif + +#else +# error Unknown platform, file needs adaption +#endif + +#ifdef __cplusplus +extern "C" +{ + namespace GenTL + { +#endif + + /* Errors */ + enum GC_ERROR_LIST + { + GC_ERR_SUCCESS = 0, + GC_ERR_ERROR = -1001, + GC_ERR_NOT_INITIALIZED = -1002, + GC_ERR_NOT_IMPLEMENTED = -1003, + GC_ERR_RESOURCE_IN_USE = -1004, + GC_ERR_ACCESS_DENIED = -1005, + GC_ERR_INVALID_HANDLE = -1006, + GC_ERR_INVALID_ID = -1007, + GC_ERR_NO_DATA = -1008, + GC_ERR_INVALID_PARAMETER = -1009, + GC_ERR_IO = -1010, + GC_ERR_TIMEOUT = -1011, + GC_ERR_ABORT = -1012, /* GenTL v1.1 */ + GC_ERR_INVALID_BUFFER = -1013, /* GenTL v1.1 */ + GC_ERR_NOT_AVAILABLE = -1014, /* GenTL v1.2 */ + GC_ERR_INVALID_ADDRESS = -1015, /* GenTL v1.3 */ + GC_ERR_BUFFER_TOO_SMALL = -1016, /* GenTL v1.4 */ + GC_ERR_INVALID_INDEX = -1017, /* GenTL v1.4 */ + GC_ERR_PARSING_CHUNK_DATA = -1018, /* GenTL v1.4 */ + GC_ERR_INVALID_VALUE = -1019, /* GenTL v1.4 */ + GC_ERR_RESOURCE_EXHAUSTED = -1020, /* GenTL v1.4 */ + GC_ERR_OUT_OF_MEMORY = -1021, /* GenTL v1.4 */ + GC_ERR_BUSY = -1022, /* GenTL v1.5 */ + + GC_ERR_CUSTOM_ID = -10000 + }; + typedef int32_t GC_ERROR; + +# ifndef GC_GENTL_HEADER_VERSION + +# define GenTLMajorVersion 1 /* defines the major version of the GenICam GenTL standard version this header is based on */ +# define GenTLMinorVersion 5 /* defines the minor version of the GenICam GenTL standard version this header is based on */ +# define GenTLSubMinorVersion 0 /* defines the sub minor version of the GenICam GenTL standard version this header is based on */ + +# define GC_GENTL_HEADER_VERSION_CODE(major,minor,subminor) (((major)<<24)+((minor)<<16)+(subminor)) +# define GC_GENTL_HEADER_VERSION GC_GENTL_HEADER_VERSION_CODE(GenTLMajorVersion,GenTLMinorVersion,GenTLSubMinorVersion) + +# endif /* GC_GENTL_HEADER_VERSION */ + +# ifndef GC_GENTL_DONT_USE_TYPE_DEFINITIONS +# define TLTypeMixedName "Mixed" /* Type to use for several supported technologies */ +# define TLTypeCustomName "Custom" /* Type to use for custom technologies */ +# define TLTypeGEVName "GEV" /* Type to use for GigE Vision technology */ +# define TLTypeCLName "CL" /* Type to use for Camera Link technology */ +# define TLTypeIIDCName "IIDC" /* Type to use for IIDC 1394 technology */ +# define TLTypeUVCName "UVC" /* Type to use for USB video class devices */ +# define TLTypeCXPName "CXP" /* Type to use for CoaXPress, V1.3 */ +# define TLTypeCLHSName "CLHS" /* Type to use for Camera Link HS, V1.3 */ +# define TLTypeU3VName "U3V" /* Type to use for USB3 Vision Standard, V1.4 */ +# define TLTypeETHERNETName "Ethernet" /* Type to use for Ethernet devices, V1.3 */ +# define TLTypePCIName "PCI" /* Type to use for PCI/PCIe devices, V1.3 */ +# endif /* GC_GENTL_DONT_USE_TYPE_DEFINITIONS */ + +# ifndef GC_GENTL_DONT_USE_MODULE_NAMES +# define TLSystemModuleName "TLSystem" /* Name to identify a system module */ +# define TLInterfaceModuleName "TLInterface" /* Name to identify a interface module */ +# define TLDeviceModuleName "TLDevice" /* Name to identify a device module */ +# define TLDataStreamModuleName "TLDataStream" /* Name to identify a data stream module */ +# define TLBufferModuleName "TLBuffer" /* Name to identify a buffer module */ +# define TLRemoteDeviceModuleName "Device" /* Name to identify a remote device module */ +# endif /* GC_GENTL_DONT_USE_MODULE_NAMES */ + + /* Handles */ + typedef void * TL_HANDLE; /* Transport Layer handle, obtained through the TLOpen */ + typedef void * IF_HANDLE; /* Interface handle, obtained through ::TLOpenInterface */ + typedef void * DEV_HANDLE; /* Device Handle, obtained through the ::IFOpenDevice */ + typedef void * DS_HANDLE; /* Handle to an image stream object, obtained through DevOpenDataStream */ + typedef void * PORT_HANDLE; /* A Port handle is used to access the register space of a port */ + /* a PORT_HANDLE can be one of the following TL_HANDLE, IF_HANDLE, */ + /* DEV_HANDLE, handle to a device port, obtained through ::DevGetPort, */ + /* DS_HANDLE, BUFFER_HANDLE */ + + typedef void * BUFFER_HANDLE; /* BufferHandle, obtained through the ::DSAnnounceBuffer function */ + typedef void * EVENTSRC_HANDLE; /* A Event source handle is used to register a OS Event and to retrieve a GenTL event handle */ + /* a EVENTSRC_HANDLE can be on of the following TL_HANDLE, */ + /* IF_HANDLE, DEV_HANDLE, A handle to a device port, obtained through ::DevGetPort */ + /* DS_HANDLE, BUFFER_HANDLE */ + typedef void * EVENT_HANDLE; /* Event Handle */ + +# define GENTL_INVALID_HANDLE NULL /* Invalid handle value, V1.4 */ +# define GENTL_INFINITE 0xFFFFFFFFFFFFFFFFULL /* Infinite value to be used in various function calls, V1.4 */ + + /* Defines the data type possible for the various Info functions. */ + enum INFO_DATATYPE_LIST + { + INFO_DATATYPE_UNKNOWN = 0, /* Unknown data type */ + INFO_DATATYPE_STRING = 1, /* NULL-terminated C string (ASCII encoded). */ + INFO_DATATYPE_STRINGLIST = 2, /* Concatenated INFO_DATATYPE_STRING list. End of list is signaled with an additional NULL. */ + INFO_DATATYPE_INT16 = 3, /* Signed 16 bit integer. */ + INFO_DATATYPE_UINT16 = 4, /* Unsigned 16 bit integer */ + INFO_DATATYPE_INT32 = 5, /* Signed 32 bit integer */ + INFO_DATATYPE_UINT32 = 6, /* Unsigned 32 bit integer */ + INFO_DATATYPE_INT64 = 7, /* Signed 64 bit integer */ + INFO_DATATYPE_UINT64 = 8, /* Unsigned 64 bit integer */ + INFO_DATATYPE_FLOAT64 = 9, /* Signed 64 bit floating point number. */ + INFO_DATATYPE_PTR = 10, /* Pointer type (void*). Size is platform dependent (32 bit on 32 bit platforms). */ + INFO_DATATYPE_BOOL8 = 11, /* Boolean value occupying 8 bit. 0 for false and anything for true. */ + INFO_DATATYPE_SIZET = 12, /* Platform dependent unsigned integer (32 bit on 32 bit platforms). */ + INFO_DATATYPE_BUFFER = 13, /* Like a INFO_DATATYPE_STRING but with arbitrary data and no NULL termination. */ + INFO_DATATYPE_PTRDIFF = 14, /* Platform dependent signed integer (32 bit on 32 bit platforms). GenTL v1.3 */ + + INFO_DATATYPE_CUSTOM_ID = 1000 /* Starting value for custom IDs. */ + }; + typedef int32_t INFO_DATATYPE; + + /* Defines char encoding schemes used by the producer, GenTL v1.4 */ + enum TL_CHAR_ENCODING_LIST + { + TL_CHAR_ENCODING_ASCII = 0, + TL_CHAR_ENCODING_UTF8 = 1 + }; + typedef int32_t TL_CHAR_ENCODING; /* GenTL v1.4 */ + + /* System module information commands for the GenICam::TL::Client::TLGetInfo and GenICam::TL::Client::GCGetInfo functions. */ + enum TL_INFO_CMD_LIST + { + TL_INFO_ID = 0, /* STRING Transport layer ID. */ + TL_INFO_VENDOR = 1, /* STRING Transport layer vendor name. */ + TL_INFO_MODEL = 2, /* STRING Transport layer model name. */ + TL_INFO_VERSION = 3, /* STRING Transport layer version. */ + TL_INFO_TLTYPE = 4, /* STRING Transport layer technology that is supported. */ + TL_INFO_NAME = 5, /* STRING File name including extension of the library. */ + TL_INFO_PATHNAME = 6, /* STRING Full path including file name and extension of the library. */ + TL_INFO_DISPLAYNAME = 7, /* STRING User readable name of the device. If this is not defined in the device this should be VENDOR MODEL (ID). */ + TL_INFO_CHAR_ENCODING = 8, /* INT32 Reporting the char encoding used by this Producer, GenTL v1.4 */ + TL_INFO_GENTL_VER_MAJOR = 9, /* UINT32 Major number of the GenTL spec this producer complies with, GenTL v1.5 */ + TL_INFO_GENTL_VER_MINOR = 10, /* UINT32 Minor number of the GenTL spec this producer complies with, GenTL v1.5 */ + TL_INFO_CUSTOM_ID = 1000 /* Starting value for custom IDs. */ + }; + typedef int32_t TL_INFO_CMD; + + /* This enumeration defines commands to retrieve information with the GenICam::TL::Client::IFGetInfo function from the Interface module. */ + enum INTERFACE_INFO_CMD_LIST + { + INTERFACE_INFO_ID = 0, /* STRING Unique ID of the interface. */ + INTERFACE_INFO_DISPLAYNAME = 1, /* STRING User readable name of the interface. */ + INTERFACE_INFO_TLTYPE = 2, /* STRING Transport layer technology that is supported. */ + + INTERFACE_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t INTERFACE_INFO_CMD; + + /* This enumeration defines flags of how a device is to be opened with the GenICam::TL::Client::IFOpenDevice function. */ + enum DEVICE_ACCESS_FLAGS_LIST + { + DEVICE_ACCESS_UNKNOWN = 0, /* Not used in a command. Can be used to initialize a variable to query that information. */ + DEVICE_ACCESS_NONE = 1, /* This either means that the device is not open because it was not opened before or the access to it was denied. */ + DEVICE_ACCESS_READONLY = 2, /* Open the device read only. All Port functions can only read from the device. */ + DEVICE_ACCESS_CONTROL = 3, /* Open the device in a way that other hosts/processes can have read only access to the device. Device access level is read/write for this process. */ + DEVICE_ACCESS_EXCLUSIVE = 4, /* Open the device in a way that only this host/process can have access to the device. Device access level is read/write for this process. */ + + DEVICE_ACCESS_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t DEVICE_ACCESS_FLAGS; + + /* This enumeration defines values for the accessibility of the device to be returned in the GenICam::TL::Client::DevGetInfo function on a device handle. */ + enum DEVICE_ACCESS_STATUS_LIST + { + DEVICE_ACCESS_STATUS_UNKNOWN = 0, /* The device accessibility is not known. */ + DEVICE_ACCESS_STATUS_READWRITE = 1, /* The device is available for read/write access. */ + DEVICE_ACCESS_STATUS_READONLY = 2, /* The device is available for read only access. */ + DEVICE_ACCESS_STATUS_NOACCESS = 3, /* The device is not accessible. */ + DEVICE_ACCESS_STATUS_BUSY = 4, /* The device has already been opened by another process/host. GenTL v1.5 */ + DEVICE_ACCESS_STATUS_OPEN_READWRITE = 5, /* The device has already been opened by this process. GenTL v1.5 */ + DEVICE_ACCESS_STATUS_OPEN_READONLY = 6, /* The device has already been opened by this process. GenTL v1.5 */ + + DEVICE_ACCESS_STATUS_CUSTOM_ID = 1000 /* Starting value for custom IDs. */ + }; + typedef int32_t DEVICE_ACCESS_STATUS; + + /* This enumeration defines commands to retrieve information with the GenICam::TL::Client::DevGetInfo function on a device handle. */ + enum DEVICE_INFO_CMD_LIST + { + DEVICE_INFO_ID = 0, /* STRING Unique ID of the device. */ + DEVICE_INFO_VENDOR = 1, /* STRING Device vendor name. */ + DEVICE_INFO_MODEL = 2, /* STRING Device model name. */ + DEVICE_INFO_TLTYPE = 3, /* STRING Transport layer technology that is supported. */ + DEVICE_INFO_DISPLAYNAME = 4, /* STRING String containing a display name for the device ( including a unique id ) */ + DEVICE_INFO_ACCESS_STATUS = 5, /* INT32 Gets the access mode the GenTL Producer has on the opened device. (DEVICE_ACCESS_STATUS enumeration value). */ + DEVICE_INFO_USER_DEFINED_NAME = 6, /* STRING String containing the user defined name, GenTL v1.4 */ + DEVICE_INFO_SERIAL_NUMBER = 7, /* STRING String containing the device's serial number, GenTL v1.4 */ + DEVICE_INFO_VERSION = 8, /* STRING String containing the device version, GenTL v1.4 */ + DEVICE_INFO_TIMESTAMP_FREQUENCY = 9, /* UINT64 Tick-frequency of the time stamp clock, GenTL v1.4 */ + + DEVICE_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t DEVICE_INFO_CMD; + + /* This enumeration defines special stop flags for the acquisition engine. The function used is GenICam::TL::Client::DSStopAcquisition. */ + enum ACQ_STOP_FLAGS_LIST + { + ACQ_STOP_FLAGS_DEFAULT = 0, /* Stop the acquisition engine when the currently running tasks like filling a buffer are completed (default behavior). */ + ACQ_STOP_FLAGS_KILL = 1, /* Stop the acquisition engine immediately and leave buffers currently being filled in the Input Buffer Pool. */ + + ACQ_STOP_FLAGS_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t ACQ_STOP_FLAGS; + + /* This enumeration defines special start flags for the acquisition engine. The function used is GenICam::TL::Client::DSStartAcquisition. */ + enum ACQ_START_FLAGS_LIST + { + ACQ_START_FLAGS_DEFAULT = 0, /* Default behavior. */ + + ACQ_START_FLAGS_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t ACQ_START_FLAGS; + + /* This enumeration commands from which to which queue/pool buffers are flushed with the GenICam::TL::Client::DSFlushQueue function. */ + enum ACQ_QUEUE_TYPE_LIST + { + ACQ_QUEUE_INPUT_TO_OUTPUT = 0, /* Flushes the input pool to the output queue and if necessary adds entries in the New Buffer event data queue. */ + ACQ_QUEUE_OUTPUT_DISCARD = 1, /* Discards all buffers in the output queue and if necessary remove the entries from the event data queue. */ + ACQ_QUEUE_ALL_TO_INPUT = 2, /* Puts all buffers in the input pool. Even those in the output queue and discard entries in the event data queue. */ + ACQ_QUEUE_UNQUEUED_TO_INPUT = 3, /* Puts all buffers that are not in the input pool or the output queue in the input pool. */ + ACQ_QUEUE_ALL_DISCARD = 4, /* Discards all buffers in the input pool and output queue. */ + + ACQ_QUEUE_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t ACQ_QUEUE_TYPE; + + /* This enumeration defines commands to retrieve information with the GenICam::TL::Client::DSGetInfo function on a data stream handle */ + enum STREAM_INFO_CMD_LIST + { + STREAM_INFO_ID = 0, /* STRING Unique ID of the data stream. */ + STREAM_INFO_NUM_DELIVERED = 1, /* UINT64 Number of delivered buffers since last acquisition start. */ + STREAM_INFO_NUM_UNDERRUN = 2, /* UINT64 Number of lost buffers due to queue underrun. */ + STREAM_INFO_NUM_ANNOUNCED = 3, /* SIZET Number of announced buffers. */ + STREAM_INFO_NUM_QUEUED = 4, /* SIZET Number of buffers in the input pool. */ + STREAM_INFO_NUM_AWAIT_DELIVERY = 5, /* SIZET Number of buffers in the output queue. */ + STREAM_INFO_NUM_STARTED = 6, /* UINT64 Number of buffers started in the acquisition engine. */ + STREAM_INFO_PAYLOAD_SIZE = 7, /* SIZET Size of the expected data in bytes. */ + STREAM_INFO_IS_GRABBING = 8, /* BOOL8 Flag indicating whether the acquisition engine is started or not. */ + STREAM_INFO_DEFINES_PAYLOADSIZE = 9, /* BOOL8 Flag that indicated that this data stream defines a payload size independent from the remote device. */ + STREAM_INFO_TLTYPE = 10, /* STRING Transport layer technology that is supported. */ + STREAM_INFO_NUM_CHUNKS_MAX = 11, /* SIZET Max number of chunks in a buffer, if known. GenTL v1.3 */ + STREAM_INFO_BUF_ANNOUNCE_MIN = 12, /* SIZET Min number of buffers to announce before acq can start, if known. GenTL v1.3 */ + STREAM_INFO_BUF_ALIGNMENT = 13, /* SIZET Buffer alignment in bytes. GenTL v1.3 */ + + STREAM_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t STREAM_INFO_CMD; + + /* This enumeration defines commands to retrieve information with the GenICam::TL::Client::DSGetBufferInfo function on a buffer handle. */ + enum BUFFER_INFO_CMD_LIST + { + BUFFER_INFO_BASE = 0, /* PTR Base address of the buffer memory. */ + BUFFER_INFO_SIZE = 1, /* SIZET Size of the buffer in bytes. */ + BUFFER_INFO_USER_PTR = 2, /* PTR Private data pointer of the GenTL Consumer. */ + BUFFER_INFO_TIMESTAMP = 3, /* UINT64 Timestamp the buffer was acquired. */ + BUFFER_INFO_NEW_DATA = 4, /* BOOL8 Flag to indicate that the buffer contains new data since the last call. */ + BUFFER_INFO_IS_QUEUED = 5, /* BOOL8 Flag to indicate if the buffer is in the input pool or output queue. */ + BUFFER_INFO_IS_ACQUIRING = 6, /* BOOL8 Flag to indicate that the buffer is currently being filled with data. */ + BUFFER_INFO_IS_INCOMPLETE = 7, /* BOOL8 Flag to indicate that a buffer was filled but an error occurred during that process. */ + BUFFER_INFO_TLTYPE = 8, /* STRING Transport layer technology that is supported. */ + BUFFER_INFO_SIZE_FILLED = 9, /* SIZET Number of bytes written into the buffer last time it has been filled. This value is reset to 0 when the buffer is placed into the Input Buffer Pool. */ + BUFFER_INFO_WIDTH = 10, /* SIZET GenTL v1.2 */ + BUFFER_INFO_HEIGHT = 11, /* SIZET GenTL v1.2 */ + BUFFER_INFO_XOFFSET = 12, /* SIZET GenTL v1.2 */ + BUFFER_INFO_YOFFSET = 13, /* SIZET GenTL v1.2 */ + BUFFER_INFO_XPADDING = 14, /* SIZET GenTL v1.2 */ + BUFFER_INFO_YPADDING = 15, /* SIZET GenTL v1.2 */ + BUFFER_INFO_FRAMEID = 16, /* UINT64 GenTL v1.2 */ + BUFFER_INFO_IMAGEPRESENT = 17, /* BOOL8 GenTL v1.2 */ + BUFFER_INFO_IMAGEOFFSET = 18, /* SIZET GenTL v1.2 */ + BUFFER_INFO_PAYLOADTYPE = 19, /* SIZET GenTL v1.2 */ + BUFFER_INFO_PIXELFORMAT = 20, /* UINT64 GenTL v1.2 */ + BUFFER_INFO_PIXELFORMAT_NAMESPACE = 21, /* UINT64 GenTL v1.2 */ + BUFFER_INFO_DELIVERED_IMAGEHEIGHT = 22, /* SIZET GenTL v1.2 */ + BUFFER_INFO_DELIVERED_CHUNKPAYLOADSIZE = 23, /* SIZET GenTL v1.2 */ + BUFFER_INFO_CHUNKLAYOUTID = 24, /* UINT64 GenTL v1.2 */ + BUFFER_INFO_FILENAME = 25, /* STRING GenTL v1.2 */ + BUFFER_INFO_PIXEL_ENDIANNESS = 26, /* INT32 GenTL v1.4 */ + BUFFER_INFO_DATA_SIZE = 27, /* SIZET GenTL v1.4 */ + BUFFER_INFO_TIMESTAMP_NS = 28, /* UINT64 GenTL v1.4 */ + BUFFER_INFO_DATA_LARGER_THAN_BUFFER = 29, /* BOOL8 GenTL v1.4 */ + BUFFER_INFO_CONTAINS_CHUNKDATA = 30, /* BOOL8 GenTL v1.4 */ + + BUFFER_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t BUFFER_INFO_CMD; + + /* This enumeration defines commands to retrieve information about individual data parts in a multi-part buffer + using the GenICam::TL::Client::DSGetBufferPartInfo function. Introduced in GenTL v1.5. */ + enum BUFFER_PART_INFO_CMD_LIST + { + BUFFER_PART_INFO_BASE = 0, /* PTR Base address of the buffer part memory. */ + BUFFER_PART_INFO_DATA_SIZE = 1, /* SIZET Size of the buffer part in bytes. */ + BUFFER_PART_INFO_DATA_TYPE = 2, /* SIZET Type of the data in given part (PARTDATATYPE_ID enumeration value). */ + BUFFER_PART_INFO_DATA_FORMAT = 3, /* UINT64 Format of individual items (such as pixels) in the buffer part. */ + BUFFER_PART_INFO_DATA_FORMAT_NAMESPACE = 4, /* UINT64 Allows interpretation of BUFFER_PART_INFO_DATA_FORMAT (PIXELFORMAT_NAMESPACE_ID enumeration value). */ + BUFFER_PART_INFO_WIDTH = 5, /* SIZET Width of data in the buffer part in pixels. */ + BUFFER_PART_INFO_HEIGHT = 6, /* SIZET Expected height of data in the buffer part in pixels . */ + BUFFER_PART_INFO_XOFFSET = 7, /* SIZET Horizontal offset of data in the buffer part in pixels. */ + BUFFER_PART_INFO_YOFFSET = 8, /* SIZET Vertical offset of data in the buffer part in pixels. */ + BUFFER_PART_INFO_XPADDING = 9, /* SIZET Horizontal padding of data in the buffer part in pixels. */ + BUFFER_PART_INFO_SOURCE_ID = 10, /* UINT64 Identifier allowing to group data parts belonging to the same source. */ + BUFFER_PART_INFO_DELIVERED_IMAGEHEIGHT = 11, /* SIZET Height of the data currently in the buffer part in pixels*/ + BUFFER_PART_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t BUFFER_PART_INFO_CMD; /* GenTL v1.5 */ + + /* Enumeration of TLType dependent payload types. Introduced in GenTL v1.2 */ + enum PAYLOADTYPE_INFO_IDS + { + PAYLOAD_TYPE_UNKNOWN = 0, /* GenTL v1.2 */ + PAYLOAD_TYPE_IMAGE = 1, /* GenTL v1.2 */ + PAYLOAD_TYPE_RAW_DATA = 2, /* GenTL v1.2 */ + PAYLOAD_TYPE_FILE = 3, /* GenTL v1.2 */ + PAYLOAD_TYPE_CHUNK_DATA = 4, /* GenTL v1.2, Deprecated in GenTL 1.5*/ + PAYLOAD_TYPE_JPEG = 5, /* GenTL v1.4 */ + PAYLOAD_TYPE_JPEG2000 = 6, /* GenTL v1.4 */ + PAYLOAD_TYPE_H264 = 7, /* GenTL v1.4 */ + PAYLOAD_TYPE_CHUNK_ONLY = 8, /* GenTL v1.4 */ + PAYLOAD_TYPE_DEVICE_SPECIFIC = 9, /* GenTL v1.4 */ + PAYLOAD_TYPE_MULTI_PART = 10, /* GenTL v1.5 */ + + PAYLOAD_TYPE_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t PAYLOADTYPE_INFO_ID; + + /* Enumeration of TLType dependent pixel format namespaces introduced GenTL v1.2 */ + enum PIXELFORMAT_NAMESPACE_IDS + { + PIXELFORMAT_NAMESPACE_UNKNOWN = 0, /* GenTL v1.2 */ + PIXELFORMAT_NAMESPACE_GEV = 1, /* GenTL v1.2 */ + PIXELFORMAT_NAMESPACE_IIDC = 2, /* GenTL v1.2 */ + PIXELFORMAT_NAMESPACE_PFNC_16BIT = 3, /* GenTL v1.4 */ + PIXELFORMAT_NAMESPACE_PFNC_32BIT = 4, /* GenTL v1.4 */ + + PIXELFORMAT_NAMESPACE_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t PIXELFORMAT_NAMESPACE_ID; /* GenTL v1.2 */ + + /* Enumeration of pixel endianness values. Introduced in GenTL v1.4 */ + enum PIXELENDIANNESS_IDS + { + PIXELENDIANNESS_UNKNOWN = 0, /* Unknown pixel endianness. GenTL v1.4 */ + PIXELENDIANNESS_LITTLE = 1, /* Little endian pixel data. GenTL v1.4 */ + PIXELENDIANNESS_BIG = 2 /* Big endian pixel data. GenTL v1.4 */ + }; + typedef int32_t PIXELENDIANNESS_ID; /* GenTL v1.4*/ + + /* Enumeration describing which data type is present in given buffer part. Introduced in GenTL v1.5 */ + enum PARTDATATYPE_IDS + { + PART_DATATYPE_UNKNOWN = 0, /* Unknown data type */ + PART_DATATYPE_2D_IMAGE = 1, /* Color or monochrome 2D image. */ + PART_DATATYPE_2D_PLANE_BIPLANAR = 2, /* Single color plane of a planar 2D image consisting of 2 planes. */ + PART_DATATYPE_2D_PLANE_TRIPLANAR = 3, /* Single color plane of a planar 2D image consisting of 3 planes. */ + PART_DATATYPE_2D_PLANE_QUADPLANAR = 4, /* Single color plane of a planar 2D image consisting of 4 planes. */ + PART_DATATYPE_3D_IMAGE = 5, /* 3D image (pixel coordinates). */ + PART_DATATYPE_3D_PLANE_BIPLANAR = 6, /* Single plane of a planar 3D image consisting of 2 planes. */ + PART_DATATYPE_3D_PLANE_TRIPLANAR = 7, /* Single plane of a planar 3D image consisting of 3 planes. */ + PART_DATATYPE_3D_PLANE_QUADPLANAR = 8, /* Single plane of a planar 3D image consisting of 4 planes. */ + PART_DATATYPE_CONFIDENCE_MAP = 9, /* Confidence of the individual pixel values. */ + + PART_DATATYPE_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t PARTDATATYPE_ID; /* GenTL v1.5*/ + + /* This enumeration defines commands to retrieve information with the GenICam::TL::Client::GCGetPortInfo function on a module or remote device handle. */ + enum PORT_INFO_CMD_LIST + { + PORT_INFO_ID = 0, /* STRING Unique ID of the port. */ + PORT_INFO_VENDOR = 1, /* STRING Port vendor name. */ + PORT_INFO_MODEL = 2, /* STRING Port model name. */ + PORT_INFO_TLTYPE = 3, /* STRING Transport layer technology that is supported. */ + PORT_INFO_MODULE = 4, /* STRING GenTL Module the port refers to. */ + PORT_INFO_LITTLE_ENDIAN = 5, /* BOOL8 Flag indicating that the port data is little endian. */ + PORT_INFO_BIG_ENDIAN = 6, /* BOOL8 Flag indicating that the port data is big endian. */ + PORT_INFO_ACCESS_READ = 7, /* BOOL8 Port has read access. */ + PORT_INFO_ACCESS_WRITE = 8, /* BOOL8 Port has write access. */ + PORT_INFO_ACCESS_NA = 9, /* BOOL8 Port is not accessible. */ + PORT_INFO_ACCESS_NI = 10, /* BOOL8 Port is not implemented. */ + PORT_INFO_VERSION = 11, /* STRING Version of the port. */ + PORT_INFO_PORTNAME = 12, /* STRING Name of the port as referenced in the XML description. */ + + PORT_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t PORT_INFO_CMD; + + /* This enumeration defines enum values returned by the URL_INFO_SCHEME command. + introduced in GenTL v1.5 */ + enum URL_SCHEME_IDS + { + URL_SCHEME_LOCAL = 0, /* The XML can be read from the local register map */ + URL_SCHEME_HTTP = 1, /* The XML can be downloaded from a http server */ + URL_SCHEME_FILE = 2, /* The XML can be read from the local hard drive */ + + URL_SCHEME_CUSTOM_ID = 1000 /* Starting value for custom scheme locations */ + }; + typedef int32_t URL_SCHEME_ID; + + /* This enumeration defines commands to retrieve information with the GenICam::TL::Client::GCGetPortURLInfo + function on a module or remote device handle. Introduced in GenTL v1.1 */ + enum URL_INFO_CMD_LIST + { + URL_INFO_URL = 0, /* STRING URL as defined in chapter 4.1.2 GenTL v1.1 */ + URL_INFO_SCHEMA_VER_MAJOR = 1, /* INT32 Major version of the schema this URL refers to. GenTL v1.1 */ + URL_INFO_SCHEMA_VER_MINOR = 2, /* INT32 Minor version of the schema this URL refers to. GenTL v1.1 */ + URL_INFO_FILE_VER_MAJOR = 3, /* INT32 Major version of the XML-file this URL refers to. GenTL v1.1 */ + URL_INFO_FILE_VER_MINOR = 4, /* INT32 Minor version of the XML-file this URL refers to. GenTL v1.1 */ + URL_INFO_FILE_VER_SUBMINOR = 5, /* INT32 Subminor version of the XML-file this URL refers to. GenTL v1.1 */ + URL_INFO_FILE_SHA1_HASH = 6, /* BUFFER 160-bit SHA1 Hash code of XML-file. GenTL v1.4 */ + URL_INFO_FILE_REGISTER_ADDRESS = 7, /* UINT64 Register address in the device's register map. GenTL v1.5 */ + URL_INFO_FILE_SIZE = 8, /* UINT64 File size in bytes. GenTL v1.5 */ + URL_INFO_SCHEME = 9, /* INT32 Scheme of the URL as defined in URL_SCHEME_IDS. GenTL v1.5 */ + URL_INFO_FILENAME = 10, /* STRING File name if the scheme of the URL is file. GenTL v1.5 */ + + URL_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t URL_INFO_CMD; /* GenTL v1.1 */ + + /* Known event types that can be registered on certain modules with the GenICam::TL::Client::GCRegisterEvent function. */ + enum EVENT_TYPE_LIST + { + EVENT_ERROR = 0, /* Notification on module errors. */ + EVENT_NEW_BUFFER = 1, /* Notification on newly filled buffers. */ + EVENT_FEATURE_INVALIDATE = 2, /* Notification if a feature was changed by the GenTL Producer library and thus needs to be invalidated in the GenICam GenApi instance using the module. */ + EVENT_FEATURE_CHANGE = 3, /* Notification if the GenTL Producer library wants to manually set a feature in the GenICam GenApi instance using the module. */ + EVENT_REMOTE_DEVICE = 4, /* Notification if the GenTL Producer wants to inform the GenICam GenApi instance of the remote device that a GenApi compatible event was fired. */ + EVENT_MODULE = 5, /* Notification if the GenTL Producer wants to inform the GenICam GenApi instance of the module that a GenApi compatible event was fired. GenTL v1.4 */ + + EVENT_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t EVENT_TYPE; + + /* Event info command */ + enum EVENT_INFO_CMD_LIST + { + EVENT_EVENT_TYPE = 0, /* INT32 The event type of the event handle (EVENT_TYPE enum value). */ + EVENT_NUM_IN_QUEUE = 1, /* SIZET Number of events in the event data queue. */ + EVENT_NUM_FIRED = 2, /* UINT64 Number of events that were fired since the creation of the module. */ + EVENT_SIZE_MAX = 3, /* SIZET Max size of data carried with an event in bytes. GenTL v1.2 */ + EVENT_INFO_DATA_SIZE_MAX = 4, /* SIZET Max size of data provided through EventGetDataInfo in bytes. GenTL v1.2 */ + + EVENT_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t EVENT_INFO_CMD; + + /* Event data info command */ + enum EVENT_DATA_INFO_CMD_LIST + { + EVENT_DATA_ID = 0, /* Event specific Unique Event ID (String or Number)*/ + EVENT_DATA_VALUE = 1, /* Event specific Data */ + EVENT_DATA_NUMID = 2, /* UINT64 Numeric representation of the unique Event ID, GenTL v1.3. */ + + EVENT_DATA_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + }; + typedef int32_t EVENT_DATA_INFO_CMD; + + /* Structure of the data returned from a signaled "New Buffer" event. */ +# pragma pack (push, 1) + typedef struct S_EVENT_NEW_BUFFER + { + BUFFER_HANDLE BufferHandle; /* Buffer handle which contains new data. */ + void* pUserPointer; /* User pointer provided at announcement of the buffer. */ + } EVENT_NEW_BUFFER_DATA; +# pragma pack (pop) + + /* Structure to be use with GCWritePortStacked and GCReadPortStacked. */ +# pragma pack (push, 1) + typedef struct S_PORT_REGISTER_STACK_ENTRY + { + uint64_t Address; /* Address of the register. */ + void* pBuffer; /* Pointer to the buffer containing the data. */ + size_t Size; /* Number of bytes to read write. */ + } PORT_REGISTER_STACK_ENTRY; +# pragma pack (pop) + +# pragma pack (push, 1) + /* Structure carrying information about a single chunk in the buffer, V1.3 */ + typedef struct S_SINGLE_CHUNK_DATA + { + uint64_t ChunkID; /* Numeric representation of ChunkID */ + ptrdiff_t ChunkOffset; /* Chunk offset in the buffer */ + size_t ChunkLength; /* Size of the chunk data */ + } SINGLE_CHUNK_DATA; +# pragma pack (pop) + + + /* C API Interface Functions */ +# define GC_API GC_IMPORT_EXPORT GC_ERROR GC_CALLTYPE + GC_API GCGetInfo(TL_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + + GC_API GCGetLastError(GC_ERROR *piErrorCode, char *sErrText, size_t *piSize); + + GC_API GCInitLib(void); + GC_API GCCloseLib(void); + + GC_API GCReadPort(PORT_HANDLE hPort, uint64_t iAddress, void *pBuffer, size_t *piSize); + GC_API GCWritePort(PORT_HANDLE hPort, uint64_t iAddress, const void *pBuffer, size_t *piSize); + GC_API GCGetPortURL(PORT_HANDLE hPort, char *sURL, size_t *piSize); + + GC_API GCGetPortInfo(PORT_HANDLE hPort, PORT_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + + GC_API GCRegisterEvent(EVENTSRC_HANDLE hEventSrc, EVENT_TYPE iEventID, EVENT_HANDLE *phEvent); + GC_API GCUnregisterEvent(EVENTSRC_HANDLE hEventSrc, EVENT_TYPE iEventID); + + GC_API EventGetData(EVENT_HANDLE hEvent, void *pBuffer, size_t *piSize, uint64_t iTimeout); + GC_API EventGetDataInfo(EVENT_HANDLE hEvent, const void *pInBuffer, size_t iInSize, EVENT_DATA_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pOutBuffer, size_t *piOutSize); + GC_API EventGetInfo(EVENT_HANDLE hEvent, EVENT_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API EventFlush(EVENT_HANDLE hEvent); + GC_API EventKill(EVENT_HANDLE hEvent); + + GC_API TLOpen(TL_HANDLE *phTL); + GC_API TLClose(TL_HANDLE hTL); + GC_API TLGetInfo(TL_HANDLE hTL, TL_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + + GC_API TLGetNumInterfaces(TL_HANDLE hTL, uint32_t *piNumIfaces); + GC_API TLGetInterfaceID(TL_HANDLE hTL, uint32_t iIndex, char *sID, size_t *piSize); + GC_API TLGetInterfaceInfo(TL_HANDLE hTL, const char *sIfaceID, INTERFACE_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API TLOpenInterface(TL_HANDLE hTL, const char *sIfaceID, IF_HANDLE *phIface); + GC_API TLUpdateInterfaceList(TL_HANDLE hTL, bool8_t *pbChanged, uint64_t iTimeout); + + GC_API IFClose(IF_HANDLE hIface); + GC_API IFGetInfo(IF_HANDLE hIface, INTERFACE_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + + GC_API IFGetNumDevices(IF_HANDLE hIface, uint32_t *piNumDevices); + GC_API IFGetDeviceID(IF_HANDLE hIface, uint32_t iIndex, char *sIDeviceID, size_t *piSize); + GC_API IFUpdateDeviceList(IF_HANDLE hIface, bool8_t *pbChanged, uint64_t iTimeout); + GC_API IFGetDeviceInfo(IF_HANDLE hIface, const char *sDeviceID, DEVICE_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API IFOpenDevice(IF_HANDLE hIface, const char *sDeviceID, DEVICE_ACCESS_FLAGS iOpenFlags, DEV_HANDLE *phDevice); + + GC_API DevGetPort(DEV_HANDLE hDevice, PORT_HANDLE *phRemoteDevice); + GC_API DevGetNumDataStreams(DEV_HANDLE hDevice, uint32_t *piNumDataStreams); + GC_API DevGetDataStreamID(DEV_HANDLE hDevice, uint32_t iIndex, char *sDataStreamID, size_t *piSize); + GC_API DevOpenDataStream(DEV_HANDLE hDevice, const char *sDataStreamID, DS_HANDLE *phDataStream); + GC_API DevGetInfo(DEV_HANDLE hDevice, DEVICE_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API DevClose(DEV_HANDLE hDevice); + GC_API DevSendActionCommand(DEV_HANDLE hDevice, uint32_t device_key, uint32_t group_key, uint32_t group_mask, uint64_t action_time = 0); + + GC_API DSAnnounceBuffer(DS_HANDLE hDataStream, void *pBuffer, size_t iSize, void *pPrivate, BUFFER_HANDLE *phBuffer); + GC_API DSAllocAndAnnounceBuffer(DS_HANDLE hDataStream, size_t iSize, void *pPrivate, BUFFER_HANDLE *phBuffer); + GC_API DSFlushQueue(DS_HANDLE hDataStream, ACQ_QUEUE_TYPE iOperation); + GC_API DSStartAcquisition(DS_HANDLE hDataStream, ACQ_START_FLAGS iStartFlags, uint64_t iNumToAcquire); + GC_API DSStopAcquisition(DS_HANDLE hDataStream, ACQ_STOP_FLAGS iStopFlags); + GC_API DSGetInfo(DS_HANDLE hDataStream, STREAM_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API DSGetBufferID(DS_HANDLE hDataStream, uint32_t iIndex, BUFFER_HANDLE *phBuffer); + GC_API DSClose(DS_HANDLE hDataStream); + + GC_API DSRevokeBuffer(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer, void **pBuffer, void **pPrivate); + GC_API DSQueueBuffer(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer); + GC_API DSGetBufferInfo(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer, BUFFER_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + + /* GenTL v1.1 */ + GC_API GCGetNumPortURLs(PORT_HANDLE hPort, uint32_t *piNumURLs); + GC_API GCGetPortURLInfo(PORT_HANDLE hPort, uint32_t iURLIndex, URL_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API GCReadPortStacked(PORT_HANDLE hPort, PORT_REGISTER_STACK_ENTRY *pEntries, size_t *piNumEntries); + GC_API GCWritePortStacked(PORT_HANDLE hPort, PORT_REGISTER_STACK_ENTRY *pEntries, size_t *piNumEntries); + + /* GenTL v1.3 */ + GC_API DSGetBufferChunkData(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer, SINGLE_CHUNK_DATA *pChunkData, size_t *piNumChunks); + + /* GenTL v1.4 */ + GC_API IFGetParentTL(IF_HANDLE hIface, TL_HANDLE *phSystem); + GC_API DevGetParentIF(DEV_HANDLE hDevice, IF_HANDLE *phIface); + GC_API DSGetParentDev(DS_HANDLE hDataStream, DEV_HANDLE *phDevice); + + /* GenTL v1.5 */ + GC_API DSGetNumBufferParts(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer, uint32_t *piNumParts); + GC_API DSGetBufferPartInfo(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer, uint32_t iPartIndex, BUFFER_PART_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + + /* typedefs for dynamic loading */ +# define GC_API_P(function) typedef GC_ERROR( GC_CALLTYPE *function ) + GC_API_P(PGCGetInfo)(TL_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API_P(PGCGetLastError)(GC_ERROR *piErrorCode, char *sErrText, size_t *piSize); + GC_API_P(PGCInitLib)(void); + GC_API_P(PGCCloseLib)(void); + GC_API_P(PGCReadPort)(PORT_HANDLE hPort, uint64_t iAddress, void *pBuffer, size_t *piSize); + GC_API_P(PGCWritePort)(PORT_HANDLE hPort, uint64_t iAddress, const void *pBuffer, size_t *piSize); + GC_API_P(PGCGetPortURL)(PORT_HANDLE hPort, char *sURL, size_t *piSize); + GC_API_P(PGCGetPortInfo)(PORT_HANDLE hPort, PORT_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + + GC_API_P(PGCRegisterEvent)(EVENTSRC_HANDLE hEventSrc, EVENT_TYPE iEventID, EVENT_HANDLE *phEvent); + GC_API_P(PGCUnregisterEvent)(EVENTSRC_HANDLE hEventSrc, EVENT_TYPE iEventID); + GC_API_P(PEventGetData)(EVENT_HANDLE hEvent, void *pBuffer, size_t *piSize, uint64_t iTimeout); + GC_API_P(PEventGetDataInfo)(EVENT_HANDLE hEvent, const void *pInBuffer, size_t iInSize, EVENT_DATA_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pOutBuffer, size_t *piOutSize); + GC_API_P(PEventGetInfo)(EVENT_HANDLE hEvent, EVENT_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API_P(PEventFlush)(EVENT_HANDLE hEvent); + GC_API_P(PEventKill)(EVENT_HANDLE hEvent); + GC_API_P(PTLOpen)(TL_HANDLE *phTL); + GC_API_P(PTLClose)(TL_HANDLE hTL); + GC_API_P(PTLGetInfo)(TL_HANDLE hTL, TL_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API_P(PTLGetNumInterfaces)(TL_HANDLE hTL, uint32_t *piNumIfaces); + GC_API_P(PTLGetInterfaceID)(TL_HANDLE hTL, uint32_t iIndex, char *sID, size_t *piSize); + GC_API_P(PTLGetInterfaceInfo)(TL_HANDLE hTL, const char *sIfaceID, INTERFACE_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API_P(PTLOpenInterface)(TL_HANDLE hTL, const char *sIfaceID, IF_HANDLE *phIface); + GC_API_P(PTLUpdateInterfaceList)(TL_HANDLE hTL, bool8_t *pbChanged, uint64_t iTimeout); + GC_API_P(PIFClose)(IF_HANDLE hIface); + GC_API_P(PIFGetInfo)(IF_HANDLE hIface, INTERFACE_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API_P(PIFGetNumDevices)(IF_HANDLE hIface, uint32_t *piNumDevices); + GC_API_P(PIFGetDeviceID)(IF_HANDLE hIface, uint32_t iIndex, char *sIDeviceID, size_t *piSize); + GC_API_P(PIFUpdateDeviceList)(IF_HANDLE hIface, bool8_t *pbChanged, uint64_t iTimeout); + GC_API_P(PIFGetDeviceInfo)(IF_HANDLE hIface, const char *sDeviceID, DEVICE_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API_P(PIFOpenDevice)(IF_HANDLE hIface, const char *sDeviceID, DEVICE_ACCESS_FLAGS iOpenFlags, DEV_HANDLE *phDevice); + + GC_API_P(PDevGetPort)(DEV_HANDLE hDevice, PORT_HANDLE *phRemoteDevice); + GC_API_P(PDevGetNumDataStreams)(DEV_HANDLE hDevice, uint32_t *piNumDataStreams); + GC_API_P(PDevGetDataStreamID)(DEV_HANDLE hDevice, uint32_t iIndex, char *sDataStreamID, size_t *piSize); + GC_API_P(PDevOpenDataStream)(DEV_HANDLE hDevice, const char *sDataStreamID, DS_HANDLE *phDataStream); + GC_API_P(PDevGetInfo)(DEV_HANDLE hDevice, DEVICE_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API_P(PDevClose)(DEV_HANDLE hDevice); + + GC_API_P(PDSAnnounceBuffer)(DS_HANDLE hDataStream, void *pBuffer, size_t iSize, void *pPrivate, BUFFER_HANDLE *phBuffer); + GC_API_P(PDSAllocAndAnnounceBuffer)(DS_HANDLE hDataStream, size_t iSize, void *pPrivate, BUFFER_HANDLE *phBuffer); + GC_API_P(PDSFlushQueue)(DS_HANDLE hDataStream, ACQ_QUEUE_TYPE iOperation); + GC_API_P(PDSStartAcquisition)(DS_HANDLE hDataStream, ACQ_START_FLAGS iStartFlags, uint64_t iNumToAcquire); + GC_API_P(PDSStopAcquisition)(DS_HANDLE hDataStream, ACQ_STOP_FLAGS iStopFlags); + GC_API_P(PDSGetInfo)(DS_HANDLE hDataStream, STREAM_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API_P(PDSGetBufferID)(DS_HANDLE hDataStream, uint32_t iIndex, BUFFER_HANDLE *phBuffer); + GC_API_P(PDSClose)(DS_HANDLE hDataStream); + GC_API_P(PDSRevokeBuffer)(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer, void **pBuffer, void **pPrivate); + GC_API_P(PDSQueueBuffer)(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer); + GC_API_P(PDSGetBufferInfo)(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer, BUFFER_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + + /* GenTL v1.1 */ + GC_API_P(PGCGetNumPortURLs)(PORT_HANDLE hPort, uint32_t *iNumURLs); + GC_API_P(PGCGetPortURLInfo)(PORT_HANDLE hPort, uint32_t iURLIndex, URL_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + GC_API_P(PGCReadPortStacked)(PORT_HANDLE hPort, PORT_REGISTER_STACK_ENTRY *pEntries, size_t *piNumEntries); + GC_API_P(PGCWritePortStacked)(PORT_HANDLE hPort, PORT_REGISTER_STACK_ENTRY *pEntries, size_t *piNumEntries); + + /* GenTL v1.3 */ + GC_API_P(PDSGetBufferChunkData)(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer, SINGLE_CHUNK_DATA *pChunkData, size_t *piNumChunks); + + /* GenTL v1.4 */ + GC_API_P(PIFGetParentTL)(IF_HANDLE hIface, TL_HANDLE *phSystem); + GC_API_P(PDevGetParentIF)(DEV_HANDLE hDevice, IF_HANDLE *phIface); + GC_API_P(PDSGetParentDev)(DS_HANDLE hDataStream, DEV_HANDLE *phDevice); + + /* GenTL v1.5 */ + GC_API_P(PDSGetNumBufferParts)(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer, uint32_t *piNumParts); + GC_API_P(PDSGetBufferPartInfo)(DS_HANDLE hDataStream, BUFFER_HANDLE hBuffer, uint32_t iPartIndex, BUFFER_PART_INFO_CMD iInfoCmd, INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); +#ifdef __cplusplus + } /* end of namespace GenTL */ +} /* end of extern "C" */ +#endif +#endif /* GC_TLI_CLIENT_H_ */ diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/GenTLCustom.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/GenTLCustom.h new file mode 100644 index 00000000..4735552b --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/GenTLCustom.h @@ -0,0 +1,67 @@ +#pragma once +#include "GenTL.h" + +namespace GenTL +{ + /* This enumeration defines commands to retrieve information with the GenICam::TL::Client::DevGetInfo function on a device handle. */ + enum DEVICE_INFO_CMD_CUSTOM_LIST + { + //DEVICE_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + DEVICE_INFO_ENDIANNESS_MECHANISM = 1001, /* INT64 Endianess Mechanism Device should use. */ + DEVICE_INFO_GEV_IP_ADDRESS = 1002, /* UINT32 IP address of the device. */ + DEVICE_INFO_GEV_SUBNET_MASK = 1003, /* UINT32 Subnet mask of the device. */ + DEVICE_INFO_GEV_MAC_ADDRESS = 1004, /* INT64 MAC address of the device. */ + DEVICE_INFO_GEV_DEFAULT_GATEWAY = 1005, /* UINT32 Default Gateway of the device. */ + DEVICE_INFO_GEV_CONFIG_DHCP = 1006, /* BOOL Is DHCP ip configuration on. */ + DEVICE_INFO_GEV_CONFIG_PERSISTENT_IP = 1007, /* BOOL IS Persistent Ip configuration on. */ + DEVICE_INFO_GEV_CONFIG_LLA = 1008, /* BOOL IS LLA configuration on. */ + DEVICE_INFO_IS_CONNECTED = 1009 /* BOOL Is a device connected and can this GenTL module communicate with it. */ + }; + + /* This enumeration defines special start flags for the acquisition engine. The function used is GenICam::TL::Client::DSStartAcquisition. */ + enum ACQ_START_FLAGS_CUSTOM_LIST + { + ACQ_START_FLAGS_GVSP_LEGACY_ID = 1001 /* Start GVSP 2.0 stream forcing legacy mode if possible. */ + }; + + /* This enumeration defines commands to retrieve information with the GenICam::TL::Client::GetInfo function on a stream handle. */ + enum STREAM_INFO_CMD_CUSTOM_LIST + { + //STREAM_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + STREAM_INFO_BUFFER_HANDLING_MODE = 1001, /* INT64 Buffer Handling Mode. */ + STREAM_INFO_MISSED_IMAGE_COUNT = 1002, /* UINT64 Number of missed images beginning from stream start. */ + STREAM_INFO_MISSED_PACKET_COUNT = 1003, /* UINT64 Number of missed packets beginning from stream start. */ + STREAM_INFO_TOTAL_MISSED_IMAGE_COUNT = 1004, /* UINT64 Number of missed images, not resetting on each stream start. */ + STREAM_INFO_TOTAL_INCOMPLETE_IMAGE_COUNT = 1005, /* UINT64 Number of incomplete images, not resetting on each stream start. */ + STREAM_INFO_PACKET_RESEND_ENABLE = 1006, /* BOOL Is packet resend enabled for this stream. */ + STREAM_INFO_IMAGE_BUFFER_EXPIRY_MS = 1007, /* UINT32 Image buffer will be completed when timeout expires. */ + STREAM_INFO_AUTO_PACKET_SIZE_ENABLE = 1008, /* BOOL Enable/Disable auto packet size negotiation. */ + STREAM_INFO_INTER_PACKET_EXPIRY_MS = 1009, /* UINT32 A packet resend request will be fired when timeout expires. */ + STREAM_INFO_MAX_RESEND_REQUESTS = 1010 /* UINT32 Max number of resend request packets allowed per image */ + }; + + /* This enumeration defines commands to retrieve additional information about the interface handle.*/ + enum INTERFACE_INFO_CMD_CUSTOM_LIST + { + //INTERFACE_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + INTERFACE_INFO_GEV_MAC_ADDRESS = 1001, /* INT64 MAC address of the interface. */ + INTERFACE_INFO_GEV_SUBNET_IP_ADDRESS = 1002, /* INT64 Subnet IP address of the interface. */ + INTERFACE_INFO_GEV_SUBNET_MASK = 1003 /* INT64 Subnet mask of the interface. */ + }; + + /* This enumeration defines commands to retrieve information with the GenICam::TL::Client::GetInfo function on a stream handle. */ + enum PAYLOADTYPE_INFO_CSTOM_IDS + { + //STREAM_INFO_CUSTOM_ID = 1000 /* Starting value for GenTL Producer custom IDs. */ + PAYLOAD_TYPE_IMAGE_EXTENDED_CHUNK = 0x4001 /* Buffer Handling Mode. */ + }; + + enum GC_ERROR_CUSTOM_LIST + { + GC_ERR_MISMATCHED_MSG = GC_ERR_CUSTOM_ID + 1, + GC_ERR_MISMATCHED_CMD = GC_ERR_CUSTOM_ID + 2, + }; + + //custom functions extending basic gentl implementation + GC_API IFForceIp(IF_HANDLE hIface, uint64_t mac, uint32_t ip, uint32_t subnet, uint32_t gateway); +} // namespace GenTL diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/GetInfoUtility.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/GetInfoUtility.h new file mode 100644 index 00000000..d256f88f --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/GetInfoUtility.h @@ -0,0 +1,35 @@ +#include "stdafx.h" + +namespace Arena +{ + template + size_t GetInfoSizeT(MODULE* pModule, CMD cmd) + { + if (!pDevice) + { + THROW_TYPE( + InvalidArgumentException, + "Device is null"); + } + + size_t value = 0; + size_t valueLen = sizeof(value); + GenTL::INFO_DATATYPE type; + GenTL::GC_ERROR err = pModule->GetInfo(cmd, &type, &value, &valueLen); + if (err != GenTL::GC_ERR_SUCCESS) + { + THROW_NO_GENTL( + err, + "Unable to get info"); + } + + if (type != GenTL::INFO_DATATYPE_PTR) + { + THROW_TYPE( + LogicalErrorException, + "Incorrect datatype returned (pointer expected)"); + } + + return value; + } +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/IBuffer.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/IBuffer.h new file mode 100644 index 00000000..93ab2a3d --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/IBuffer.h @@ -0,0 +1,574 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ +#pragma once + +namespace Arena +{ + class IImage; + class IChunkData; + + /** + * @fn int64_t CalculateCRC32(const uint8_t* pData, size_t nBytes) + * + * @param pData + * - Type: const uint8_t + * - A pointer to the data to use to calculate the CRC + * + * @param nBytes + * - Type: size_t + * - The size of the data + * + * @return + * - Type: int64_t + * - The calculated CRC value + * + * CalculateCRC32 calculates a CRC value (cyclical redundancy check) + * on a dataset. This is used to check whether the dataset has been sent in + * its entirety. + * + * A CRC is performed by running a set of calculations on a dataset both + * before and after a transmission. The two calculated values are then + * compared for equality. If the values are the same, then the transmission is + * deemed successful; if different, then something in the transmission went + * wrong. + * + * A device can be set to send a CRC value by enabling its chunk data setting. + * + * \code{.cpp} + * // Enable chunk data and the CRC chunk + * { + * GenApi::INodeMap* pNodeMap = pDevice->GetNodeMap(); + * + * GenApi::CBooleanPtr pChunkModeActive = pNodeMap->GetNode("ChunkModeActive"); + * pChunkModeActive->SetValue(true); + * + * GenApi::CEnumerationPtr pChunkSelector = pNodeMap->GetNode("ChunkSelector"); + * GenApi::CEnumEntryPtr pCRC = pChunkSelector->GetEntryByname("CRC"); + * pChunkSelector->SetIntValue(pCRC->GetValue()); + * + * GenApi::CBooleanPtr pChunkEnable = pNodeMap->GetNode("ChunkEnable"); + * pChunkEnable->SetValue(true); + * } + * \endcode + * + * The CRC value can then be retrieved from a device. + * + * \code{.cpp} + * // Retrieving CRC from chunk data, calculating it on the data, and comparing the two + * { + * Arena::IChunkData* pChunkData = pImage->AsChunkData(); + * GenApi::CIntegerPtr pChunkCRC = pChunkData->GetChunk("ChunkCRC"); + * int32_t crcFromDevice = pChunkCRC->GetValue(); + * + * int32_t crcCalculated = CalculateCRC32(pImage->GetData(), pImage->SizeFilled()); + * if (crcFromDevice == crcCalculated) + * { + * // Image data is complete + * } + * } + * \endcode + * + * @warning + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IChunkData + */ + ARENA_API int64_t CalculateCRC32(const uint8_t* pData, size_t nBytes); + + /** + * @class IBuffer + * + * An interface to a buffer object + * + * Buffers are the most generic form of acquisition engine data retrieved from + * a device. They are acquired and requeued via devices (Arena::IDevice). + * + * \code{.cpp} + * // retrieving a buffer after starting the stream + * // requeuing it before stopping the stream + * { + * pDevice->StartStream(); + * Arena::IBuffer* pBuffer = pDevice->GetBuffer(2000); + * // ... + * pDevice->RequeueBuffer(pBuffer); + * pDevice->StopStream(); + * } + * \endcode + * + * Buffers are the base class of images (Arena::IImage) and chunk data + * (Arena::IChunkData). Information shared between both can be accessed in the + * buffer class: + * - buffer and payload information like payload (Arena::IBuffer::GetData), + * payload and buffer size (Arena::IBuffer::GetSizeFilled, + * Arena::IBuffer::GetSizeOfBuffer), and frame ID + * (Arena::IBuffer::GetFrameID) + * - type information like payload type (Arena::IBuffer::GetPayloadType) and + * whether the payload has image and/or chunk data + * (Arena::IBuffer::HasImageData, Arena::IBuffer::HasChunkData) + * - casting helpers (Arena::IBuffer::AsImage, Arena::IBuffer::AsChunkData) + * - error information (Arena::IBuffer::IsIncomplete, + * - Arena::IBuffer::DataLargerThanBuffer) + * + * Retrieving data as a buffer can be preferable to an image or chunk data, as + * it can allow data to be received and treated generically before further + * processing. + * + * \code{.cpp} + * // processing image and chunk data only if they exist + * { + * Arena::IBuffer* pBuffer = pDevice->GetBuffer(2000); + * if (pBuffer->HasImageData()) + * { + * Arena::IImage* pImage = pBuffer->AsImage(); + * // ... + * } + * if (pBuffer->HasChunkData()) + * { + * Arena::IChunkData* pChunkData = pBuffer->AsChunkData(); + * // ... + * } + * pDevice->RequeueBuffer(pBuffer); + * } + * \endcode + * + * @warning + * - Should be requeued; otherwise, acquisition engine may starve + * - Properties are lazily instantiated from acquisition engine + * + * @see + * - Arena::IDevice + * - Arena::IBuffer + * - Arena::IImage + * - Arena::IChunkData + */ + class ARENA_API IBuffer + { + public: + + /** + * @fn virtual const uint8_t* GetData() + * + * @return + * - Type: const uint8_t* + * - Pointer to the beginning of the payload data + * + * GetData retrieves a pointer to the buffer's payload data. This + * data may include image data, chunk data, or both. + * + * To check the type of data returned, image (Arena::IBuffer::HasImageData) + * and chunk data (Arena::IBuffer::HasChunkData) can be checked for + * specifically, or the payload type (Arena::EBufferPayloadType) can be + * retrieved. + * + * The returned data only includes payload data, not transport layer protocol + * leaders, which is handled internally. The pointer can be used in + * conjunction with size getters (Arena::IBuffer::GetSizeFilled) to read, + * process, and pass the data around. + * + * @warning + * - Causes undefined behavior if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::HasImageData + * - Arena::IBuffer::HasChunkData + * - Arena::IBuffer::GetSizeFilled + * - Arena::EBufferPayloadType + */ + virtual const uint8_t* GetData() = 0; + + /** + * @fn virtual size_t GetSizeFilled() + * + * @return + * - Type: size_t + * - Unit: bytes + * - Size of the received payload + * + * GetSizeFilled retrieves the size of the payload data, excluding + * transport layer protocol leaders. The payload data may include image data, + * chunk data, or both. + * + * The size filled is often same as the size of the buffer + * (Arena::IBuffer::GetSizeOfBuffer), but not because they are one and the + * same. GetSizeFilled returns the number of bytes received whereas + * GetSizeOfBuffer returns the size of the buffer, which can either + * be allocated by the user or calculated by Arena + * (Arena::IDevice::GetNodeMap, 'PayloadSize'). + * + * @warning + * - Causes undefined behavior if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeOfBuffer + * - Arena::IDevice::GetNodeMap + */ + virtual size_t GetSizeFilled() = 0; + + /** + * @fn virtual size_t GetPayloadSize() + * + * @return + * - Type: size_t + * - Unit: bytes + * - Size of the intended payload + * + * GetPayloadSize retrieves the intended size of the payload. This + * is similar to the retrieved payload size (Arena::IBuffer::GetSizeFilled), + * but different in that missed data is included. This returns the same as + * the SFNC feature by the same name ('PayloadSize'). + * + * @warning + * - Causes undefined behavior if buffer requeued + * + * @see + * - Arena::IBuffer::GetSizeFilled + */ + virtual size_t GetPayloadSize() = 0; + + /** + * @fn virtual size_t GetSizeOfBuffer() + * + * @return + * - Type: size_t + * - Unit: bytes + * - Size of the buffer + * + * GetSizeOfBuffer retrieves the size of a buffer. + * + * The size filled is often same as the size of the buffer + * (Arena::IBuffer::GetSizeOfBuffer), but not because they are one and the + * same. GetSizeFilled returns the number of bytes received whereas + * GetSizeOfBuffer returns the size of the buffer, which can either + * be allocated by the user or calculated by Arena + * (Arena::IDevice::GetNodeMap, 'PayloadSize'). + * + * The payload size is calculated at the beginning of the stream and cannot + * be recalculated until the stream has stopped. Because of this, features + * that can affect payload size ('Width', 'Height', 'PixelFormat') become + * unwritable when the stream has started. + * + * @warning + * - Causes undefined behavior if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeOfBuffer + * - Arena::IDevice::GetNodeMap + */ + virtual size_t GetSizeOfBuffer() = 0; + + /** + * @fn virtual uint64_t GetFrameId() + * + * @return + * - Type: uint64_t + * - Frame ID + * + * GetFrameId gets the frame ID, a sequential identifier for + * buffers. + * + * Frame IDs start at '1' and continue until either 65535 (16-bit) or 2^64-1 + * (64-bit), at which point they roll over back to '1'. The frame ID should + * never be '0'. In order to use 64-bit frame IDs, the device must support + * GigE Vision 2.0. Simply enable the extended ID mode feature + * (Arena::IDevice::GetNodeMap, 'GevGVSPExtendedIDMode'). + * + * @warning + * - Causes undefined behavior if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice::GetNodeMap + */ + virtual uint64_t GetFrameId() = 0; + + /** + * @fn virtual size_t GetPayloadType() + * + * @return + * - Type: size_t + * - Represents: enum Arena::EBufferPayloadType + * - Type of payload data + * + * GetPayloadType returns a buffer's payload type + * (Arena::EBufferPayloadType), as defined by the GigE Vision specification. + * + * The payload type indicates how to interpret the data stored in the buffer + * (Arena::IBuffer::GetData). Lucid devices may provide three ways to + * interpret the data: + * - as an image (Arena::EBufferPayloadType::BufferPayloadTypeImage) + * - as an image with chunk data appended to the end + * (Arena::EBufferPayloadType::BufferPayloadTypeImageExtended) + * - as chunk data, which may or may not include image data as a chunk + * - (Arena::EBufferPayloadType::BufferPayloadTypeChunkData) + * - as chunk data, which may or may not include image data as a chunk + * (Arena::EBufferPayloadType::BufferPayloadTypeChunkData) + * + * @warning + * - Causes undefined behavior if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetData + * - Arena::EBufferPayloadType + */ + virtual size_t GetPayloadType() = 0; + + /** + * @fn virtual bool HasImageData() + * + * @return + * - Type: bool + * - True if the payload has image data + * - False if the payload has image data packaged as chunk data + * - Otherwise, false + * + * HasImageData returns whether or not a buffer's payload has data + * that may be interpreted as image data. + * + * HasImageData returns true if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeImage + * - Arena::EBufferPayloadType::BufferPayloadTypeImageExtendedChunk + * + * It returns false if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeChunkData + * + * @warning + * - Causes undefined behavior if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * - Returns false if image data is packaged as chunk data + * + * @see + * - Arena::EBufferPayloadType + */ + virtual bool HasImageData() = 0; + + /** + * @fn virtual bool HasChunkData() + * + * @return + * - Type: bool + * - True if the payload has chunk data + * - Otherwise, false + * + * HasChunkData returns whether or not a buffer's payload that may + * be interpreted as chunk data. + * + * HasChunkData returns true if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeChunkData + * - Arena::EBufferPayloadType::BufferPayloadTypeImageExtendedChunk + * + * It returns false if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeImage + * + * @warning + * - Causes undefined behavior if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::EBufferPayloadType + */ + virtual bool HasChunkData() = 0; + + /** + * @fn virtual IImage* AsImage() + * + * @return + * - Type: Arena::IImage* + * - Pointer to the original object cast to an image + * - Null on failure + * + * AsImage casts the buffer to an image (Arena::IImage). This is + * only possible if the payload contains image data. + * + * @warning + * - Causes undefined behavior if buffer requeued + * + * @see + * - Arena::IImage + */ + virtual IImage* AsImage() = 0; + + /** + * @fn virtual IChunkData* AsChunkData() + * + * @return + * - Type: Arena::IChunkData* + * - Pointer to the original object cast to chunk data + * - Null on failure + * + * AsChunkData casts the buffer to a chunk data (Arena::IChunkData). + * This is only possible if the payload contains chunk data. + * + * @warning + * - Causes undefined behavior if buffer requeued + * + * @see + * - Arena::IChunkData + */ + virtual IChunkData* AsChunkData() = 0; + + /** + * @fn virtual bool IsIncomplete() + * + * @return + * - Type: bool + * - True if the data is incomplete + * - Otherwise, false + * + * IsIncomplete returns whether or not the payload is complete. + * + * Error handling may be required in the case that the data is incomplete. An + * incomplete image signifies that the data size + * (Arena::IBuffer::GetSizeFilled) does not match the expected data size + * (Arena::IDevice::GetTLStreamNodeMap, 'PayloadSize'). This is either due to + * missed packets or a small buffer. + * + * The number of missed packets may be discovered through the stream node map + * (Arena::IDevice::GetTLStreamNodeMap). The missed packet count feature + * (Arena::IDevice::GetTLStreamNodeMap, 'StreamMissedPacketCount') is a + * cumulative count of all missed packets, and does not necessarily reflect + * the number of missed packets for any given buffer. + * + * A buffer may be missing data if the buffer to hold the data is too small. + * This happens when the size of the buffer (Arena::IBuffer::GetSizeOfBuffer) + * does not match the expected data size (Arena::IDevice::GetTLStreamNodeMap, + * 'PayloadSize'). This function will also return true when checking whether + * the data is larger than the buffer (Arena::IBuffer::DataLargerThanBuffer). + * + * @warning + * - Causes undefined behavior if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeFilled + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::IBuffer::GetSizeOfBuffer + * - Arena::IBuffer::DataLargerThanBuffer + */ + virtual bool IsIncomplete() = 0; + + /** + * @fn virtual bool DataLargerThanBuffer() + * + * @return + * - Type: bool + * - True if the payload is larger than the buffer + * - Otherwise, false + * + * DataLargerThanBuffer returns whether or not a buffer's payload + * data is too larger for the buffer. + * + * A buffer may be missing data if the buffer to hold the data is too small. + * This happens when the size of the buffer (Arena::IBuffer::GetSizeOfBuffer) + * does not match the expected data size (Arena::IDevice::GetTLStreamNodeMap, + * 'PayloadSize'). This function will also return true when checking whether + * the data is larger than the buffer. + * + * @warning + * - Causes undefined behavior if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeOfBuffer + * - Arena::IDevice::GetTLStreamNodeMap + */ + virtual bool DataLargerThanBuffer() = 0; + + /** + * @fn virtual bool VerifyCRC() + * + * @return + * - Type: bool + * - True if the calculated CRC value equals the one sent from the device + * - Otherwise, false + * + * VerifyCRC calculates the CRC of a buffer's data and verifies it + * against the CRC value sent from the device. This helps verify that no data + * has been changed or missed during a transmission. This function calls a + * global helper function to calculate the CRC (Arena::CalculateCRC32). + * + * A CRC is performed by running a set of calculations on a dataset both + * before and after a transmission. The two calculated values are then + * compared for equality. If the values are the same, then the transmission + * is deemed successful; if different, then something in the transmission + * went wrong. + * + * A device can be set to send a CRC value by enabling its chunk data + * setting. + * + * \code{.cpp} + * // Enable chunk data and the CRC chunk + * { + * GenApi::INodeMap* pNodeMap = pDevice->GetNodeMap(); + * + * GenApi::CBooleanPtr pChunkModeActive = pNodeMap->GetNode("ChunkModeActive"); + * pChunkModeActive->SetValue(true); + * + * GenApi::CEnumerationPtr pChunkSelector = pNodeMap->GetNode("ChunkSelector"); + * GenApi::CEnumEntryPtr pCRC = pChunkSelector->GetEntryByname("CRC"); + * pChunkSelector->SetIntValue(pCRC->GetValue()); + * + * GenApi::CBooleanPtr pChunkEnable = pNodeMap->GetNode("ChunkEnable"); + * pChunkEnable->SetValue(true); + * } + * \endcode + * + * The data can then be checked by verifying the CRC. + * + * \code{.cpp} + * // Verifying a buffer's data + * { + * Arena::IBuffer* pBuffer = pDevice->GetBuffer(timeout); + * if (!pBuffer->VerifyCRC()) + * { + * // data not complete + * } + * } + * \endcode + * + * @warning + * - May throw GenICam::GenericException or other derived exception + * - Throws if chunk data disabled or not present, or CRC chunk disabled + * + * @see + * - Arena::CalculateCRC + * - Arena::IBuffer + */ + virtual bool VerifyCRC() = 0; + + /** + * @fn virtual ~IBuffer() + * + * A destructor + */ + virtual ~IBuffer(){}; + + protected: + + IBuffer(){}; + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/IChunkData.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/IChunkData.h new file mode 100644 index 00000000..8248f07d --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/IChunkData.h @@ -0,0 +1,501 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ + +#pragma once + +#include "IBuffer.h" + +namespace Arena +{ + class IImage; + + /** + * @class IChunkData + * + * An interface to a chunk data object + * + * The chunk data interface helps read and interpret chunk data. It inherits + * from the buffer interface (Arena::IBuffer). If chunk data is present, + * buffers (Arena::IBuffer) and images (Arena::IImage) may be cast to chunk + * data. + * + * \code{.cpp} + * // retrieving a buffer, checking for and casting to chunk data + * { + * Arena::IBuffer* pBuffer = pDevice->GetBuffer(100); + * + * if (pBuffer->HasChunkData()) + * { + * Arena::IChunkData* pChunkData = pBuffer->AsChunkData(); + * // ... + * } + * } + * \endcode + * + * The concept of chunk data is a method of adding extra data (such as CRC, + * width, height, etc.) to an image. A nuance of this concept is whether the + * additional information is appended to the back of the image or the image is + * treated as part of the chunk data. This is important for parsing the data. + * Lucid devices create chunk data by appending it to the payload. + * + * In order to receive chunk data with an image, chunk data must be enabled + * and configured on node map (GenApi::INodeMap, Arena::IDevice::GetNodeMap). + * Chunk data must first be activated ('ChunkModeActive'). Each specific chunk + * must then be selected and enabled ('ChunkSelector', 'ChunkEnable'). + * + * \code{.cpp} + * // enabling timestamp chunk data + * { + * CBooleanPtr pChunkModeActive = pNodeMap->GetNode("ChunkModeActive"); + * pChunkModeActive->SetValue(true); + * + * CEnumerationPtr pChunkSelector = pNodeMap->GetNode("ChunkSelector"); + * CEnumEntryPtr pPixelFormat = pChunkSelector->GetEntryByName("PixelFormat"); + * pChunkSelector->SetIntValue(pPixelFormat->GetValue()); + * + * CBooleanPtr pChunkEnable = pNodeMap->GetNode("ChunkEnable"); + * pChunkEnable->SetValue(true); + * } + * \endcode + * + * Along with the functionality of its parent class (Arena::IBuffer), chunk + * data objects provide the ability to get chunks + * (Arena::IChunkData::GetChunk). + * + * @warning + * - Should be requeued; same as other buffers + * - Properties are lazily instantiated from the acquisition engine + * + * @see + * - Arena::IBuffer + * - Arena::IImage + * - Arena::IChunkData + */ + class ARENA_API IChunkData : public IBuffer + { + public: + + /** + * @fn virtual GenApi::INode* GetChunk(GenICam::gcstring name) + * + * @param name + * - Type: GenICam::gcstring + * - Name of the chunk + * - Prefixed with 'Chunk' + * + * @return + * - Type: GenApi::INode* + * - Requested node + * - Null on failure + * + * GetChunk gets a specified chunk, returning it as a node in order + * to preserve metadata related to the chunk. + * + * Internally, chunk data objects have an internal node map and a chunk + * adapter. These allow chunk information to be processed and read as nodes + * (GenApi::INode) using GenICam and GenApi. + * + * There is a chance that incomplete images have garbage data in place of + * expected chunk data. If this is the case, it is still possible to attempt + * chunk retrieval. Invalid chunks return as null. + * + * \code{.cpp} + * // printing the timestamp from chunk + * { + * Arena::IChunkData* pChunkData = pImage->AsChunkData(); + * if (!pChunkData->IsIncomplete()) + * { + * GenApi::CIntegerPtr pChunkTimestamp = pChunkData->GetChunk("ChunkTimestamp"); + * std::cout << "Timestamp: " << pChunkTimestamp->GetValue() << std::endl; + * } + * } + * \endcode + * + * Chunk data must meet three criteria to provide relevant data. Chunk mode + * must be activated ('ChunkModeActive'), the chunk must be enabled + * ('ChunkSelector', 'ChunkEnable'), and the node must exist: + * - if chunk mode is inactive, the buffer will not contain chunk data + * - if chunk does not exist, returns null + * - if chunk is not enabled, returned node will be unavailable + * - otherwise, returns node successfully + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - Returns null on corrupted or missing data + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IChunkData + */ + virtual GenApi::INode* GetChunk(GenICam::gcstring name) = 0; + + /** + * @fn virtual size_t GetSizeFilled() + * + * @return + * - Type: size_t + * - Unit: bytes + * - Size of the chunk portion of the payload + * + * A getter for the size of the data + * + * GetSizeFilled retrieves the size of the data of a buffer, + * excluding transport layer protocol leaders. It takes no inputs and returns + * the size of the data as output. + * + * The return value of GetSizeFilled should always be the same as + * the return value of GetSizeOfBuffer + * (Arena::IBuffer::GetSizeOfBuffer), but not because they are one and the + * same. GetSizeFilled returns the size of the data whereas + * GetSizeFilled returns the size of the buffer, which is set according + * to the expected payload size ('PayloadSize'). + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeOfBuffer + */ + virtual size_t GetSizeFilled() = 0; + + /** + * @fn virtual size_t GetPayloadSize() + * + * @return + * - Type: size_t + * - Unit: bytes + * - Size of the intended payload + * + * GetPayloadSize retrieves the intended size of the payload. This + * is similar to the retrieved payload size (Arena::IBuffer::GetSizeFilled), + * but different in that missed data is included. This returns the same as + * the SFNC feature by the same name ('PayloadSize'). + * + * @warning + * - Causes undefined behavior if buffer requeued + * + * @see + * - Arena::IBuffer::GetSizeFilled + */ + virtual size_t GetPayloadSize() = 0; + + /** + * @fn virtual size_t GetSizeOfBuffer() + * + * @return + * - Type: size_t + * - Unit: bytes + * - Size of the buffer + * + * GetSizeOfBuffer retrieves the size of the buffer. + * + * The size filled is often same as the size of the buffer + * (Arena::IBuffer::GetSizeOfBuffer), but not because they are one and the + * same. GetSizeFilled returns the number of bytes received whereas + * GetSizeOfBuffer returns the size of the buffer, which can either + * be allocated by the user or calculated by Arena + * (Arena::IDevice::GetNodeMap, 'PayloadSize'). + * + * The payload size is calculated at the beginning of the stream and cannot + * be recalculated until the stream has stopped. Because of this, features + * that can affect payload size ('Width', 'Height', 'PixelFormat') become + * unwritable when the stream has started. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeOfBuffer + * - Arena::IDevice::GetNodeMap + */ + virtual size_t GetSizeOfBuffer() = 0; + + /** + * @fn virtual uint64_t GetFrameId() + * + * @return + * - Type: uint64_t + * - Frame ID + * + * GetFrameId returns the frame ID, a sequential identifier for + * buffers. + * + * Frame IDs start at '1' and continue until either 65535 (16-bit) or 2^64-1 + * (64-bit), at which point they roll over back to '1'. The frame ID should + * never be '0'. In order to use 64-bit frame IDs, the device must support + * GigE Vision 2.0. Simply enable the extended ID mode feature + * ('GevGVSPExtendedIDMode'). + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + */ + virtual uint64_t GetFrameId() = 0; + + /** + * @fn virtual size_t GetPayloadType() + * + * @return + * - Type: size_t + * - Represents: enum Arena::EBufferPayloadType + * - Type of payload data + * + * GetPayloadType returns a buffer's payload type + * (Arena::EBufferPayloadType), as defined in the GigE Vision specification. + * + * The payload type indicates how to interpret the data stored in the buffer + * (Arena::IBuffer::GetData). Lucid devices may provide three ways to + * interpret the data: + * - as an image (Arena::EBufferPayloadType::BufferPayloadTypeImage) + * - as an image with chunk data appended to the end + * (Arena::EBufferPayloadType::BufferPayloadTypeImageExtended) + * - as chunk data, which may or may not include image data as a chunk + * - (Arena::EBufferPayloadType::BufferPayloadTypeChunkData) + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetData + * - Arena::EBufferPayloadType + */ + virtual size_t GetPayloadType() = 0; + + /** + * @fn virtual bool HasImageData() + * + * @return + * - Type: bool + * - True if the payload has image data + * - False if the payload has image data packaged as chunk + * - Otherwise, false + * + * HasImageData returns whether or not a buffer's payload may be + * interpreted as image data. + * + * HasImageData returns true if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeImage + * - Arena::EBufferPayloadType::BufferPayloadTypeImageExtendedChunk + * + * It returns false if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeChunkData + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::EBufferPayloadType + */ + virtual bool HasImageData() = 0; + + /** + * @fn virtual bool HasChunkData() + * + * @return + * - True if the payload has chunk data + * - Otherwise, false + * + * HasChunkData returns whether or not a buffer's payload may be + * interpreted as chunk data. Calling HasChunkData from chunk data + * returns true. + * + * HasChunkData returns true if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeChunkData + * - Arena::EBufferPayloadType::BufferPayloadTypeImageExtendedChunk + * + * It returns false if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeImage + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::EBufferPayloadType + */ + virtual bool HasChunkData() = 0; + + /** + * @fn virtual IImage* AsImage() + * + * @return + * - Type: Arena::IImage* + * - Pointer to the original object cast to an image + * - Null on failure + * + * AsImage casts the buffer to an image (Arena::IImage). This is + * only possible if the payload contains image data not packaged as a chunk. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * + * @see + * - Arena::IImage + */ + virtual IImage* AsImage() = 0; + + /** + * @fn virtual bool IsIncomplete() + * + * @return + * - Type: bool + * - True if the data is incomplete + * - Otherwise, false + * + * IsIncomplete returns whether or not a buffer's payload data is + * complete. + * + * Error handling may be required in the case that the data is incomplete. An + * incomplete image signifies that the data size + * (Arena::IBuffer::GetSizeFilled) does not match the expected data size + * ('PayloadSize'). This is either due to missed packets or a small buffer. + * + * The number of missed packets may be discovered through the stream node map + * (Arena::IDevice::GetTLStreamNodeMap). The missed packet count feature + * ('StreamMissedPacketCount') is a cumulative count of all missed packets, + * and does not necessarily reflect the number of missed packets for any + * given buffer. + * + * A buffer may be missing data if the buffer to hold the data is too small. + * This happens when the size of the buffer (Arena::IBuffer::GetSizeOfBuffer) + * does not match the expected data size ('PayloadSize'). This function will + * also return true when checking whether the data is larger than the buffer + * (Arena::IBuffer::DataLargerThanBuffer). + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeFilled + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::IBuffer::GetSizeOfBuffer + * - Arena::IBuffer::DataLargerThanBuffer + */ + virtual bool IsIncomplete() = 0; + + /** + * @fn virtual bool DataLargerThanBuffer() + * + * @return + * - Type: bool + * - True if the payload is larger than the buffer + * - Otherwise, false + * + * DataLargerThanBuffer returns whether or not a buffer's payload + * data is too larger for the buffer. + * + * A buffer may be missing data if the buffer to hold the data is too small. + * This happens when the size of the buffer (Arena::IBuffer::GetSizeOfBuffer) + * does not match the expected data size ('PayloadSize'). This function will + * also return true when checking whether the data is larger than the buffer. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeOfBuffer + */ + virtual bool DataLargerThanBuffer() = 0; + + /** + * @fn virtual bool VerifyCRC() + * + * @return + * - Type: bool + * - True if the calculated CRC value equals the one sent from the device + * - Otherwise, false + * + * VerifyCRC calculates the CRC of chunk data and verifies it + * against the CRC value sent from the device. This helps verify that no data + * has been changed or missed during transmission. This function calls a + * global helper function to calculate the CRC (Arena::CalculateCRC32). + * + * A CRC is performed by running a set of calculations on a dataset both + * before and after a transmission. The two calculated values are then + * compared for equality. If the values are the same, then the transmission + * is deemed successful; if different, then something in the transmission + * went wrong. + * + * A device can be set to send a CRC value by enabling its chunk data + * setting. + * + * \code{.cpp} + * // Enable chunk data and the CRC chunk + * { + * GenApi::INodeMap* pNodeMap = pDevice->GetNodeMap(); + * + * GenApi::CBooleanPtr pChunkModeActive = pNodeMap->GetNode("ChunkModeActive"); + * pChunkModeActive->SetValue(true); + * + * GenApi::CEnumerationPtr pChunkSelector = pNodeMap->GetNode("ChunkSelector"); + * GenApi::CEnumEntryPtr pCRC = pChunkSelector->GetEntryByname("CRC"); + * pChunkSelector->SetIntValue(pCRC->GetValue()); + * + * GenApi::CBooleanPtr pChunkEnable = pNodeMap->GetNode("ChunkEnable"); + * pChunkEnable->SetValue(true); + * } + * \endcode + * + * The data can then be checked by verifying the CRC. + * + * \code{.cpp} + * // Verifying a buffer's data + * { + * Arena::IImage* pImage = pDevice->GetImage(timeout); + * if (!pImage->VerifyCRC()) + * { + * // data not complete + * } + * } + * \endcode + * + * @warning + * - May throw GenICam::GenericException or other derived exception + * - Throws if chunk data disabled or not present, or CRC chunk disabled + * + * @see + * - Arena::CalculateCRC + * - Arena::IImage + */ + virtual bool VerifyCRC() = 0; + + /** + * @fn virtual ~IChunkData() + * + * A destructor + */ + virtual ~IChunkData(){}; + + protected: + + IChunkData(){}; + virtual const uint8_t* GetData() = 0; + virtual IChunkData* AsChunkData() = 0; + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/IDevice.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/IDevice.h new file mode 100644 index 00000000..9dc5a336 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/IDevice.h @@ -0,0 +1,954 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ + +#pragma once + +#include "IImage.h" +#include "ArenaDefs.h" + +namespace Arena +{ + /** + * @class IDevice + * + * An interface to a device object + * + * Devices constitute the core of the Arena SDK, providing the means to + * interacting with physical devices. They are created and destroyed via the + * system (Arena::ISystem). + * + * \code{.cpp} + * // creating and destroying a device + * { + * std::vector devInfos = pSystem->GetDevices(); + * Arena::IDevice* pDevice = pSystem->CreateDevice(devInfos[0]); + * // ... + * pSystem->DestroyDevice(pDevice); + * } + * \endcode + * + * A device manages its images and chunk data (Arena::IBuffer, Arena::IImage, + * Arena::IChunkData), events, and node maps (GenApi::INodeMap) by: + * - starting and stopping the stream (Arena::IDevice::StartStream, + * Arena::IDevice::StopStream), + * - retrieving and requeuing images and chunk data + * (Arena::IDevice::GetBuffer, Arena::IDevice::GetImage, + * Arena::IDevice::RequeueBuffer), + * - handling events (Arena::IDevice::InitializeEvents, + * Arena::IDevice::DeinitializeEvents, Arena::IDevice::WaitOnEvent), + * - and providing access to its node maps (Arena::IDevice::GetNodemap, + * Arena::IDevice::GetTLDeviceNodeMap, + * Arena::IDevice::GetTLInterfaceNodeMap, + * Arena::IDevice::GetTLStreamNodeMap) + * + * @warning + * - Must be destroyed; otherwise, memory will leak + * + * @see + * - Arena::ISystem + * - Arena::DeviceInfo + * - Arena::IDevice + * - Arena::IBuffer + * - Arena::IImage + * - Arena::IChunkData + */ + class ARENA_API IDevice + { + public: + + /** + * @fn virtual bool IsConnected() + * + * - Type: bool + * - Indicates if device is connected + * + * IsConnected returns true if a device has been opened and + * maintains a valid communication socket. The device is opened when + * (Arena::ISystem::CreateDevice) is called. If the connection to the device + * is lost this will return false. + * + * More specifically, for GigE devices, this flag is set to false when the + * Arena is not able to refresh the heartbeat on the device. If an operation + * times out more than 3 times the device will be flagged as not connected. + * + * @see + * - Arena::ISystem::CreateDevice + */ + virtual bool IsConnected() = 0; + + /** + * @fn virtual void StartStream(size_t numBuffers = 10) + * + * @param numBuffers + * - Type: size_t + * - Default: 10 + * - Number of internal buffers to use in the acquisition engine + * + * @return + * - none + * + * StartStream causes the device to begin streaming image/chunk data + * (Arena::IBuffer, Arena::IImage, Arena::IChunkData). It must be called + * before image or chunk data buffers are retrieved + * (Arena::IDevice::GetBuffer, Arena::IDevice::GetImage). The stream must be + * stopped (Arena::IDevice::StopStream) when no longer needed. + * + * Basically, calling StartStream prepares and starts the underlying + * streaming engine. The streaming engine primarily consists of a number of + * buffers, an input and an output queue, and a worker thread to run off of + * the main thread. All buffers are first placed in the input queue. When a + * buffer reaches its turn, it is filled with data. Once complete, it is + * moved to the output queue. At this point a buffer might be retrieved by + * the user (Arena::IDevice::GetBuffer, Arena::IDevice::GetImage) and then + * returned to the input queue (Arena::IDevice::RequeueBuffer). + * + * More specifically, StartStream : + * - allocates and announces a number of buffers according to the numBuffers + * parameter + * - pushes all buffers to the input queue + * - opens a stream channel socket + * - configures the IP and port on the device + * - fires a dummy packet to help with firewalls + * - requests a test packet to ensure configured packet size is appropriate + * - starts the worker thread and begins listening for packets related to + * the acquisition engine + * - has the device lock out certain features (e.g. 'Width', 'Height') that + * cannot be changed during the stream + * - executes the 'AcquisitionStart' feature in order to have the device + * start sending packets + * + * \code{.cpp} + * // setting width and height to max before starting the stream + * // because these features are locked out while streaming + * { + * GenApi::INodeMap* pNodeMap = pDevice->GetNodeMap(); + * + * GenApi::CIntegerPtr pWidth = pNodeMap->GetNode("Width"); + * pWidth->SetValue(pWidth->GetMax()); + * + * GenApi::CIntegerPtr pHeight = pNodeMap->GetNode("Height"); + * pHeight->SetValue(pHeight->GetMax()); + * + * pDevice->StartStream(); + * } + * \endcode + * + * All stream configurations must be completed before starting the stream. + * This includes, among other things, the buffer handling mode + * ('StreamBufferHandlingMode') found on the stream node map + * (Arena::IDevice::GetTLStreamNodeMap). Setting the buffer handling mode + * configures what the streaming engine does with buffers as they are filled + * and moved between queues. There are three modes to choose from: + * - 'OldestFirst' is the default buffer handling mode. As buffers are + * filled with data, they get pushed to the back of the output queue. When + * a buffer is requested (Arena::IDevice::GetBuffer, + * Arena::IBuffer::GetImage), the buffer at the front of the queue is + * returned. If there are no input buffers available, the next incoming + * image is dropped and the lost frame count ('StreamLostFrameCount') is + * incremented. + * - 'OldestFirstOverwrite' is similar to 'OldestFirst' except for what + * happens when there are no input buffers. Instead of dropping an image, + * the oldest buffer in the output queue gets returned to the input queue + * so that its data can be overwritten. + * - 'NewestOnly' only ever has a single buffer in the output queue. If a + * second buffer gets placed into the output queue, the older buffer gets + * returned to the back of the input queue. If there are no input buffers + * available, the next image is dropped and the lost frame count + * ('StreamLostFrameCount') is incremented. + * + * \code{.cpp} + * // setting the buffer handling mode to 'NewestOnly' before starting the stream + * { + * GenApi::INodeMap* pStreamNodeMap = pDevice->GetTLStreamNodeMap(); + * + * GenApi::CEnumerationPtr pStreamBufferHandlingMode = pStreamNodeMap->GetNode("StreamBufferHandlingMode"); + * GenApi::CEnumEntryPtr pNewestOnly = pStreamBufferHandlingMode->GetNode("NewestOnly"); + * pStreamBufferHandlingMode->SetIntValue(pNewOnly->GetValue()); + * + * pDevice->StartStream() + * } + * \endcode + * + * @warning + * - Stream must already be configured prior to call + * - Updates write access to certain nodes + * - May only be called once per stream without stopping + * - Stream must be stopped + * - May throw GenICam::GenericException or other derived exception + * - Minimum number of buffers is 1 + * + * @see + * - Arena::IBuffer + * - Arena::IImage + * - Arena::IChunkData + * - Arena::IDevice::GetBuffer + * - Arena::IDevice::GetImage + * - Arena::IDevice::StopStream + * - Arena::IDevice::RequeueBuffer + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::IBuffer::GetImage + */ + virtual void StartStream(size_t numBuffers = 10) = 0; + + /** + * @fn virtual void StopStream() + * + * @return + * - none + * + * StopStream stops the device from streaming image/chunk data + * (Arena::IBuffer, Arena::IImage, Arena::IChunkData) and cleans up the + * stream. The stream must be stopped when streaming is no longer needed. + * + * StopStream reverses the set up of the stream: + * - stops the worker thread + * - shuts down the stream channel socket + * - executes the 'AcquisitionStop' feature in order to stop the device from + * sending packets + * - has the device unlock features that had been locked for streaming (e.g. + * 'Width', 'Height') + * - revokes all buffers and cleans up their allocated memory + * + * Buffers used internally are allocated when the stream has started + * (Arena::IDevice::StartStream) and deallocated when it has stopped + * (Arena::IDevice::StopStream). If an image has been retrieved + * (Arena::IDevice::GetImage), it can be copied (Arena::ImageFactory::Copy) + * or saved before stopping the stream. If image data were accessed after + * stopping the stream, the memory would be deallocated and the behavior + * undefined. + * + * \code{.cpp} + * // retrieving an image and copying it before requeuing its buffer and stopping the stream + * { + * Arena::IImage* pImage = pDevice->GetImage(); + * Arena::IImage* pCopiedImage = Arena::ImageFactory::Copy(pImage); + * + * pDevice->RequeueBuffer(pImage); + * pDevice->StopStream(); + * } + * \endcode + * + * @warning + * - Stream must be stopped + * - Updates write access to certain nodes + * - Disallows retrieval of image/chunk data from device + * - Deallocates image/chunk data that has not been copied to memory or disk + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer + * - Arena::IImage + * - Arena::IChunkData + * - Arena::IDevice::StartStream + * - Arena::IDevice::StopStream + * - Arena::IDevice::GetImage + * - Arena::ImageFactory::Copy + */ + virtual void StopStream() = 0; + + /** + * @fn virtual IImage* GetImage(uint64_t timeout) + * + * @param timeout + * - Type: uint64_t + * - Unit: milliseconds + * - Maximum time to wait for an image + * + * @return + * - Type:: Arena::IImage* + * - Pointer to the next buffer in the output queue if it contains image + * data + * - Otherwise, null + * + * GetImage retrieves an image (Arena::IImage) from the device. It + * must be called after the stream has started (Arena::IDevice::StartStream) + * and before the stream has stopped (Arena::IDevice::StopStream). Retrieved + * images must be requeued (Arena::IDevice::RequeueBuffer). + * + * GetImage is essentially the same call as GetBuffer + * (Arena::IDevice::GetBuffer), just with an additional step. Internally, + * GetImage calls GetBuffer , which returns a a buffer. It then + * attempts to cast the buffer to an image before returning it. If the data + * in the buffer is that of an image, the cast succeeds and the image is + * returned successfully. If not, the cast fails and NULL is returned. + * + * When called, GetBuffer checks the output queue for image/chunk + * data, grabbing the first buffer in the queue. If nothing is in the output + * queue, the call will wait until something arrives. If nothing arrives + * before expiration of the timeout, a GenICam::TimeoutException is thrown. + * + * GetBuffer is a blocking call. If it is called with a timeout of + * 20 seconds and nothing arrives in the output queue, then its thread will + * be blocked for the full 20 seconds. However, as the timeout is a maximum, + * as soon as something arrives in the output queue, it will be returned, not + * waiting for the full timeout. A timeout value of 0 ensures the call will + * not block, throwing instead of waiting if nothing is in the output queue. + * + * GetImage does not verify image quality. If an incomplete image is + * returned, the call will not throw and the image will be returned as is. It + * is recommended to verify each image's completion + * (Arena::IImage::IsIncomplete). + * + * \code{.cpp} + * // retrieving an image, verifying its completion, and requeuing its buffer + * { + * Arena::IImage* pImage = pDevice->GetImage(100); + * if (pImage->IsIncomplete()) + * { + * // handle error + * // ... + * } + * // do something + * // ... + * pDevice->RequeueBuffer(pImage); + * } + * \endcode + * + * Best practices recommends that buffers be requeued + * (Arena::IDevice::RequeueBuffer) as soon as they are no longer needed in + * order to prevent starvation of the acquisition engine. If image data is + * needed for a longer period of time (i.e. for processing), it is + * recommended to copy the data (Arena::ImageFactory::Create) and requeue the + * buffer. + * + * @warning + * - Does not guarantee valid image data + * - Returns NULL for non-image formats + * - Images should be requeued to prevent starvation of the acquisition + * engine + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IImage + * - Arena::IDevice::StartStream + * - Arena::IDevice::StopStream + * - Arena::IDevice::RequeueBuffer + * - Arena::IDevice::GetBuffer + * - Arena::IImage::IsIncomplete + * - Arena::ImageFactory::Create + */ + virtual IImage* GetImage(uint64_t timeout) = 0; + + /** + * @fn virtual IBuffer* GetBuffer(uint64_t timeout) + * + * @param timeout + * - Type: uint64_t + * - Unit: milliseconds + * - Maximum time to wait for a buffer + * + * @return + * - Type: Arena::IBuffer* + * - Pointer to the next buffer in the output queue + * + * GetBuffer retrieves a buffer (Arena::IBuffer) from the device. It + * must be called after the stream has started (Arena::IDevice::StartStream) + * and before the stream has stopped (Arena::IDevice::StopStream). Retrieved + * images must be requeued (Arena::IDevice::RequeueBuffer). + * + * The data returned from GetBuffer may represents different payload + * types: an image without chunk, an image with chunk, or just chunk data. + * Note that a buffer of chunk data payload type may contain image data, but + * cannot be cast to an image because the image data is treated as a chunk. + * The payload type can be queried (Arena::IBuffer::GetPayloadType), which + * returns an enum (Arena::EBufferPayloadType): + * - Arena::EBufferPayloadType::BufferPayloadTypeImage + * - Arena::EBufferPayloadType::BufferPayloadTypeImageExtendedChunk + * - Arena::EBufferPayloadType::BufferPayloadTypeChunkData + * + * GetBuffer is essentially the same call as GetImage , just + * more generic. Internally, GetImage calls GetBuffer and + * then casts the returned buffer to an image. GetBuffer does not + * perform the cast, instead returning the raw buffer data. The buffer object + * may be queried for its type (Arena::IBuffer::GetPayloadType, + * Arena::IBuffer::HasImageData, Arena::IBuffer::HasChunkData) and cast to + * the appropriate type. + * + * \code{.cpp} + * // handling buffers with image data, chunk data, and both + * // notice the second if-statement does not contain 'else' + * // this is because buffers may have both image and chunk data + * { + * Arena::IBuffer* pBuffer = pDevice->GetBuffer(100); + * + * if (pBuffer->HasImageData()) + * { + * Arena::IImage* pImage = dynamic_cast(pBuffer); + * // ... + * } + * + * if (pBuffer->HasChunkData()) + * { + * Arena::IChunkData* pChunkData = dynamic_cast(pBuffer); + * // ... + * } + * } + * \endcode + * + * When called, GetBuffer checks the output queue for image/chunk + * data, grabbing the first buffer in the queue. If nothing is in the output + * queue, the call will wait until something arrives. If nothing arrives + * before expiration of the timeout, a GenICam::TimeoutException is thrown. + * + * GetBuffer is a blocking call. If it is called with a timeout of + * 20 seconds and nothing arrives in the output queue, then its thread will + * be blocked for the full 20 seconds. However, as the timeout is a maximum, + * as soon as something arrives in the output queue, it will be returned, not + * waiting for the full timeout. A timeout value of 0 ensures the call will + * not block, throwing instead of waiting if nothing is in the output queue. + * + * Best practices recommends that buffers be requeued + * (Arena::IDevice::RequeueBuffer) as soon as they are no longer needed. If + * image data is needed for a longer period of time (i.e. for processing), it + * is recommended to copy the data (Arena::ImageFactory::Create) and requeue + * the buffer. + * + * @warning + * - Does not guarantee valid data + * - Buffers should be requeued + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer + * - Arena::IImage + * - Arena::IChunkData + * - Arena::IDevice::StartStream + * - Arena::IDevice::StopStream + * - Arena::IDevice::RequeueBuffer + * - Arena::IBuffer::GetPayloadType + * - Arena::IBuffer::HasImageData + * - Arena::IBuffer::HasChunkData + * - Arena::ImageFactory::Create + * - Arena::EBufferPayloadType + */ + virtual IBuffer* GetBuffer(uint64_t timeout) = 0; + + /** + * @fn virtual void RequeueBuffer(IBuffer* pBuffer) + * + * @param pBuffer + * - Type: Arena::IBuffer* + * - Buffer to requeue + * + * @return + * - none + * + * RequeueBuffer relinquishes control of a buffer (Arena::IBuffer, + * Arena::IImage, Arena::IChunkData) back to Arena. It must be called after a + * buffer has been retrieved (Arena::IDevice::GetBuffer, + * Arena::IDevice::GetImage). + * + * When called, RequeueBuffer deallocates any lazily instantiated + * memory and returns the internal buffer to the acquisition engine's input + * queue, where it can be filled with new data. If enough buffers have been + * removed from the acquisition engine (i.e. not requeued), it is possible to + * starve the acquisition engine. If this happens and depending on the buffer + * handling mode (Arena::IDevice::GetTLStreamNodeMap, + * 'StreamBufferHandlingMode'), data may start being dropped or buffers may + * start being recycled. + * + * Best practices recommends that buffers be requeued as soon as they are no + * longer needed. If image data is needed for a longer period of time (i.e. + * for processing), it is recommended to copy the data + * (Arena::ImageFactory::Create) and requeue the buffer. + * + * It is important to only call RequeueBuffer on buffers retrieved + * from a device (Arena::IDevice::GetBuffer, Arena::IDevice::GetImage), and + * not on images created through the image factory (Arena::ImageFactory). + * + * \code{.cpp} + * // one image is retrieved from a buffer and then requeued + * // another is created through the image factory and then destroyed + * { + * Arena::IImage* pRetrievedImage = pDevice->GetImage(); + * // ... + * pDevice->RequeueBuffer(pRetrievedImage); + * + * Arena::IImage* pCreatedImage = Arena::ImageFactory::Create(pData, height, width, bpp, size, pixelFormat); + * // ... + * Arena::ImageFactory::Destroy(pCreatedImage); + * } + * \endcode + * + * @warning + * - Buffers should be requeued + * - Used only on buffers retrieved from a device, not on images created + * through the image factory + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer + * - Arena::IImage + * - Arena::IChunkData + * - Arena::ImageFactory + * - Arena::IDevice::GetBuffer + * - Arena::IDevice::GetImage + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::ImageFactory::Create + * - Arena::ImageFactory::Destroy + */ + virtual void RequeueBuffer(IBuffer* pBuffer) = 0; + + /** + * @fn virtual void InitializeEvents() + * + * @return + * - none + * + * InitializeEvents causes the underlying events engine to start + * listening for events. It must be called before waiting on events + * (Arena::IDevice::WaitOnEvent). The event infrastructure must be turned off + * (Arena::IDevice::DeinitializeEvents) when no longer needed. + * + * The underlying events engine works very similarly to the acquisition + * engine, except that event data is processed instead of image data. It + * consists of 100 buffers, an input and an output queue, and event + * registration information. When an event fires, the events engine takes an + * event buffer from the input queue, stores all relevant data, and places it + * in the output queue. When WaitOnEvent + * (Arena::IDevice::WaitOnEvent) is called, the engine takes the buffer from + * the output queue, processes its data, and returns it to the input queue. + * + * More specifically, InitializeEvents : + * - allocates and registers 100 buffers for the events engine + * - places all buffers into the input queue + * - opens a message channel socket + * - configures the IP and port, and sets the packet size + * - fires a dummy packet to help with firewalls + * - starts the worker thread listening for event packets + * + * Events are transmitted from the device through the GigE Vision message + * channel. Arena processes event data internally, which it attaches to the + * device node map (not the GenTL device node map) using a + * GenApi::EventAdapter. The appropriate nodes are then updated in the node + * map. It can be helpful to incorporate callbacks (GenApi::Callback) to be + * notified when these events occur. + * + * \code{.cpp} + * // initializing events, registering and firing a test event, and deinitializing an event + * // (same code snippet as Arena::IDevice::WaitOnEvent) + * { + * void OnNodeCallback(GenApi::INode* pNode) + * { + * GenApi::CIntegerPtr pTimestamp = node; + * // ... + * } + * + * GenApi::INode* pEventTestTimestamp = pNodeMap->GetNode("EventTestTimestamp"); + * GenApi::CCommandPtr pTestEventGenerate = pNodeMap->GetNode("TestEventGenerate"); + * + * pDevice->InitializeEvents(); + * GenApi::CallbackHandleType hCallback = GenApi::Register(pEventTestTimestamp, OnNodeCallback); + * + * for (int i = 0; i < 10; i++) + * { + * pTestEventGenerate->Execute(); + * pDevice->WaitOnEvent(100); + * } + * + * GenApi::Deregister(hCallback); + * pDevice->DeinitializeEvents(); + * } + * \endcode + * + * @warning + * - Event infrastructure must be deinitialized + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice::WaitOnEvent + * - Arena::IDevice::DeinitializeEvents + */ + virtual void InitializeEvents() = 0; + + /** + * @fn virtual void DeinitializeEvents() + * + * @return + * - none + * + * DeinitializeEvents stops the underlying events engine from + * listening for messages, shutting it down and cleaning it up. It should be + * called only after the events infrastructure has been initialized + * (Arena::IDevice::InitializeEvents) and after all events have been + * processed (Arena::IDevice::WaitOnEvent). + * + * Roughly speaking, DeinitializeEvents takes all necessary steps to + * undoing and cleaning up the event infrastructure's initialization + * (Arena::IDevice::InitializeEvents). This includes: + * - stopping the worker thread set up to listen for events + * - closes the message channel socket + * - unregisters all event buffers and deallocates their memory + * + * @warning + * - Event infrastructure must be deinitialized + * - Stops events processing + * - Deallocates event data that has not yet been processed + * - May throw GenICam::GenericException or other derived exception + * + * Arena::IDevice::DeinitializeEvents will perform these actions: + * - Stop the listening thread + * - shut down the message channel socket + * - unregister all buffers and clean up memory that was allocated for them + * + * @see + * - Arena::IDevice::InitializeEvents + * - Arena::IDevice::WaitOnEvent + * - Arena::IDevice::DeinitializeEvents + */ + virtual void DeinitializeEvents() = 0; + + /** + * @fn virtual void WaitOnEvent(uint64_t timeout) + * + * @param timeout + * - Type: uint64_t + * - Unit: milliseconds + * - Maximum time to wait for an event + * + * @return + * - none + * + * WaitOnEvent waits for an event to occur in order to process its + * data. It must be called after the event infrastructure has been + * initialized (Arena::IDevice::InitializeEvents) and before it is + * deinitialized (Arena::IDevice::DeinitializeEvents). + * + * Event processing has been designed to largely abstract away its + * complexities. When an event occurs, the data is stored in an event buffer + * and placed on the output queue. WaitOnEvent causes the data to be + * processed, updating all relevant nodes appropriately. This is why + * WaitOnEvent does not return any event data; when the data is + * processed, nodes are updated, which can then be queried for information + * through the node map. This is also why callbacks (GenApi::Callback) work + * so well with the events infrastructure; they provide a method of accessing + * nodes of interest as they change. + * + * \code{.cpp} + * // initializing events infrastructure, registering and firing a test event, and deinitializing + * // (same code snippet as Arena::IDevice::InitializeEvents) + * { + * void OnNodeCallback(GenApi::INode* pNode) + * { + * GenApi::CIntegerPtr pTimestamp = node; + * // ... + * } + * + * GenApi::INode* pEventTestTimestamp = pNodeMap->GetNode("EventTestTimestamp"); + * GenApi::CCommandPtr pTestEventGenerate = pNodeMap->GetNode("TestEventGenerate"); + * + * pDevice->InitializeEvents(); + * GenApi::CallbackHandleType hCallback = GenApi::Register(pEventTestTimestamp, OnNodeCallback); + * + * for (int i = 0; i < 10; i++) + * { + * pTestEventGenerate->Execute(); + * pDevice->WaitOnEvent(100); + * } + * + * GenApi::Deregister(hCallback); + * pDevice->DeinitializeEvents(); + * } + * \endcode + * + * When called, WaitOnEvent checks the output queue for event data + * to process, grabbing the first buffer from the queue. If nothing is in the + * output queue, the call will wait until an event arrives. If nothing + * arrives before expiration of the timeout, a GenICam::TimeoutException is + * thrown. + * + * WaitOnEvent is a blocking call. If it is called with a timeout of + * 20 seconds and nothing arrives in the output queue, then its thread will + * be blocked for the full 20 seconds. However, as the timeout is a maximum, + * when an event arrives in the output queue, the event will process, not + * waiting for the full timeout. A timeout value of 0 ensures the call will + * not block, throwing instead of waiting if nothing is in the output queue. + * + * @warning + * - Event data processed internally + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice::InitializeEvents + * - Arena::IDevice::DeinitializeEvents + */ + virtual void WaitOnEvent(uint64_t timeout) = 0; + + /** + * @fn virtual GenApi::INodeMap* GetNodeMap() + * + * @return + * - Type: GenApi::INodeMap* + * - Pointer to the main node map for the device + * + * GetNodeMap retrieves the already initialized main node map + * (GenApi::INodeMap), used to access a device's complete feature set of + * nodes (GenApi::INode). + * + * As a simple getter, GetNodeMap retrieves this node map without + * doing anything to initialize, manage, or maintain it. This node map is + * initialized when the device is created (Arena::ISystem::CreateDevice) and + * deinitialized when the device is destroyed + * (Arena::ISystem::DestroyDevice). Because node maps are cleaned up + * internally, retrieving multiple pointers to the same node map is + * permitted. + * + * The node map is built from XMLs stored on the device itself. The XML is + * downloaded and parsed before constructing and initializing the node map. + * This node map describes and provides access to all device features, and + * may vary from device to device. Lucid products conform to the SFNC 2.3 + * specification. Note that both chunk data and event data are updated on + * this node map. + * + * Arena provides access to five different node maps. This one comes from the + * device and describes all its features. Please check device documentation + * for more information regarding these features. + * - device (Arena::IDevice::GetNodeMap) + * + * The other four node maps describe and provide access to information and + * settings through the software rather than the device. + * - system GenTL (Arena::ISystem::GetNodeMap) + * - stream GenTL (Arena::IDevice::GetTLStreamNodeMap) + * - device GenTL (Arena::IDevice::GetTLDeviceNodeMap) + * - interface GenTL (Arena::IDevice::GetTLInterfaceNodeMap) + * + * The most noticable difference between the two device node maps is that the + * GenTL device node map has only a small set of features compared to the + * main node map. There are a few features that overlap. For example, the + * difference between retrieving the serial number ('DeviceSerialNumber') is + * that using the main node map queries the camera directly whereas the GenTL + * node map queries a set of information cached at device creation. The + * result, however, should be the same. Basically, the GenTL node map queries + * the software for information whereas the main node map queries the device. + * + * @warning + * - Provides access to main node map, not to be confused with the GenTL + * device node map + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::ISystem::CreateDevice + * - Arena::ISystem::DestroyDevice + * - Arena::IDevice::GetNodeMap + * - Arena::ISystem::GetNodeMap + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::IDevice::GetTLDeviceNodeMap + * - Arena::IDevice::GetTLInterfaceNodeMap + */ + virtual GenApi::INodeMap* GetNodeMap() = 0; + + /** + * @fn virtual GenApi::INodeMap* GetTLDeviceNodeMap() + * + * @return + * - Type: GenApi::INodeMap* + * - Pointer to the GenTL node map for the device + * + * GetTLDeviceNodeMap retrieves the already initialized GenTL device + * node map (GenApi::INodeMap), used to access a subset of cached device + * related nodes (GenApi::INode). + * + * As a simple getter, GetTLDeviceNodeMap retrieves this node map + * without doing anything to initialize, manage, or maintain it. This node + * map is initialized when the device is created + * (Arena::ISystem::CreateDevice) and deinitialized when the device is + * destroyed (Arena::ISystem::DestroyDevice). Because node maps are cleaned + * up internally, retrieving multiple pointers to the same node map is + * permitted. + * + * All available nodes can be viewed in the XML, + * SFNC_GenTLDevice_Reference_Version_1_0_0_Schema_1_1.xml, found in + * Arena///xml. Nodes in this node map include nodes + * related to: + * - device discovery information + * - GigE Vision IP configuration information + * - the ability to select streams + * + * Arena provides access to five different node maps. This one comes from the + * device and describes all its features. Please check device documentation + * for more information regarding these features. + * - device (Arena::IDevice::GetNodeMap) + * + * The other four, including this one, node maps describe and provide access + * to information and settings through the software rather than the device. + * - system GenTL (Arena::ISystem::GetNodeMap) + * - stream GenTL (Arena::IDevice::GetTLStreamNodeMap) + * - device GenTL (Arena::IDevice::GetTLDeviceNodeMap) + * - interface GenTL (Arena::IDevice::GetTLInterfaceNodeMap) + * + * The most noticable difference between the two device node maps is that the + * GenTL device node map has only a small set of features compared to the + * main node map. There are a few features that overlap. For example, the + * difference between retrieving the serial number ('DeviceSerialNumber') is + * that using the main node map queries the camera directly whereas the GenTL + * node map queries a set of information cached at device creation. The + * result, however, should be the same. Basically, the GenTL node map queries + * the software for information whereas the main node map queries the device. + * + * @warning + * - Provides access to the GenTL device node map, not to be confused with + * the main device node map + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::ISystem::CreateDevice + * - Arena::ISystem::DestroyDevice + * - Arena::IDevice::GetNodeMap + * - Arena::ISystem::GetNodeMap + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::IDevice::GetTLDeviceNodeMap + * - Arena::IDevice::GetTLInterfaceNodeMap + */ + virtual GenApi::INodeMap* GetTLDeviceNodeMap() = 0; + + /** + * @fn virtual GenApi::INodeMap* GetTLStreamNodeMap() + * + * @return + * - Type: GenApi::INodeMap* + * - Pointer to the GenTL node map for the stream + * + * GetTLStreamNodeMap retrieves the already initialized GenTL stream + * node map (GenApi::INodeMap), used to access stream related nodes + * (GenApi::INode). + * + * As a simple getter, GetTLStreamNodeMap retrieves this node map + * without doing anything to initialize, manage, or maintain it. This node + * map is initialized when the device is created + * (Arena::ISystem::CreateDevice) and deinitialized when the device is + * destroyed (Arena::ISystem::DestroyDevice). Because node maps are cleaned + * up internally, retrieving multiple pointers to the same node map is + * permitted. + * + * All available nodes can be viewed in the XML, + * SFNC_GenTLDataStream_Reference_Version_1_0_0_Schema_1_1.xml, found in + * Arena///xml. Nodes in this node map include nodes + * related to: + * - stream ID and type + * - buffer handling mode + * - stream information such as the payload size or whether the device is + * currently streaming + * - stream statistics such as lost frames, announced buffers, or missed + * packets + * + * Arena provides access to five different node maps. This one comes from the + * device and describes all its features. Please check device documentation + * for more information regarding these features. + * - device (Arena::IDevice::GetNodeMap) + * + * The other four, including this one, node maps describe and provide access + * to information and settings through the software rather than the device. + * - system GenTL (Arena::ISystem::GetNodeMap) + * - stream GenTL (Arena::IDevice::GetTLStreamNodeMap) + * - device GenTL (Arena::IDevice::GetTLDeviceNodeMap) + * - interface GenTL (Arena::IDevice::GetTLInterfaceNodeMap) + * + * A common use case requiring this node map is to configure the buffer + * handling mode prior to starting the stream. + * + * \code{.cpp} + * // setting the buffer handling mode to 'NewestOnly' before starting the stream + * { + * GenApi::INodeMap* pStreamNodeMap = pDevice->GetTLStreamNodeMap(); + * + * GenApi::CEnumerationPtr pStreamBufferHandlingMode = pStreamNodeMap->GetNode("StreamBufferHandlingMode"); + * GenApi::CEnumEntry pNewestOnly = pStreamBufferHandlingMode->GetEntryByName("NewestOnly"); + * pStreamBufferHandlingMode->SetIntValue(pNewestOnly->GetValue()); + * + * pDevice->StartStream(); + * } + * \endcode + * + * @warning + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::ISystem::CreateDevice + * - Arena::ISystem::DestroyDevice + * - Arena::IDevice::GetNodeMap + * - Arena::ISystem::GetNodeMap + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::IDevice::GetTLDeviceNodeMap + * - Arena::IDevice::GetTLInterfaceNodeMap + */ + virtual GenApi::INodeMap* GetTLStreamNodeMap() = 0; + + /** + * @fn virtual GenApi::INodeMap* GetTLInterfaceNodeMap() + * + * @return + * - Type: GenApi::INodeMap* + * - Pointer to the GenTL node map for the interface + * + * GetTLInterfaceNodeMap retrieves the already initialized GenTL + * interface node map (GenApi::INodeMap), used to access interface related + * nodes (GenApi::INode). + * + * As a simple getter, GetTLInterfaceNodeMap retrieves this node map + * without doing anything to initialize, manage, or maintain it. This node + * map is initialized when the device is created + * (Arena::ISystem::CreateDevice) and deinitialized when the device is + * destroyed (Arena::ISystem::DestroyDevice). Because node maps are cleaned + * up internally, retrieving multiple pointers to the same node map is + * permitted. + * + * All available nodes can be viewed in the XML, + * SFNC_GenTLInterface_Reference_Version_1_0_0_Schema_1_1.xml, found in + * Arena///xml. Nodes in this node map include nodes + * related to: + * - interface discovery information + * - interface IP configuration information + * - ability to update and select devices + * - device discovery and IP configuration information + * + * Arena provides access to five different node maps. This one comes from the + * device and describes all its features. Please check device documentation + * for more information regarding these features. + * - device (Arena::IDevice::GetNodeMap) + * + * The other four, including this one, node maps describe and provide access + * to information and settings through the software rather than the device. + * - system GenTL (Arena::ISystem::GetNodeMap) + * - stream GenTL (Arena::IDevice::GetTLStreamNodeMap) + * - device GenTL (Arena::IDevice::GetTLDeviceNodeMap) + * - interface GenTL (Arena::IDevice::GetTLInterfaceNodeMap) + * + * @warning + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::ISystem::CreateDevice + * - Arena::ISystem::DestroyDevice + * - Arena::IDevice::GetNodeMap + * - Arena::ISystem::GetNodeMap + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::IDevice::GetTLDeviceNodeMap + * - Arena::IDevice::GetTLInterfaceNodeMap + */ + virtual GenApi::INodeMap* GetTLInterfaceNodeMap() = 0; + + virtual void SendActionCommand(uint32_t deviceKey, uint32_t groupKey, uint32_t groupMask, uint64_t actionTime) = 0; + + /** + * @fn virtual ~IDevice() + * + * A destructor + */ + virtual ~IDevice(){}; + + protected: + + IDevice(){}; + }; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/IImage.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/IImage.h new file mode 100644 index 00000000..cd0d0b7d --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/IImage.h @@ -0,0 +1,857 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ + +#pragma once + +#include "IBuffer.h" + +namespace Arena +{ + class IChunkData; + + /** + * @class IImage + * + * An interface to an image object + * + * Images are the most common form of data retrieved from the stream engine. + * They are retrieved and requeued via devices (Arena::IDevice) as well as + * created, copied, and converted via the image factory (Arena::ImageFactory). + * + * \code{.cpp} + * // retrieving an image after starting the stream + * // requeuing it before stopping the stream + * { + * pDevice->StartStream(); + * Arena::IImage* pImage = pDevice->GetImage(100); + * // ... + * pDevice->RequeueBuffer(pImage); + * pDevice->StopStream(); + * } + * \endcode + * + * Along with the functionality of its parent class (Arena::IBuffer), images + * provide access to additional information particular to images. This + * includes: + * - size information (Arena::IImage::GetWidth, Arena::IImage::GetHeight + * - offsets (Arena::IImage::GetOffsetX, Arena::IImage::GetOffsetY) + * - padding (Arena::IImage::GetPaddingX, Arena::IImage::GetPaddingY) + * - pixel information (Arena::IImage::GetPixelFormat, + * Arena::IImage::GetPixelEndianness) + * - timestamps (Arena::IImage::GetTimestamp, Arena::IImage::GetTimestampNs) + * + * It is important to note that images retrieved from the camera must be + * requeued (Arena::IDevice::RequeueBuffer) whereas images created using the + * image factory must be destroyed (Arena::ImageFactory::Destroy). + * + * \code{.cpp} + * // retrieving an image, copying it, requeuing its buffer, and destroying the copy + * { + * Arena::IImage* pRetrievedImage = pDevice->GetImage(timeout); + * Arena::IImage* pCopiedImage = Arena::ImageFactory::Create(pImage->GetData(), pImage->GetHeight(), pImage->GetWidth(), pImage->GetBitsPerPixel(), pImage->GetImageLength(), pImage->GetPixelFormat()); + * pDevice->RequeueBuffer(pRetrievedImage); + * // ... + * Arena::ImageFactory::Destroy(pCopiedImage); + * } + * \endcode + * + * @warning + * - Should be requeued if retrieved from the device + * - Must be destroyed if created by the image factory + * - Properties of images from the stream engine are lazily instantiated + * - Properties of images from the image factory may be unavailable + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + * - Arena::IImage + * - Arena::IBuffer + */ + class ARENA_API IImage : public IBuffer + { + public: + /** + * @fn virtual size_t GetWidth() + * + * @return + * - Type: size_t + * - Unit: pixels + * - Width of the image + * + * GetWidth gets the width of the image in pixels. Images are + * self-describing, so the device does not need to be queried to get this + * information. + * + * Images are either retrieved from a device (Arena::IDevice) or created by + * the image factory (Arena::ImageFactory). If the image was retrieved from a + * device, the width is populated by the acquisition engine payload leader. + * The device itself is not queried as this data is present in the image + * data. If the image was created by the image factory, the width is + * populated by the arguments. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + */ + virtual size_t GetWidth() = 0; + + /** + * @fn virtual size_t GetHeight() + * + * @return + * - Type: size_t + * - Unit: pixels + * - Height of the image + * + * GetHeight gets the height of the image. Images are + * self-describing, so the device does not need to be queried to get this + * information. + * + * Images are either retrieved from a device (Arena::IDevice) or created by + * the image factory (Arena::ImageFactory). If the image was retrieved from a + * device, the height is populated by the acquisition engine payload leader. + * The device itself is not queried as this data is present in the image + * data. If the image was created by the image factory, the height is + * populated by the arguments. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + */ + virtual size_t GetHeight() = 0; + + /** + * @fn virtual size_t GetOffsetX() + * + * @return + * - Type: size_t + * - Unit: pixels + * - Offset X of the image + * + * GetOffsetX gets the offset of the image along the X-axis. Images + * are self-describing, so the device does not need to be queried to get this + * information. + * + * Images are either retrieved from a device (Arena::IDevice) or created by + * the image factory (Arena::ImageFactory). If the image was retrieved from a + * device, the offset X is populated by the acquisition engine payload + * leader. The device itself is not queried as this data is present in the + * image data. If the image was created by the image factory, the offset X is + * populated by the arguments. + * + * The image factory can create an image from another image (Arena::IImage) + * or from a minimal set of parameters (data, width, height, pixel format). + * If the image is created from parameters, the offset X will be set to 0, no matter + * its original value. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + * - Arena::IImage + */ + virtual size_t GetOffsetX() = 0; + + /** + * @fn virtual size_t GetOffsetY() + * + * @return + * - Type: size_t + * - Unit: pixels + * - Offset Y of the image + * + * GetOffsetY gets the offset of the image along the Y-axis. Images + * are self-describing, so the device does not need to be queried to get this + * information. + * + * Images are either retrieved from a device (Arena::IDevice) or created by + * the image factory (Arena::ImageFactory). If the image was retrieved from a + * device, the offset Y is populated by the acquisition engine payload + * leader. The device itself is not queried as this data is present in the + * image data. If the image was created by the image factory, the offset Y is + * populated by the arguments. + * + * The image factory can create an image from another image (Arena::IImage) + * or from a minimal set of parameters (data, width, height, pixel format). + * If the created from parameters, the offset Y will be set to 0, no matter + * its original value. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + * - Arena::IImage + */ + virtual size_t GetOffsetY() = 0; + + /** + * @fn virtual size_t GetPaddingX() + * + * @return + * - Type: size_t + * - Unit: pixels + * - Padding X of the image + * + * GetPaddingX gets the padding of the image along the X-axis. + * Images are self-describing, so the device does not need to be queried to + * get this information. + * + * Images are either retrieved from a device (Arena::IDevice) or created by + * the image factory (Arena::ImageFactory). If the image was retrieved from a + * device, the padding X is populated by the acquisition engine payload + * leader. The device itself is not queried as this data is present in the + * image data. If the image was created by the image factory, the padding X + * is populated by the arguments. + * + * The image factory can create an image from another image (Arena::IImage) + * or from a minimal set of parameters (data, width, height, pixel format). + * If the image was created from parameters, the padding X will be set to 0, + * no matter its original value. + * + * Padding X specifically refers to the number of bytes padding the end of + * each line. This number will affect the pitch/stride/step of an image. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + * - Arena::IImage + */ + virtual size_t GetPaddingX() = 0; + + /** + * @fn virtual size_t GetPaddingY() + * + * @return + * - Type: size_t + * - Unit: pixels + * - Padding Y of the image + * + * GetPaddingY gets the padding of the image along the Y-axis. + * Images are self-describing, so the device does not need to be queried to + * get this information. + * + * Images are either retrieved from a device (Arena::IDevice) or created by + * the image factory (Arena::ImageFactory). If the image was retrieved from a + * device, the padding Y is populated by the acquisition engine payload + * leader. The device itself is not queried as this data is present in the + * image data. If the image was created by the image factory, the padding Y + * is populated by the arguments. + * + * The image factory can create an image from another image (Arena::IImage) + * or from a minimal set of parameters (data, width, height, pixel format). + * If the image was created from parameters, the padding Y will be set to 0, + * no matter its original value. + * + * Padding Y specifically refers to the number of bytes padding the end of an + * image. This number will not affect the pitch/stride/step of an image. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + * - Arena::IImage + */ + virtual size_t GetPaddingY() = 0; + + /** + * @fn virtual uint64_t GetPixelFormat() + * + * @return + * - Type: uint64_t + * - Represents: enum PfncFormat + * - Pixel format of the image + * + * GetPixelFormat gets the pixel format (PfncFormat) of the image, + * as defined by the PFNC (Pixel Format Naming Convention). Images are + * self-describing, so the device does not need to be queried to get this + * information. + * + * Images are either retrieved from a device (Arena::IDevice) or created by + * the image factory (Arena::ImageFactory). If the image was retrieved from a + * device, the pixel format is populated by the acquisition engine payload + * leader. The device itself is not queried as this data is present in the + * image data. If the image was created by the image factory, the pixel + * format is populated by the arguments. + * + * Pixel format value are determined by the PFNC (Pixel Format Naming + * Convention) specification. The PFNC assigns a name and number to each + * pixel format helping to standardize pixel formats. The number of bits per + * pixel can be found in each integer at bytes 5 and 6 (mask 0x00FF0000). The + * pixel format can be determined by the integer using the GetPixelFormatName + * function provided by the PFNC. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + */ + virtual uint64_t GetPixelFormat() = 0; + + /** + * @fn virtual size_t GetBitsPerPixel() + * + * @return + * - Type: size_t + * - Unit: pixels + * - Bits per pixel of the image + * + * GetBitsPerPixel gets the number of bits per pixel of the image + * from the integer value of the pixel format (PfncFormat). Internally, a + * public helper funciton is called (Arena::GetBitsPerPixel). + * + * Pixel format value are determined by the PFNC (Pixel Format Naming + * Convention) specification. The PFNC assigns a name and number to each + * pixel format helping to standardize pixel formats. The number of bits per + * pixel can be found in each integer at bytes 5 and 6 (mask 0x00FF0000). The + * pixel format can be determined by the integer using the GetPixelFormatName + * function provided by the PFNC. + * + * @see + * - Arena::GetBitsPerPixel + */ + virtual size_t GetBitsPerPixel() = 0; + + /** + * @fn virtual int32_t GetPixelEndianness() + * + * @return + * - Type: int32_t + * - Represents: enum Arena::EPixelEndianness + * - Endianness of the pixels of the image + * + * GetPixelEndianness gets the pixel endianness + * (Arena::EPixelEndianness) of the image. Images are self-describing, so the + * device does not need to be queried to get this information. + * + * Images are either retrieved from a device (Arena::IDevice) or created by + * the image factory (Arena::ImageFactory). If the image was retrieved from a + * device, the pixel endianness is populated by the acquisition engine + * payload leader. The device itself is not queried as this data is present + * in the image data. If the image was created by the image factory, the + * pixel endianness is populated by the arguments. + * + * The image factory can create an image from another image (Arena::IImage) + * or from a minimal set of parameters (data, width, height, pixel format). + * If the image was created from parameters, the pixel endianness will be set + * to 0 (EPixelEndianness::PixelEndiannessUnknown), no matter its original + * value. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + * - Arena::IImage + * - Arena::EPixelEndianness + */ + virtual int32_t GetPixelEndianness() = 0; + + /** + * @fn virtual uint64_t GetTimestamp() + * + * @return + * - Type: uint64_t + * - Unit: nanoseconds + * - Timestamp of the image in nanoseconds + * + * GetTimestamp gets the timestamp of the image in nanoseconds. + * Images are self-describing, so the device does not need to be queried to + * get this information. + * + * Images are either retrieved from a device (Arena::IDevice) or created by + * the image factory (Arena::ImageFactory). If the image was retrieved from a + * device, the timestamp is populated by the acquisition engine payload + * leader. The device itself is not queried as this data is present in the + * image data. If the image was created by the image factory, the timestamp + * is populated by the arguments. + * + * The image factory can create an image from another image (Arena::IImage) + * or from a minimal set of parameters (data, width, height, pixel format). + * If the image was created from parameters, the timestamp will be set to 0, + * no matter its original value. + * + * This is the same as the nanosecond timestamp call + * (Arena::IImage::GetTimestampNs). + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + * - Arena::IImage + * - Arena::IImage::GetTimestampNs + */ + virtual uint64_t GetTimestamp() = 0; + + /** + * @fn virtual uint64_t GetTimestampNs() + * + * @return + * - Type: uint64_t + * - Unit: nanoseconds + * - Timestamp of the image in nanoseconds + * + * GetTimestampNs gets the timestamp of the image in nanoseconds. + * Images are self-describing, so the device does not need to be queried to + * get this information. + * + * Images are either retrieved from a device (Arena::IDevice) or created by + * the image factory (Arena::ImageFactory). If the image was retrieved from a + * device, the timestamp is populated by the acquisition engine payload + * leader. The device itself is not queried as this data is present in the + * image data. If the image was created by the image factory, the timestamp + * is populated by the arguments. + * + * The image factory can create an image from another image (Arena::IImage) + * or from a minimal set of parameters (data, width, height, pixel format). + * If the image was created from parameters, the timestamp will be set to 0, + * no matter its original value. + * + * This is the same as the general timestamp call + * (Arena::IImage::GetTimestamp). + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ImageFactory + * - Arena::IImage + * - Arena::IImage::GetTimestamp + */ + virtual uint64_t GetTimestampNs() = 0; + + /** + * @fn virtual const uint8_t* GetData() + * + * @return + * - Type: const uint8_t* + * - Pointer to the payload data + * + * GetData returns a pointer to the beginning of the image's payload + * data. The payload may include chunk data. + * + * The returned data only includes payload data, not transport layer protocol + * leaders, which is handled internally. The pointer can be used in + * conjunction with size getters (Arena::IBuffer::GetSizeFilled) to read, + * process, and pass the data around. The data may include image data + * (Arena::IBuffer::HasImageData), chunk data (Arena::IBuffer::HasChunkData), + * or both. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeFilled + * - Arena::IBuffer::HasImageData + * - Arena::IBuffer::HasChunkData + */ + virtual const uint8_t* GetData() = 0; + + /** + * @fn virtual size_t GetSizeFilled() + * + * @return + * - Type: size_t + * - Unit: bytes + * - Size of the payload data + * + * GetSizeFilled retrieves the size of the data of a buffer, + * excluding transport layer protocol leaders. + * + * The size filled is often same as the size of the buffer + * (Arena::IBuffer::GetSizeOfBuffer), but not because they are one and the + * same. GetSizeFilled returns the number of bytes received whereas + * GetSizeOfBuffer returns the size of the buffer, which can either + * be allocated by the user or calculated by Arena + * (Arena::IDevice::GetNodeMap, 'PayloadSize'). + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeOfBuffer + * - Arena::IDevice::GetNodeMap + */ + virtual size_t GetSizeFilled() = 0; + + /** + * @fn virtual size_t GetPayloadSize() + * + * @return + * - Type: size_t + * - Unit: bytes + * - Size of the intended payload + * + * GetPayloadSize retrieves the intended size of the payload. This + * is similar to the retrieved payload size (Arena::IBuffer::GetSizeFilled), + * but different in that missed data is included. This returns the same as + * the SFNC feature by the same name ('PayloadSize'). + * + * @warning + * - Causes undefined behavior if buffer requeued + * + * @see + * - Arena::IBuffer::GetSizeFilled + */ + virtual size_t GetPayloadSize() = 0; + + /** + * @fn virtual size_t GetSizeOfBuffer() + * + * @return + * - Type: size_t + * - Unit: bytes + * - Size of the buffer + * + * GetSizeOfBuffer retrieves the size of the buffer. + * + * The size filled is often same as the size of the buffer + * (Arena::IBuffer::GetSizeOfBuffer), but not because they are one and the + * same. GetSizeFilled returns the number of bytes received whereas + * GetSizeOfBuffer returns the size of the buffer, which can either + * be allocated by the user or calculated by Arena + * (Arena::IDevice::GetNodeMap, 'PayloadSize'). + * + * The payload size is calculated at the beginning of the stream and cannot + * be recalculated until the stream has stopped. Because of this, features + * that can affect payload size ('Width', 'Height', 'PixelFormat') become + * unwritable when the stream has started. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeOfBuffer + * - Arena::IDevice::GetNodeMap + */ + virtual size_t GetSizeOfBuffer() = 0; + + /** + * @fn virtual uint64_t GetFrameId() + * + * @return + * - Type: uint64_t + * - Frame ID of the image + * + * GetFrameId gets the frame ID, a sequential identifier for + * buffers. + * + * Frame IDs start at '1' and continue until either 65535 (16-bit) or 2^64-1 + * (64-bit), at which point they roll over back to '1'. The frame ID should + * never be '0'. In order to use 64-bit frame IDs, the device must support + * GigE Vision 2.0. Simply enable the extended ID mode feature + * ('GevGVSPExtendedIDMode'). + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + */ + virtual uint64_t GetFrameId() = 0; + + /** + * @fn virtual size_t GetPayloadType() + * + * @return + * - Type: size_t + * - Represents: enum Arena::EBufferPayloadType + * - Type of payload data + * + * GetPayloadType returns a buffer's payload type + * (Arena::EBufferPayloadType), as defined in the GigE Vision specification. + * + * The payload type indicates how to interpret the data stored in the buffer + * (Arena::IBuffer::GetData). Lucid devices may provide three ways to + * interpret the data: + * - as an image (Arena::EBufferPayloadType::BufferPayloadTypeImage) + * - as an image with chunk data appended to the end + * (Arena::EBufferPayloadType::BufferPayloadTypeImageExtended) + * - as chunk data, which may or may not include image data as a chunk + * - (Arena::EBufferPayloadType::BufferPayloadTypeChunkData) + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetData + * - Arena::EBufferPayloadType + */ + virtual size_t GetPayloadType() = 0; + + /** + * @fn virtual bool HasImageData() + * + * @return + * - Type: bool + * - True if the payload has image data + * - False if the payload has image packaged as chunk data + * - Otherwise, false + * + * HasImageData returns whether or not a payload can be interpreted + * as image data. Calling HasImageData from an image returns true. + * + * HasImageData returns true if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeImage + * - Arena::EBufferPayloadType::BufferPayloadTypeImageExtendedChunk + * + * It returns false if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeChunkData + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::EBufferPayloadType + */ + virtual bool HasImageData() = 0; + + /** + * @fn virtual bool HasChunkData() + * + * @return + * - Type: bool + * - True if the payload has chunk data + * - Otherwise, false + * + * HasChunkData returns whether or not a payload can be interpreted + * as chunk data. + * + * HasChunkData returns true if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeChunkData + * - Arena::EBufferPayloadType::BufferPayloadTypeImageExtendedChunk + * + * It returns false if the payload type is: + * - Arena::EBufferPayloadType::BufferPayloadTypeImage + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::EBufferPayloadType + */ + virtual bool HasChunkData() = 0; + + /** + * virtual IChunkData* AsChunkData() + * + * @return + * - Type: Arena::IChunkData + * - Pointer to the original object cast to chunk data + * - Null on failure + * + * AsChunkData casts the image to chunk data (Arena::IChunkData). + * This is only possible if the payload contains chunk data. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * + * @see + * - Arena::IChunkData + * - Arena::IBuffer::HasChunkData + */ + virtual IChunkData* AsChunkData() = 0; + + /** + * @fn virtual bool IsIncomplete() + * + * @return + * - Type: bool + * - True if the data is incomplete + * - Otherwise, false + * + * IsIncomplete returns whether or not a buffer's payload data is + * complete. + * + * Error handling may be required in the case that the data is incomplete. An + * incomplete image signifies that the data size + * (Arena::IBuffer::GetSizeFilled) does not match the expected data size + * ('PayloadSize'). This is either due to missed packets or a small buffer. + * + * The number of missed packets may be discovered through the stream node map + * (Arena::IDevice::GetTLStreamNodeMap). The missed packet count feature + * ('StreamMissedPacketCount') is a cumulative count of all missed packets, + * and does not necessarily reflect the number of missed packets for any + * given buffer. + * + * A buffer may be missing data if the buffer to hold the data is too small. + * This happens when the size of the buffer (Arena::IBuffer::GetSizeOfBuffer) + * does not match the expected data size ('PayloadSize'). This function will + * also return true when checking whether the data is larger than the buffer + * (Arena::IBuffer::DataLargerThanBuffer). + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeFilled + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::IBuffer::GetSizeOfBuffer + * - Arena::IBuffer::DataLargerThanBuffer + */ + virtual bool IsIncomplete() = 0; + + /** + * @fn virtual bool DataLargerThanBuffer() + * + * @return + * - Type: bool + * - True if the payload is larger than the buffer + * - Otherwise, false + * + * DataLargerThanBuffer returns whether or not a buffer's payload + * data is too larger for the buffer. + * + * A buffer may be missing data if the buffer to hold the data is too small. + * This happens when the size of the buffer (Arena::IBuffer::GetSizeOfBuffer) + * does not match the expected data size ('PayloadSize'). This function will + * also return true when checking whether the data is larger than the buffer. + * + * @warning + * - Causes undefined behaviour if buffer requeued + * - Properties lazily instantiated if buffer retrieved from device + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IBuffer::GetSizeOfBuffer + */ + virtual bool DataLargerThanBuffer() = 0; + + /** + * @fn virtual bool VerifyCRC() + * + * @return + * - Type: bool + * - True if the calculated CRC value equals the one sent from the device + * - Otherwise, false + * + * VerifyCRC calculates the CRC of an image's data and verifies it + * against the CRC value sent from the device. This helps verify that no data + * has been changed or missed during transmission. This function calls a + * global helper function to calculate the CRC (Arena::CalculateCRC32). + * + * A CRC is performed by running a set of calculations on a dataset both + * before and after a transmission. The two calculated values are then + * compared for equality. If the values are the same, then the transmission + * is deemed successful; if different, then something in the transmission + * went wrong. + * + * A device can be set to send a CRC value by enabling its chunk data + * setting. + * + * \code{.cpp} + * // Enable chunk data and the CRC chunk + * { + * GenApi::INodeMap* pNodeMap = pDevice->GetNodeMap(); + * + * GenApi::CBooleanPtr pChunkModeActive = pNodeMap->GetNode("ChunkModeActive"); + * pChunkModeActive->SetValue(true); + * + * GenApi::CEnumerationPtr pChunkSelector = pNodeMap->GetNode("ChunkSelector"); + * GenApi::CEnumEntryPtr pCRC = pChunkSelector->GetEntryByname("CRC"); + * pChunkSelector->SetIntValue(pCRC->GetValue()); + * + * GenApi::CBooleanPtr pChunkEnable = pNodeMap->GetNode("ChunkEnable"); + * pChunkEnable->SetValue(true); + * } + * \endcode + * + * The data can then be checked by verifying the CRC. + * + * \code{.cpp} + * // Verifying a buffer's data + * { + * Arena::IImage* pImage = pDevice->GetImage(timeout); + * if (!pImage->VerifyCRC()) + * { + * // data not complete + * } + * } + * \endcode + * + * @warning + * - May throw GenICam::GenericException or other derived exception + * - Throws if chunk data disabled or not present, or CRC chunk disabled + * + * @see + * - Arena::CalculateCRC + * - Arena::IImage + */ + virtual bool VerifyCRC() = 0; + + /** + * @fn virtual ~IImage() + * + * A destructor + */ + virtual ~IImage(){}; + + protected: + virtual IImage* AsImage() = 0; + IImage(){}; + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/ISystem.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/ISystem.h new file mode 100644 index 00000000..ca65b7dd --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/ISystem.h @@ -0,0 +1,461 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ + +#pragma once + +#include "DeviceInfo.h" +#include "InterfaceInfo.h" +#include "IDevice.h" +#include + +namespace Arena +{ + /** + * @class ISystem + * + * An interface to the system object + * + * The system is the entry point to the Arena SDK. It is retrieved and cleaned + * up through global functions (Arena::OpenSystem, Arena::CloseSystem). + * + * \code{.cpp} + * // opening and closing the system + * { + * Arena::ISystem* pSystem = Arena::OpenSystem(); + * // do something + * // ... + * Arena::CloseSystem(pSystem); + * } + * \endcode + * + * It manages devices (Arena::DeviceInfo, Arena::IDevice) and the system node + * map (GenApi::INodeMap) by: + * - maintaing a list of enumerated devices (Arena::ISystem::UpdateDevices, + * Arena::ISystem::GetDevices), + * - creating and destroying devices (Arena::ISystem::CreateDevice, + * Arena::ISystem::DestroyDevice), + * - and providing access to its node map + * (Arena::ISystem::GetTLSystemNodeMap). + * + * @warning + * - May only be opened once; subsequent attempts will throw + * - Must be closed as final step with Arena; otherwise, memory will leak + * + * @see + * - Arena::ISystem + * - Arena::DeviceInfo + * - Arena::IDevice + */ + class ARENA_API ISystem + { + public: + + /** + * @fn virtual std::vector GetInterfaces() + * + * @return + * - Type: std::vector + * - Vector of interface information objects + * - Each interface information object refers to am interface on the system + * + * GetInterfaces retrieves the internally maintained list of + * interface information objects (Arena::InterfaceInfo). + * + * Internally, the system creates a list of information on all enumerated + * interfaces. GetInterface updates and retrieves this list. The + * interface information objects can be used to specify which interface to + * update devices on (Arena::ISystem::UpdateDevices). + * + * The device information objects (Arena::InterfaceInfo) in the returned list + * merely house information in order to differentiate one interface from + * another. A standard vector (std::vector) is returned so that it can be + * searched and iterated over using the C++ standard library. + * + * @warning + * - Returns an empty list if no interfaces discovered + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::InterfaceInfo + * - Arena::ISystem::UpdateDevices + */ + virtual std::vector GetInterfaces() = 0; + + /** + * @fn virtual bool UpdateDevices(uint64_t timeout) + * + * @param timeout + * - Type: uint64_t + * - Unit: milliseconds + * - Time to wait for connected devices to respond + * + * @return + * - Type: bool + * - True on first call that a device is found + * - True if the device list has changed since the last call + * - Otherwise, false + * + * UpdateDevices updates the internal list of devices, (along with + * their relevant interfaces). It must be called before retrieving the list + * of devices (Arena::ISystem::GetDevices) or any time that an updated device + * list might be necessary. + * + * When called, the system broadcasts a discovery packet to all interfaces, + * waiting until the end of the timeout for any responses from enumerated + * devices. The new, updated list of devices is compared to the old list. If + * the contents of the list have changed, 'true' is returned; otherwise + * 'false'. + * + * The GigE Vision spec requires devices respond to a broadcast discovery + * packet within one second unless set otherwise (Arena::IDevice::GetNodeMap, + * 'DiscoveryAckDelay'). Lucid devices are set to respond within 100 ms. + * Therefore, 100 works as an appropriate timeout value in many use cases. + * This response time can be customized through the 'DiscoveryAckDelay' + * feature, if supported. The timeout value should reflect any such changes. + * + * @warning + * - Slightly affects bandwidth usage due to the broadcasting of discovery + * packets + * - Discovers devices on all subnets, even when unable to communicate with + * them due to IP configuration + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::ISystem::GetDevices + * - Arena::IDevice::GetNodeMap + */ + virtual bool UpdateDevices(uint64_t timeout) = 0; + + /** + * @fn virtual bool UpdateDevices(InterfaceInfo ifaceInfo, uint64_t timeout) + * + * @param ifaceInfo + * - Type: InterfaceInfo + * - Specific interface to enumerate devices on + * + * @param timeout + * - Type: uint64_t + * - Unit: milliseconds + * - Time to wait for connected devices to respond + * + * @return + * - Type: bool + * - True on first call that a device is found + * - True if the device list has changed since the last call + * - Otherwise, false + * + * UpdateDevices updates the internal list of devices, (along with + * their relevant interfaces). It must be called before retrieving the list + * of devices (Arena::ISystem::GetDevices) or any time that an updated device + * list might be necessary. + * + * When called, the system broadcasts a discovery packet to all interfaces, + * waiting until the end of the timeout for any responses from enumerated + * devices. The new, updated list of devices is compared to the old list. If + * the contents of the list have changed, 'true' is returned; otherwise + * 'false'. + * + * The GigE Vision spec requires devices respond to a broadcast discovery + * packet within one second unless set otherwise (Arena::IDevice::GetNodeMap, + * 'DiscoveryAckDelay'). Lucid devices are set to respond within 100 ms. + * Therefore, 100 works as an appropriate timeout value in many use cases. + * This response time can be customized through the 'DiscoveryAckDelay' + * feature, if supported. The timeout value should reflect any such changes. + * + * @warning + * - Slightly affects bandwidth usage due to the broadcasting of discovery + * packets + * - Discovers devices on all subnets, even when unable to communicate with + * them due to IP configuration + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::ISystem::GetDevices + * - Arena::IDevice::GetNodeMap + */ + virtual bool UpdateDevices(InterfaceInfo ifaceInfo, uint64_t timeout) = 0; + + /** + * @fn virtual std::vector GetDevices() + * + * @return + * - Type: std::vector + * - Vector of device information objects + * - Each device information object refers to a connected device + * + * GetDevices retrieves the internally maintained list of device + * information objects (Arena::DeviceInfo). It must be called after the list + * has been updated (Arena::ISystem::UpdateDevices) and before a device is + * created (Arena::ISystem::CreateDevice). + * + * Internally, the system stores a list of information on all enumerated + * devices. As a simple getter, GetDevices retrieves this list + * without doing anything to update, maintain, or manage it. + * + * The device information objects (Arena::DeviceInfo) in the returned list + * should not be confused with Arena::IDevice objects. Whereas the latter are + * handles used to interact with a physical device, device information + * objects merely house information in order to differentiate one from + * another. A standard vector (std::vector) is returned so that it can be + * searched and iterated over using conventional means of the C++ standard + * library. + * + * @warning + * - Returns an empty list if list never updated + * - Returns objects containing device information, not device objects + * themselves + * - Returns devices on all subnets, even when unable to communicate with + * them due to IP configuration + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::DeviceInfo + * - Arena::IDevice + * - Arena::ISystem::UpdateDevices + * - Arena::ISystem::CreateDevice + */ + virtual std::vector GetDevices() = 0; + + /** + * @fn virtual IDevice* CreateDevice(DeviceInfo info) + * + * @param info + * - Type: Arena::DeviceInfo + * - Device information object of the device to create + * + * @return + * - Type: Arena::IDevice* + * - Pointer to an initialized, ready-to-use device + * + * CreateDevice creates and initializes a device using a single + * device information object (Arena::DeviceInfo). It must be called after + * devices have been retrieved (Arena::ISystem::GetDevices). The device must + * be destroyed (Arena::ISystem::DestroyDevice) when no longer needed. + * + * When called, CreateDevice prepares the camera for user + * interaction, opening the control channel socket and initializing all node + * maps (GenApi::INodeMap). The returned device is ready to stream images, + * send events, and read or customize features. + * + * A single process may only create a single device once, but a single device + * may be opened on multiple processes. The first process to create the + * device is given read-write access. Additional processes are given + * read-only access. With read-only access, processes can read features and + * receive images and events; they cannot, however, write values, start the + * image stream or initialize events. + * + * @warning + * - Provides read-write access only to initial process to create device; + * following processes given read-only access + * - Devices must be destroyed + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::DeviceInfo + * - Arena::IDevice + * - Arena::ISystem::GetDevices + * - Arena::ISystem::DestroyDevice + */ + virtual IDevice* CreateDevice(DeviceInfo info) = 0; + + /** + * @fn virtual void DestroyDevice(IDevice* pDevice) + * + * @param pDevice + * - Type: Arena::IDevice* + * - Device to destroy + * + * @return + * - none + * + * DestroyDevice destroys and cleans up the internal memory of a + * device (Arena::IDevice). Devices that have been created + * (Arena::ISystem::CreateDevice) must be destroyed. + * + * When called, DestroyDevice deletes all internal memory associated + * with a device: if a stream has been left open, it is closed; all node maps + * and chunk data adapters are deallocated; events are unregistered and the + * message channel closed; finally, the control channel socket is closed, + * allowing the device to be opened in read-write mode again. + * + * Destroying a device does not reset device settings, and will not return a + * camera to a stable state. To reset settings or return to a stable state, + * power-cycle a device (unplug and plug back in) or reset it ('DeviceReset' + * feature). + * + * @warning + * - Devices must be destroyed + * - Does not affect device settings + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::IDevice + * - Arena::ISystem::CreateDevice + */ + virtual void DestroyDevice(IDevice* pDevice) = 0; + + /** + * @fn virtual GenApi::INodeMap* GetTLSystemNodeMap() + * + * @return + * - Type: GenApi::INodeMap* + * - GenTL node map for the system + * + * GetTLSystemNodeMap retrieves the GenTL system node map + * (GenApi::INodeMap), used to access system related nodes (GenApi::INode). + * + * As a simple getter, GetTLSystemNodeMap retrieves this node map + * without doing anything to initialize, manage, or maintain it. This node + * map is initialized when the system is opened (Arena::OpenSystem) and + * deinitialized when the system is closed (Arena::CloseSystem). Because node + * maps are cleaned up internally, retrieving multiple pointers to the same + * node map is permitted. + * + * All available nodes can be viewed in ArenaView or + * the examples (Cpp_Explore_NodeMaps) example. Nodes in this + * node map include nodes related to: + * - Arena SDK information + * - GenTL and GEV versioning information + * - the ability to update and select interfaces + * - interface discovery and IP configuration information + * + * Arena provides access to five different node maps. This one comes from the + * device and describes all its features. Please check device documentation + * for more information regarding these features. + * - device (Arena::IDevice::GetNodeMap) + * + * The other four, including this one, node maps describe and provide access + * to information and settings through the software rather than the device. + * - system GenTL (Arena::ISystem::GetTLSystemNodeMap) + * - stream GenTL (Arena::IDevice::GetTLStreamNodeMap) + * - device GenTL (Arena::IDevice::GetTLDeviceNodeMap) + * - interface GenTL (Arena::IDevice::GetTLInterfaceNodeMap) + * + * @warning + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::OpenSystem + * - Arena::CloseSystem + * - Arena::IDevice::GetNodeMap + * - Arena::ISystem::GetTLSystemNodeMap + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::IDevice::GetTLDeviceNodeMap + * - Arena::IDevice::GetTLInterfaceNodeMap + */ + virtual GenApi::INodeMap* GetTLSystemNodeMap() = 0; + + /** + * @fn virtual GenApi::INodeMap* GetTLInterfaceNodeMap(DeviceInfo devInfo) + * + * @param devInfo + * - Type: DeviceInfo + * - Device to get the interface nodemap for + * + * @return + * - Type: GenApi::INodeMap* + * - GenTL node map for the system + * + * GetTLInterfaceNodeMap retrieves the GenTL Interface node map + * (GenApi::INodeMap), used to access interface related nodes + * (GenApi::INode). + * + * As a simple getter, GetTLInterfaceNodeMap retrieves this node map + * without doing anything to initialize, manage, or maintain it. This node + * map is initialized when the system is opened (Arena::OpenSystem) and + * deinitialized when the system is closed (Arena::CloseSystem). Because node + * maps are cleaned up internally, retrieving multiple pointers to the same + * node map is permitted. This nodemap will be associated to the device + * indicated by the devInfo (Arena::DeviceInfo) parameter. + * + * All available nodes can be viewed in the XML, + * SFNC_GenTLInterface_Reference_Version_1_0_0_Schema_1_1.xml, found in + * Arena///xml. Nodes in this node map include nodes + * related to: + * - Interface information + * - GenTL and GEV versioning information + * - the ability to update and select devices + * - device discovery and IP configuration information + * + * Arena provides access to five different node maps. This one comes from the + * device and describes all its features. Please check device documentation + * for more information regarding these features. + * - device (Arena::IDevice::GetNodeMap) + * + * The other four, including this one, node maps describe and provide access + * to information and settings through the software rather than the device. + * - system GenTL (Arena::ISystem::GetTLSystemNodeMap) + * - stream GenTL (Arena::IDevice::GetTLStreamNodeMap) + * - device GenTL (Arena::IDevice::GetTLDeviceNodeMap) + * - interface GenTL (Arena::IDevice::GetTLInterfaceNodeMap) + * + * @warning + * - May throw GenICam::GenericException or other derived exception + * + * @see + * - Arena::DeviceInfo + * - Arena::OpenSystem + * - Arena::CloseSystem + * - Arena::IDevice::GetNodeMap + * - Arena::ISystem::GetTLSystemNodeMap + * - Arena::IDevice::GetTLStreamNodeMap + * - Arena::IDevice::GetTLDeviceNodeMap + * - Arena::IDevice::GetTLInterfaceNodeMap + */ + virtual GenApi::INodeMap* GetTLInterfaceNodeMap(DeviceInfo devInfo) = 0; + + /** + * @fn virtual void ForceIp(uint64_t macAddress, uint64_t ipAddress, uint64_t subnetMask, uint64_t defaultGateway) + * + * @param macAddress a 48 bit mac address identifying the device to force the + * ip to. + * - ipAddress a 32bit integer representing the IP address that is meant to + * be forced to the device + * - subnetMask a 32bit integer representing the subnetmask that is meant to + * be forced to the device + * - defaultGateway a 32 bit integer representing the gateway that is meant + * to be forced to the device + * + * Forces the device that matches the macAddress to a temporary new + * ipAddress, subnetMask and defaultGateway + * + * Arena::ISystem::ForceIp will send a ForceIP command out on all the + * interfaces This call also updates the internal list of interfaces in case + * that has not been done yet. The ForceIP command will be a network wide + * broadcast 255.255.255.255 and will request an acknowledment to be + * broadcast back to the host. + * + * @warning + * - This function may throw an exception derived from + * GenApi::GenericExcpetion upon failure + * + * @see + * - Arena::ISystem::ForceIp + */ + virtual void ForceIp(uint64_t macAddress, uint64_t ipAddress, uint64_t subnetMask, uint64_t defaultGateway) = 0; + + /** + * @fn virtual ~ISystem() + * + * A destructor + */ + virtual ~ISystem(){}; + + protected: + + // empty ctor + // inaccessible + ISystem(){}; + }; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/Image.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/Image.h new file mode 100644 index 00000000..b6f93174 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/Image.h @@ -0,0 +1,59 @@ +#pragma once +#include "IImage.h" +#include "Buffer.h" + +namespace Arena +{ + class TLDataStream; + class ARENA_TEST_API Image : public IImage, virtual public Buffer + { + public: + Image(TLDataStream* pStream, GenTL::BUFFER_HANDLE hBuffer); + Image(Image* pImage); + Image(); + virtual ~Image(); + + //IBuffer methods + virtual const uint8_t* GetData(); + virtual size_t GetSizeFilled(); + virtual size_t GetPayloadSize(); + virtual size_t GetSizeOfBuffer(); + virtual bool HasChunkData(); + virtual IChunkData* AsChunkData(); + virtual size_t GetPayloadType(); + virtual bool IsIncomplete(); + virtual uint64_t GetFrameId(); + virtual bool HasImageData(); + virtual IImage* AsImage(); + virtual bool DataLargerThanBuffer(); + virtual bool VerifyCRC(); + + //IImage methods + virtual size_t GetWidth(); + virtual size_t GetHeight(); + virtual size_t GetOffsetX(); + virtual size_t GetOffsetY(); + virtual uint64_t GetPixelFormat(); + virtual uint64_t GetTimestamp(); + virtual size_t GetPaddingX(); + virtual size_t GetPaddingY(); + virtual int32_t GetPixelEndianness(); + virtual uint64_t GetTimestampNs(); + virtual size_t GetBitsPerPixel(); + + friend ImageFactory; + + protected: + // TODO These should all be GenApi::CPtr types once we implement a GenTL nodemap + size_t* m_pWidth; + size_t* m_pHeight; + size_t* m_pOffsetX; + size_t* m_pOffsetY; + uint64_t* m_pPixelFormat; + uint64_t* m_pTimestamp; + size_t* m_pPaddingX; + size_t* m_pPaddingY; + int32_t* m_pPixelEndianness; + uint64_t* m_pTimestampNs; + }; +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/ImageExtendedChunk.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/ImageExtendedChunk.h new file mode 100644 index 00000000..583dba5a --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/ImageExtendedChunk.h @@ -0,0 +1,46 @@ +#pragma once +#include "IChunkData.h" +#include "Image.h" +#include "ChunkData.h" + +namespace Arena +{ + class TLDataStream; + class ARENA_TEST_API ImageExtendedChunk : virtual public Image, virtual public ChunkData + { + public: + ImageExtendedChunk(TLDataStream* pStream, GenTL::BUFFER_HANDLE hBuffer, GenApi::INodeMap* pChunkNodeMap); + ImageExtendedChunk(ImageExtendedChunk* pImageExt); + ImageExtendedChunk(); + virtual ~ImageExtendedChunk(); + + //IBuffer methods + virtual const uint8_t* GetData(); + virtual size_t GetSizeFilled(); + virtual size_t GetPayloadSize(); + virtual size_t GetSizeOfBuffer(); + virtual bool HasChunkData(); + virtual size_t GetPayloadType(); + virtual bool IsIncomplete(); + virtual uint64_t GetFrameId(); + virtual bool HasImageData(); + virtual bool DataLargerThanBuffer(); + virtual bool VerifyCRC(); + //IImage methods + virtual size_t GetWidth(); + virtual size_t GetHeight(); + virtual size_t GetOffsetX(); + virtual size_t GetOffsetY(); + virtual uint64_t GetPixelFormat(); + virtual uint64_t GetTimestamp(); + virtual size_t GetPaddingX(); + virtual size_t GetPaddingY(); + virtual int32_t GetPixelEndianness(); + virtual uint64_t GetTimestampNs(); + virtual IChunkData* AsChunkData(); + + //ChunkData methods + virtual GenApi::INode* GetChunk(GenICam::gcstring name); + virtual IImage* AsImage(); + }; +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/ImageFactory.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/ImageFactory.h new file mode 100644 index 00000000..84b62291 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/ImageFactory.h @@ -0,0 +1,262 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ + +#pragma once + +#include "ArenaDefs.h" + +namespace Arena +{ + /** + * @class ImageFactory + * + * ImageFactory is a static class responsible for the creation, + * copying, conversion, and destruction of images (Arena::IImage). + * + * The image factory allocates and deallocates memory for its images. Memory + * is allocated when an image is created (Arena::ImageFactory::Create) or + * converted (Arena::ImageFactory::Convert). To clean up memory, all images + * created by the image factory must be destroyed + * (Arena::ImageFactory::Destroy). + * + * Images from the image factory are treated noticeably different from those + * from a device (Arena::IDevice). Retrieving an image from a device grabs a + * buffer that had its memory preallocated when the device started streaming; + * retrieving and requeuing does not allocate or deallocate memory, but simply + * moves buffers around the acquisition engine. Creating, copying, and + * converting an image with the image factory allocates and deallocates memory + * as needed. This is why images from a device must be requeued + * (Arena::IDevice::RequeueBuffer) while images from the image factory must be + * destroyed (Arena::ImageFactory::Destroy). + * + * @warning + * - Images from the image factory must be destroyed + * - Images from a device must be requeued + * + * @see + * - Arena::IImage + * - Arena::IDevice + */ + class ARENA_API ImageFactory + { + public: + + /** + * @fn static IImage* Create(const uint8_t* pData, size_t dataSize, size_t width, size_t height, uint64_t pixelFormat) + * + * @param pData + * - Type: const uint8_t + * - Pointer to the beginning of the payload data + * + * @param dataSize + * - Type: size_t + * - Size of the data + * + * @param width + * - Type: size_t + * - Unit: pixels + * - Width of the image to create + * + * @param height + * - Type: size_t + * - Unit: pixels + * - Height of the image to create + * + * @param pixelFormat + * - Type: uint64_t + * - Represents: enum PfncFormat + * - Pixel format of the image to create + * + * @return + * - Type: Arena::IImage* + * - Pointer to the image created from the parameters + * + * Create creates an image (Arena::IImage) from a minimal set of + * parameters. Images created with the image factory must be destroyed + * (Arena::ImageFactory::Destroy) when no longer needed. + * + * Create can create images from any raw image data. It has been + * designed to be generic in order to integrate image data from a variety of + * sources. + * + * When creating an image, the image factory allocates memory for the new + * image. As such, images created by the image factory must be destroyed; + * otherwise, memory will leak. + * + * \code{.cpp} + * // creating and destroying an image + * { + * IImage* pCreate = Arena::ImageFactory::Create(pData, width, height, pixelFormat); + * // ... + * Arena::ImageFactory::Destroy(pCreate); + * } + * \endcode + * + * @warning + * - Images from the image factory must be destroyed + * - Images from a device must be requeued + * - Incorrect data size may result in application crash + * + * @see + * - Arena::IImage + * - Arena::ImageFactory::Destroy + * - Arena::ImageFactory::Create + */ + static IImage* Create(const uint8_t* pData, size_t dataSize, size_t width, size_t height, uint64_t pixelFormat); + + /** + * @fn static IImage* Copy(IImage* pBuffer) + * + * @param pBuffer + * - Type: Arena::IImage* + * - Pointer to the image to copy + * + * @return + * - Type: Arena::IImage* + * - Pointer to a deep copy of the image + * + * Creates a deep copy of an image + * + * Copy creates a deep copy of an image (Arena::IImage) from another + * image. Images created with the image factory must be destroyed + * (Arena::ImageFactory::Destroy) when no longer needed. + * + * When copying an image, the image factory allocates memory for the new + * image. As such, images created by copying an image with the image factory + * must be destroyed; otherwise, memory will leak. + * + * \code{.cpp} + * // creating and destroying an image + * { + * IImage* pCopy = Arena::ImageFactory::Copy(pImage); + * // ... + * Arena::ImageFactory::Destroy(pCopy); + * } + * \endcode + * + * @warning + * - Images from the image factory must be destroyed + * - Images from a device should be requeued + * - Instantiates all lazy properties of the original image + * + * @see + * - Arena::IImage + * - Arena::ImageFactory::Destroy + * - Arena::ImageFactory::Copy + */ + static IImage* Copy(IImage* pBuffer); + + /** + * @fn static IImage* Convert(IImage* pImage, uint64_t pixelFormat, EBayerAlgorithm bayerAlgorithm = DirectionalInterpolation) + * + * @param pImage + * - Type: Arena::IImage + * - Pointer to the image to convert + * + * @param pixelFormat + * - Type: uint64_t + * - Represents: enum PfncFormat + * - Pixel format to convert to + * + * @param bayerAlgorithm + * - Type: Arena::EBayerAlgorithm + * - Bayer conversion algorithm to use + * - Only applicable when converting from bayer + * + * @return + * - Type: Arena::IImage* + * - Pointer to the converted image + * + * Convert converts an image (Arena::IImage) to a select pixel + * format. In doing so, it creates a completely new image, similar to a deep + * copy but with a different pixel format. Images created with the image + * factory must be destroyed (Arena::ImageFactory::Destroy) when no longer + * needed; otherwise, memory will leak. + * + * \code{.cpp} + * // creating and destroying an image + * { + * Arena::IImage* pConvert = Arena::ImageFactory::Convert(pImage, BGRa8); + * // ... + * Arena::ImageFactory::Destroy(pConvert); + * } + * \endcode + * + * The list of supported pixel formats can be found in the software node map + * (Arena::ISystem::GetNodeMap, 'SupportedPixelFormats'). The list of + * supported conversion pixel formats is difference from a device's pixel + * formats (Arena::IDevice::GetNodeMap, 'PixelFormat'). In order for + * conversion to succeed, both the source and destination pixel formats must + * be supported. Bayer formats are supported as source formats only. + * + * @warning + * - Images from the image factory must be destroyed + * - Images from a device should be requeued + * - Cannot convert to bayer formats + * - Bayer conversion algorithm only necessary when converting from bayer + * formats + * + * @see + * - Arena::IImage + * - Arena::ImageFactory::Destroy + * - Arena::ImageFactory::Convert + * - Arena::ISystem::GetNodeMap + * - Arena::IDevice::GetNodeMap + * - Arena::EBayerAlgorithm + */ + static IImage* Convert(IImage* pImage, uint64_t pixelFormat, EBayerAlgorithm bayerAlgorithm = DirectionalInterpolation); + + /** + * @fn static void Destroy(IImage* pImage) + * + * @param pImage + * - Type: Arena::IImage* + * - Pointer to the image to destroy + * - Image must be from image factory + * + * @return + * - none + * + * Destroy cleans up an image (Arena::IImage) and deallocates its + * memory. It must be called on any image created by the image factory + * (Arena::ImageFactory::Create, Arena::ImageFactory::Copy, + * Arena::ImageFactory::Convert). + * + * All images from the image factory, whether created + * (Arena::ImageFactory::Create), copied (Arena::ImageFactory::Copy), or + * converted (Arena::ImageFactory::Convert), must be destroyed to deallocate + * their memory; otherwise, memory will leak. It is important that + * Destroy only be called on images from the image factory, and not on + * those retrieved from a device (Arena::IDevice). + * + * @warning + * - Images from the image factory must be destroyed + * - Images from a device should be requeued + * + * @see + * - Arena::IImage + * - Arena::IDevice + * - Arena::ImageFactory::Create + * - Arena::ImageFactory::Copy + * - Arena::ImageFactory::Convert + */ + static void Destroy(IImage* pImage); + + private: + + // static class + // functions inaccessible + ImageFactory(); + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/Interface.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/Interface.h new file mode 100644 index 00000000..27578a6b --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/Interface.h @@ -0,0 +1,58 @@ +#pragma once +#include "TLInterface.h" +#include "Port.h" +#include "IDevice.h" +#include +#include + +namespace Arena +{ + class ARENA_TEST_API Interface + { + public: + Interface(TLInterface* pInterface, GenICam::gcstring id); + virtual ~Interface(); + + virtual TLInterface* GetTLInterface(); + virtual GenApi::INodeMap* GetNodeMap(); + virtual GenICam::gcstring GetId(); + +#if defined(_ARENA_UNIT_TEST_API) + Interface(TLInterface* pInterface, GenApi::INodeMap* pNodeMap) : + m_pInterface(pInterface), + m_port(pInterface), + m_pNodeMap(pNodeMap) + { + } + + void nullifyNodeMap() + { + m_pNodeMap = NULL; + } +#endif + + void AddDevice(IDevice* pDevice) + { + std::unique_lock l(m_mtxDev); + m_pDevices.push_back(pDevice); + } + + void RemoveDevice(IDevice* pDevice) + { + std::unique_lock l(m_mtxDev); + auto it = std::find(m_pDevices.begin(), m_pDevices.end(), pDevice); + if (it != m_pDevices.end()) + { + m_pDevices.erase(it); + } + } + + private: + TLInterface* m_pInterface; + Port m_port; + GenApi::INodeMap* m_pNodeMap; + GenICam::gcstring m_id; + std::vector m_pDevices; + std::mutex m_mtxDev; + }; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/InterfaceInfo.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/InterfaceInfo.h new file mode 100644 index 00000000..5a061685 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/InterfaceInfo.h @@ -0,0 +1,277 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ +#pragma once +#include + +namespace Arena +{ + /** + * @class InterfaceInfo + * + * Interface information objects contain interface information used to + * find, filter, and create devices on a specific interface on the host. + * + * A list of interface information objects is retrieved and maintained in the + * system (Arena::ISystem). An interface information object provides a: + * - IP and MAC addresses + * - subnet mask + * + * Interface information is acquired prior to device creation and used + * primarily to filter and find specific interface. A std::vector of + * Arena::InterfaceInfo objects is retrieved from the system (via + * Arena::ISystem::GetInterfaces). + * + * The list of InterfaceInfos is returned as a std::vector in order to make use + * of STL filter and search tools. The following code snippet demonstrates + * searching for an interface with a specific Mac address: + * + * \code{.cpp} + * // searching for a specific interface + * { + * GenICam::gcstring macToFind = "ab:cd:ef:aa:aa:aa"; + * std::vector intf = pSystem->GetInterfaces(); + * + * auto it = std::find_if(begin(intf), end(intf), [&macToFind](Arena::InterfaceInfo intf_temp) + * { + * return Intf_temp.MacAddressStr() == macToFind; + * }); + * } + * \endcode + * + * @see + * - Arena::ISystem + * - Arena::InterfaceInfo + */ + class ARENA_API InterfaceInfo + { + public: + /** + * @fn virtual uint32_t IpAddress() + * + * @return + * - Type: uint32_t + * - IP address of the interface as an integer + * + * IpAddress gets the IP address of an interface on the host, + * returning it as its raw integer value. + * + * Both IpAddress and IpAddressStr represent the same + * IP address. Where IpAddress returns the value in its raw + * integer format, IpAddressStr returns it as a more + * human-readable string: + * - IpAddress : 0xA9FE0101 + * - IpAddressStr : 169.254.1.1 + * + * An interface may have its IP address, subnet mask, and MAC Address + * checked through the main node map (Arena::ISystem::GetTLInterfaceNodeMap, + * 'GevInterfaceIPAddress', 'GevInterfaceSubnetMask', + * 'GevInterfaceMACAddress') + * + * @warning + * - Represents same information as Arena::InterfaceInfo::IpAddressStr + * + * @see + * - Arena::ISystem::GetInterfaces + * - Arena::ISystem::GetTLInterfaceNodeMap + * - Arena::InterfaceInfo::IpAddressStr + */ + virtual uint32_t IpAddress(); + + /** + * @fn virtual GenICam::gcstring IpAddressStr() + * + * @return + * - Type: GenICam::gcstring + * - IP address of the interface as a string + * + * IpAddressStr gets the IP address of an interface on the host, + * returning it as a string. + * + * Both IpAddress and IpAddressStr represent the same + * IP address. Where IpAddress returns the value in its raw + * integer format, IpAddressStr returns it as a more + * human-readable string: + * - IpAddress : 0xA9FE0101 + * - IpAddressStr : 169.254.1.1 + * + * @warning + * - Represents same information as Arena::InterfaceInfo::IpAddress + * + * + * @see + * - Arena::ISystem::GetInterfaces + * - Arena::ISystem::GetTLInterfaceNodeMap + * - Arena::InterfaceInfo::IpAddress + */ + virtual GenICam::gcstring IpAddressStr(); + + /** + * @fn virtual uint32_t SubnetMask() + * + * @return + * - Type: uint32_t + * - Subnet mask of the interface as an integer + * + * SubnetMask gets the subnet mask of an interface on the host, + * returning it as its raw integer value. + * + * Both SubnetMask and SubnetMaskStr represent the same + * subnet mask. Where SubnetMask returns the value in its raw + * integer format, SubnetMaskStr returns it as a more human-readable + * string: + * - SubnetMask : 0xFFFF0000 + * - SubnetMaskStr : 255.255.0.0 + * + * An interface may have its IP address, subnet mask, and MAC Address + * checked through the main node map (Arena::ISystem::GetTLInterfaceNodeMap, + * 'GevInterfaceIPAddress', 'GevInterfaceSubnetMask', + * 'GevInterfaceMACAddress'). + * + * @warning + * - Represents same information as Arena::InterfaceInfo::SubnetMaskStr + * + * @see + * - Arena::ISystem::GetInterfaces + * - Arena::ISystem::GetTLInterfaceNodeMap + * - Arena::InterfaceInfo::SubnetMaskStr + */ + virtual uint32_t SubnetMask(); + + /** + * @fn virtual GenICam::gcstring SubnetMaskStr() + * + * @return + * - Type: GenICam::gcstring + * - Subnet mask of the device as a string + * + * SubnetMaskStr gets the subnet mask of an interface on the host, + * returning it as a string. + * + * Both SubnetMask and SubnetMaskStr represent the same + * subnet mask. Where SubnetMask returns the value in its raw + * integer format, SubnetMaskStr returns it as a more human-readable + * string: + * - SubnetMask : 0xFFFF0000 + * - SubnetMaskStr : 255.255.0.0 + * + * @warning + * - Represents same information as Arena::InterfaceInfo::SubnetMask + * + * @see + * - Arena::ISystem::GetInterfaces + * - Arena::ISystem::GetTLInterfaceNodeMap + * - Arena::InterfaceInfo::SubnetMask + */ + virtual GenICam::gcstring SubnetMaskStr(); + + /** + * @fn virtual uint64_t MacAddress() + * + * @return + * - Type: uint64_t + * - MAC address of the interface as an integer + * + * MacAddress gets the MAC address of an interface on the host, + * returning it as its raw integer value. + * + * Both MacAddress and MacAddressStr represent the same MAC + * address. Where MacAddress returns the value in its raw integer + * format, MacAddressStr returns it as a more human-readable string: + * - MacAddress : 0x1C0FAF010101 + * - MacAddressStr : 1C:0F:AF:01:01:01 + * + * @warning + * - Represents same information as Arena::InterfaceInfo::MacAddressStr + * + * @see + * - Arena::ISystem::GetInterfaces + * - Arena::ISystem::GetTLInterfaceNodeMap + * - Arena::InterfaceInfo::MacAddressStr + */ + virtual uint64_t MacAddress(); + + /** + * @fn virtual GenICam::gcstring MacAddressStr() + * + * @return + * - Type: GenICam::gcstring + * - MAC address of the interface as a string + * + * MacAddressStr gets the MAC address of an interface on the host, + * returning it as a string. + * + * Both MacAddress and MacAddressStr represent the same MAC + * address. Where MacAddress returns the value in its raw integer + * format, MacAddressStr returns it as a more human-readable string: + * - MacAddress : 0x1C0FAF010101 + * - MacAddressStr : 1C:0F:AF:01:01:01 + * + * @warning + * - Represents same information as Arena::InterfaceInfo::MacAddress + * + * @see + * - Arena::ISystem::GetInterfaces + * - Arena::ISystem::GetTLInterfaceNodeMap + * - Arena::InterfaceInfo::MacAddress + */ + virtual GenICam::gcstring MacAddressStr(); + + /** + * @fn InterfaceInfo() + * + * An empty constructor + * + * @warning + * - Does not sufficiently initialize InterfaceInfo + */ + InterfaceInfo(); + + /** + * @fn InterfaceInfo(const InterfaceInfo& deviceInfo) + * + * A copy constructor + * + * @param deviceInfo + * - Type: const InterfaceInfo& + * - Device information object to copy + */ + InterfaceInfo(const InterfaceInfo& deviceInfo); + + /** + * @fn virtual ~InterfaceInfo() + * + * A destructor + */ + virtual ~InterfaceInfo(); + + /** + * @fn virtual InterfaceInfo& operator=(InterfaceInfo deviceInfo) + * + * A copy assignment operator + * + * @param deviceInfo + * - Type: InterfaceInfo + * - Device information object to copy + * + * @return + * - Type: InterfaceInfo& + * - Copied device information object + */ + virtual InterfaceInfo& operator=(InterfaceInfo deviceInfo); + + protected: + friend class System; + void* m_pData; + }; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/InterfaceInfoType.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/InterfaceInfoType.h new file mode 100644 index 00000000..a5247dbc --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/InterfaceInfoType.h @@ -0,0 +1,20 @@ +#pragma once + +#include "stdafx.h" + +namespace Arena +{ + class Interface; + class ARENA_TEST_API InterfaceInfo_t + { + + public: + InterfaceInfo_t(){}; + ~InterfaceInfo_t(){}; + + uint32_t m_ip; + uint32_t m_mask; + uint64_t m_mac; + uint32_t m_gateway; + }; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/NodeMapUtility.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/NodeMapUtility.h new file mode 100644 index 00000000..f4bb0273 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/NodeMapUtility.h @@ -0,0 +1,8 @@ +#pragma once +#include "TLBase.h" +#include "Port.h" + +namespace Arena +{ + GenApi::INodeMap* InitNodeMap(TLBase* pModule, Port& port); +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/PFNC.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/PFNC.h new file mode 100644 index 00000000..ba55266d --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/PFNC.h @@ -0,0 +1,1081 @@ +#pragma once +// clang-format off +/************************************************************************************************************** +* This file was automatically generated by tools designed and maintained by Leutron Vision, Matrox, Groget, MVTec Software. +* Copyright (C) 2007-2016 Leutron Vision, Matrox, Groget, MVTec Software. +* You can freely use and modify the file without restrictions. +* The file is provided as is, without any warranty. Bug reports and other feedback is appreciated. +************************************************************************************************************** +* +************************************************************************************************************** +* Version and date information: +* PFNC value list release date: 2017-02-17 +* Header generator version: 3.2 +* Header generation date: 2017-02-21 +**************************************************************************************************************/ + +#ifndef PFNC_H +#define PFNC_H + +#define PFNC_Mono1p 0x01010037 /* Monochrome 1-bit packed */ +#define PFNC_Mono2p 0x01020038 /* Monochrome 2-bit packed */ +#define PFNC_Mono4p 0x01040039 /* Monochrome 4-bit packed */ +#define PFNC_Mono8 0x01080001 /* Monochrome 8-bit */ +#define PFNC_Mono8s 0x01080002 /* Monochrome 8-bit signed */ +#define PFNC_Mono10 0x01100003 /* Monochrome 10-bit unpacked */ +#define PFNC_Mono10p 0x010A0046 /* Monochrome 10-bit packed */ +#define PFNC_Mono12 0x01100005 /* Monochrome 12-bit unpacked */ +#define PFNC_Mono12p 0x010C0047 /* Monochrome 12-bit packed */ +#define PFNC_Mono14 0x01100025 /* Monochrome 14-bit unpacked */ +#define PFNC_Mono16 0x01100007 /* Monochrome 16-bit */ +#define PFNC_BayerBG8 0x0108000B /* Bayer Blue-Green 8-bit */ +#define PFNC_BayerBG10 0x0110000F /* Bayer Blue-Green 10-bit unpacked */ +#define PFNC_BayerBG10p 0x010A0052 /* Bayer Blue-Green 10-bit packed */ +#define PFNC_BayerBG12 0x01100013 /* Bayer Blue-Green 12-bit unpacked */ +#define PFNC_BayerBG12p 0x010C0053 /* Bayer Blue-Green 12-bit packed */ +#define PFNC_BayerBG16 0x01100031 /* Bayer Blue-Green 16-bit */ +#define PFNC_BayerGB8 0x0108000A /* Bayer Green-Blue 8-bit */ +#define PFNC_BayerGB10 0x0110000E /* Bayer Green-Blue 10-bit unpacked */ +#define PFNC_BayerGB10p 0x010A0054 /* Bayer Green-Blue 10-bit packed */ +#define PFNC_BayerGB12 0x01100012 /* Bayer Green-Blue 12-bit unpacked */ +#define PFNC_BayerGB12p 0x010C0055 /* Bayer Green-Blue 12-bit packed */ +#define PFNC_BayerGB16 0x01100030 /* Bayer Green-Blue 16-bit */ +#define PFNC_BayerGR8 0x01080008 /* Bayer Green-Red 8-bit */ +#define PFNC_BayerGR10 0x0110000C /* Bayer Green-Red 10-bit unpacked */ +#define PFNC_BayerGR10p 0x010A0056 /* Bayer Green-Red 10-bit packed */ +#define PFNC_BayerGR12 0x01100010 /* Bayer Green-Red 12-bit unpacked */ +#define PFNC_BayerGR12p 0x010C0057 /* Bayer Green-Red 12-bit packed */ +#define PFNC_BayerGR16 0x0110002E /* Bayer Green-Red 16-bit */ +#define PFNC_BayerRG8 0x01080009 /* Bayer Red-Green 8-bit */ +#define PFNC_BayerRG10 0x0110000D /* Bayer Red-Green 10-bit unpacked */ +#define PFNC_BayerRG10p 0x010A0058 /* Bayer Red-Green 10-bit packed */ +#define PFNC_BayerRG12 0x01100011 /* Bayer Red-Green 12-bit unpacked */ +#define PFNC_BayerRG12p 0x010C0059 /* Bayer Red-Green 12-bit packed */ +#define PFNC_BayerRG16 0x0110002F /* Bayer Red-Green 16-bit */ +#define PFNC_RGBa8 0x02200016 /* Red-Green-Blue-alpha 8-bit */ +#define PFNC_RGBa10 0x0240005F /* Red-Green-Blue-alpha 10-bit unpacked */ +#define PFNC_RGBa10p 0x02280060 /* Red-Green-Blue-alpha 10-bit packed */ +#define PFNC_RGBa12 0x02400061 /* Red-Green-Blue-alpha 12-bit unpacked */ +#define PFNC_RGBa12p 0x02300062 /* Red-Green-Blue-alpha 12-bit packed */ +#define PFNC_RGBa14 0x02400063 /* Red-Green-Blue-alpha 14-bit unpacked */ +#define PFNC_RGBa16 0x02400064 /* Red-Green-Blue-alpha 16-bit */ +#define PFNC_RGB8 0x02180014 /* Red-Green-Blue 8-bit */ +#define PFNC_RGB8_Planar 0x02180021 /* Red-Green-Blue 8-bit planar */ +#define PFNC_RGB10 0x02300018 /* Red-Green-Blue 10-bit unpacked */ +#define PFNC_RGB10_Planar 0x02300022 /* Red-Green-Blue 10-bit unpacked planar */ +#define PFNC_RGB10p 0x021E005C /* Red-Green-Blue 10-bit packed */ +#define PFNC_RGB10p32 0x0220001D /* Red-Green-Blue 10-bit packed into 32-bit */ +#define PFNC_RGB12 0x0230001A /* Red-Green-Blue 12-bit unpacked */ +#define PFNC_RGB12_Planar 0x02300023 /* Red-Green-Blue 12-bit unpacked planar */ +#define PFNC_RGB12p 0x0224005D /* Red-Green-Blue 12-bit packed */ +#define PFNC_RGB14 0x0230005E /* Red-Green-Blue 14-bit unpacked */ +#define PFNC_RGB16 0x02300033 /* Red-Green-Blue 16-bit */ +#define PFNC_RGB16_Planar 0x02300024 /* Red-Green-Blue 16-bit planar */ +#define PFNC_RGB565p 0x02100035 /* Red-Green-Blue 5/6/5-bit packed */ +#define PFNC_BGRa8 0x02200017 /* Blue-Green-Red-alpha 8-bit */ +#define PFNC_BGRa10 0x0240004C /* Blue-Green-Red-alpha 10-bit unpacked */ +#define PFNC_BGRa10p 0x0228004D /* Blue-Green-Red-alpha 10-bit packed */ +#define PFNC_BGRa12 0x0240004E /* Blue-Green-Red-alpha 12-bit unpacked */ +#define PFNC_BGRa12p 0x0230004F /* Blue-Green-Red-alpha 12-bit packed */ +#define PFNC_BGRa14 0x02400050 /* Blue-Green-Red-alpha 14-bit unpacked */ +#define PFNC_BGRa16 0x02400051 /* Blue-Green-Red-alpha 16-bit */ +#define PFNC_BGR8 0x02180015 /* Blue-Green-Red 8-bit */ +#define PFNC_BGR10 0x02300019 /* Blue-Green-Red 10-bit unpacked */ +#define PFNC_BGR10p 0x021E0048 /* Blue-Green-Red 10-bit packed */ +#define PFNC_BGR12 0x0230001B /* Blue-Green-Red 12-bit unpacked */ +#define PFNC_BGR12p 0x02240049 /* Blue-Green-Red 12-bit packed */ +#define PFNC_BGR14 0x0230004A /* Blue-Green-Red 14-bit unpacked */ +#define PFNC_BGR16 0x0230004B /* Blue-Green-Red 16-bit */ +#define PFNC_BGR565p 0x02100036 /* Blue-Green-Red 5/6/5-bit packed */ +#define PFNC_R8 0x010800C9 /* Red 8-bit */ +#define PFNC_R10 0x010A00CA /* Red 10-bit */ +#define PFNC_R12 0x010C00CB /* Red 12-bit */ +#define PFNC_R16 0x011000CC /* Red 16-bit */ +#define PFNC_G8 0x010800CD /* Green 8-bit */ +#define PFNC_G10 0x010A00CE /* Green 10-bit */ +#define PFNC_G12 0x010C00CF /* Green 12-bit */ +#define PFNC_G16 0x011000D0 /* Green 16-bit */ +#define PFNC_B8 0x010800D1 /* Blue 8-bit */ +#define PFNC_B10 0x010A00D2 /* Blue 10-bit */ +#define PFNC_B12 0x010C00D3 /* Blue 12-bit */ +#define PFNC_B16 0x011000D4 /* Blue 16-bit */ +#define PFNC_Coord3D_ABC8 0x021800B2 /* 3D coordinate A-B-C 8-bit */ +#define PFNC_Coord3D_ABC8_Planar 0x021800B3 /* 3D coordinate A-B-C 8-bit planar */ +#define PFNC_Coord3D_ABC10p 0x021E00DB /* 3D coordinate A-B-C 10-bit packed */ +#define PFNC_Coord3D_ABC10p_Planar 0x021E00DC /* 3D coordinate A-B-C 10-bit packed planar */ +#define PFNC_Coord3D_ABC12p 0x022400DE /* 3D coordinate A-B-C 12-bit packed */ +#define PFNC_Coord3D_ABC12p_Planar 0x022400DF /* 3D coordinate A-B-C 12-bit packed planar */ +#define PFNC_Coord3D_ABC16 0x023000B9 /* 3D coordinate A-B-C 16-bit */ +#define PFNC_Coord3D_ABC16_Planar 0x023000BA /* 3D coordinate A-B-C 16-bit planar */ +#define PFNC_Coord3D_ABC32f 0x026000C0 /* 3D coordinate A-B-C 32-bit floating point */ +#define PFNC_Coord3D_ABC32f_Planar 0x026000C1 /* 3D coordinate A-B-C 32-bit floating point planar */ +#define PFNC_Coord3D_AC8 0x021000B4 /* 3D coordinate A-C 8-bit */ +#define PFNC_Coord3D_AC8_Planar 0x021000B5 /* 3D coordinate A-C 8-bit planar */ +#define PFNC_Coord3D_AC10p 0x021400F0 /* 3D coordinate A-C 10-bit packed */ +#define PFNC_Coord3D_AC10p_Planar 0x021400F1 /* 3D coordinate A-C 10-bit packed planar */ +#define PFNC_Coord3D_AC12p 0x021800F2 /* 3D coordinate A-C 12-bit packed */ +#define PFNC_Coord3D_AC12p_Planar 0x021800F3 /* 3D coordinate A-C 12-bit packed planar */ +#define PFNC_Coord3D_AC16 0x022000BB /* 3D coordinate A-C 16-bit */ +#define PFNC_Coord3D_AC16_Planar 0x022000BC /* 3D coordinate A-C 16-bit planar */ +#define PFNC_Coord3D_AC32f 0x024000C2 /* 3D coordinate A-C 32-bit floating point */ +#define PFNC_Coord3D_AC32f_Planar 0x024000C3 /* 3D coordinate A-C 32-bit floating point planar */ +#define PFNC_Coord3D_A8 0x010800AF /* 3D coordinate A 8-bit */ +#define PFNC_Coord3D_A10p 0x010A00D5 /* 3D coordinate A 10-bit packed */ +#define PFNC_Coord3D_A12p 0x010C00D8 /* 3D coordinate A 12-bit packed */ +#define PFNC_Coord3D_A16 0x011000B6 /* 3D coordinate A 16-bit */ +#define PFNC_Coord3D_A32f 0x012000BD /* 3D coordinate A 32-bit floating point */ +#define PFNC_Coord3D_B8 0x010800B0 /* 3D coordinate B 8-bit */ +#define PFNC_Coord3D_B10p 0x010A00D6 /* 3D coordinate B 10-bit packed */ +#define PFNC_Coord3D_B12p 0x010C00D9 /* 3D coordinate B 12-bit packed */ +#define PFNC_Coord3D_B16 0x011000B7 /* 3D coordinate B 16-bit */ +#define PFNC_Coord3D_B32f 0x012000BE /* 3D coordinate B 32-bit floating point */ +#define PFNC_Coord3D_C8 0x010800B1 /* 3D coordinate C 8-bit */ +#define PFNC_Coord3D_C10p 0x010A00D7 /* 3D coordinate C 10-bit packed */ +#define PFNC_Coord3D_C12p 0x010C00DA /* 3D coordinate C 12-bit packed */ +#define PFNC_Coord3D_C16 0x011000B8 /* 3D coordinate C 16-bit */ +#define PFNC_Coord3D_C32f 0x012000BF /* 3D coordinate C 32-bit floating point */ +#define PFNC_Confidence1 0x010800C4 /* Confidence 1-bit unpacked */ +#define PFNC_Confidence1p 0x010100C5 /* Confidence 1-bit packed */ +#define PFNC_Confidence8 0x010800C6 /* Confidence 8-bit */ +#define PFNC_Confidence16 0x011000C7 /* Confidence 16-bit */ +#define PFNC_Confidence32f 0x012000C8 /* Confidence 32-bit floating point */ +#define PFNC_BiColorBGRG8 0x021000A6 /* Bi-color Blue/Green - Red/Green 8-bit */ +#define PFNC_BiColorBGRG10 0x022000A9 /* Bi-color Blue/Green - Red/Green 10-bit unpacked */ +#define PFNC_BiColorBGRG10p 0x021400AA /* Bi-color Blue/Green - Red/Green 10-bit packed */ +#define PFNC_BiColorBGRG12 0x022000AD /* Bi-color Blue/Green - Red/Green 12-bit unpacked */ +#define PFNC_BiColorBGRG12p 0x021800AE /* Bi-color Blue/Green - Red/Green 12-bit packed */ +#define PFNC_BiColorRGBG8 0x021000A5 /* Bi-color Red/Green - Blue/Green 8-bit */ +#define PFNC_BiColorRGBG10 0x022000A7 /* Bi-color Red/Green - Blue/Green 10-bit unpacked */ +#define PFNC_BiColorRGBG10p 0x021400A8 /* Bi-color Red/Green - Blue/Green 10-bit packed */ +#define PFNC_BiColorRGBG12 0x022000AB /* Bi-color Red/Green - Blue/Green 12-bit unpacked */ +#define PFNC_BiColorRGBG12p 0x021800AC /* Bi-color Red/Green - Blue/Green 12-bit packed */ +#define PFNC_SCF1WBWG8 0x01080067 /* Sparse Color Filter #1 White-Blue-White-Green 8-bit */ +#define PFNC_SCF1WBWG10 0x01100068 /* Sparse Color Filter #1 White-Blue-White-Green 10-bit unpacked */ +#define PFNC_SCF1WBWG10p 0x010A0069 /* Sparse Color Filter #1 White-Blue-White-Green 10-bit packed */ +#define PFNC_SCF1WBWG12 0x0110006A /* Sparse Color Filter #1 White-Blue-White-Green 12-bit unpacked */ +#define PFNC_SCF1WBWG12p 0x010C006B /* Sparse Color Filter #1 White-Blue-White-Green 12-bit packed */ +#define PFNC_SCF1WBWG14 0x0110006C /* Sparse Color Filter #1 White-Blue-White-Green 14-bit unpacked */ +#define PFNC_SCF1WBWG16 0x0110006D /* Sparse Color Filter #1 White-Blue-White-Green 16-bit unpacked */ +#define PFNC_SCF1WGWB8 0x0108006E /* Sparse Color Filter #1 White-Green-White-Blue 8-bit */ +#define PFNC_SCF1WGWB10 0x0110006F /* Sparse Color Filter #1 White-Green-White-Blue 10-bit unpacked */ +#define PFNC_SCF1WGWB10p 0x010A0070 /* Sparse Color Filter #1 White-Green-White-Blue 10-bit packed */ +#define PFNC_SCF1WGWB12 0x01100071 /* Sparse Color Filter #1 White-Green-White-Blue 12-bit unpacked */ +#define PFNC_SCF1WGWB12p 0x010C0072 /* Sparse Color Filter #1 White-Green-White-Blue 12-bit packed */ +#define PFNC_SCF1WGWB14 0x01100073 /* Sparse Color Filter #1 White-Green-White-Blue 14-bit unpacked */ +#define PFNC_SCF1WGWB16 0x01100074 /* Sparse Color Filter #1 White-Green-White-Blue 16-bit */ +#define PFNC_SCF1WGWR8 0x01080075 /* Sparse Color Filter #1 White-Green-White-Red 8-bit */ +#define PFNC_SCF1WGWR10 0x01100076 /* Sparse Color Filter #1 White-Green-White-Red 10-bit unpacked */ +#define PFNC_SCF1WGWR10p 0x010A0077 /* Sparse Color Filter #1 White-Green-White-Red 10-bit packed */ +#define PFNC_SCF1WGWR12 0x01100078 /* Sparse Color Filter #1 White-Green-White-Red 12-bit unpacked */ +#define PFNC_SCF1WGWR12p 0x010C0079 /* Sparse Color Filter #1 White-Green-White-Red 12-bit packed */ +#define PFNC_SCF1WGWR14 0x0110007A /* Sparse Color Filter #1 White-Green-White-Red 14-bit unpacked */ +#define PFNC_SCF1WGWR16 0x0110007B /* Sparse Color Filter #1 White-Green-White-Red 16-bit */ +#define PFNC_SCF1WRWG8 0x0108007C /* Sparse Color Filter #1 White-Red-White-Green 8-bit */ +#define PFNC_SCF1WRWG10 0x0110007D /* Sparse Color Filter #1 White-Red-White-Green 10-bit unpacked */ +#define PFNC_SCF1WRWG10p 0x010A007E /* Sparse Color Filter #1 White-Red-White-Green 10-bit packed */ +#define PFNC_SCF1WRWG12 0x0110007F /* Sparse Color Filter #1 White-Red-White-Green 12-bit unpacked */ +#define PFNC_SCF1WRWG12p 0x010C0080 /* Sparse Color Filter #1 White-Red-White-Green 12-bit packed */ +#define PFNC_SCF1WRWG14 0x01100081 /* Sparse Color Filter #1 White-Red-White-Green 14-bit unpacked */ +#define PFNC_SCF1WRWG16 0x01100082 /* Sparse Color Filter #1 White-Red-White-Green 16-bit */ +#define PFNC_YCbCr8 0x0218005B /* YCbCr 4:4:4 8-bit */ +#define PFNC_YCbCr8_CbYCr 0x0218003A /* YCbCr 4:4:4 8-bit */ +#define PFNC_YCbCr10_CbYCr 0x02300083 /* YCbCr 4:4:4 10-bit unpacked */ +#define PFNC_YCbCr10p_CbYCr 0x021E0084 /* YCbCr 4:4:4 10-bit packed */ +#define PFNC_YCbCr12_CbYCr 0x02300085 /* YCbCr 4:4:4 12-bit unpacked */ +#define PFNC_YCbCr12p_CbYCr 0x02240086 /* YCbCr 4:4:4 12-bit packed */ +#define PFNC_YCbCr411_8 0x020C005A /* YCbCr 4:1:1 8-bit */ +#define PFNC_YCbCr411_8_CbYYCrYY 0x020C003C /* YCbCr 4:1:1 8-bit */ +#define PFNC_YCbCr422_8 0x0210003B /* YCbCr 4:2:2 8-bit */ +#define PFNC_YCbCr422_8_CbYCrY 0x02100043 /* YCbCr 4:2:2 8-bit */ +#define PFNC_YCbCr422_10 0x02200065 /* YCbCr 4:2:2 10-bit unpacked */ +#define PFNC_YCbCr422_10_CbYCrY 0x02200099 /* YCbCr 4:2:2 10-bit unpacked */ +#define PFNC_YCbCr422_10p 0x02140087 /* YCbCr 4:2:2 10-bit packed */ +#define PFNC_YCbCr422_10p_CbYCrY 0x0214009A /* YCbCr 4:2:2 10-bit packed */ +#define PFNC_YCbCr422_12 0x02200066 /* YCbCr 4:2:2 12-bit unpacked */ +#define PFNC_YCbCr422_12_CbYCrY 0x0220009B /* YCbCr 4:2:2 12-bit unpacked */ +#define PFNC_YCbCr422_12p 0x02180088 /* YCbCr 4:2:2 12-bit packed */ +#define PFNC_YCbCr422_12p_CbYCrY 0x0218009C /* YCbCr 4:2:2 12-bit packed */ +#define PFNC_YCbCr601_8_CbYCr 0x0218003D /* YCbCr 4:4:4 8-bit BT.601 */ +#define PFNC_YCbCr601_10_CbYCr 0x02300089 /* YCbCr 4:4:4 10-bit unpacked BT.601 */ +#define PFNC_YCbCr601_10p_CbYCr 0x021E008A /* YCbCr 4:4:4 10-bit packed BT.601 */ +#define PFNC_YCbCr601_12_CbYCr 0x0230008B /* YCbCr 4:4:4 12-bit unpacked BT.601 */ +#define PFNC_YCbCr601_12p_CbYCr 0x0224008C /* YCbCr 4:4:4 12-bit packed BT.601 */ +#define PFNC_YCbCr601_411_8_CbYYCrYY 0x020C003F /* YCbCr 4:1:1 8-bit BT.601 */ +#define PFNC_YCbCr601_422_8 0x0210003E /* YCbCr 4:2:2 8-bit BT.601 */ +#define PFNC_YCbCr601_422_8_CbYCrY 0x02100044 /* YCbCr 4:2:2 8-bit BT.601 */ +#define PFNC_YCbCr601_422_10 0x0220008D /* YCbCr 4:2:2 10-bit unpacked BT.601 */ +#define PFNC_YCbCr601_422_10_CbYCrY 0x0220009D /* YCbCr 4:2:2 10-bit unpacked BT.601 */ +#define PFNC_YCbCr601_422_10p 0x0214008E /* YCbCr 4:2:2 10-bit packed BT.601 */ +#define PFNC_YCbCr601_422_10p_CbYCrY 0x0214009E /* YCbCr 4:2:2 10-bit packed BT.601 */ +#define PFNC_YCbCr601_422_12 0x0220008F /* YCbCr 4:2:2 12-bit unpacked BT.601 */ +#define PFNC_YCbCr601_422_12_CbYCrY 0x0220009F /* YCbCr 4:2:2 12-bit unpacked BT.601 */ +#define PFNC_YCbCr601_422_12p 0x02180090 /* YCbCr 4:2:2 12-bit packed BT.601 */ +#define PFNC_YCbCr601_422_12p_CbYCrY 0x021800A0 /* YCbCr 4:2:2 12-bit packed BT.601 */ +#define PFNC_YCbCr709_8_CbYCr 0x02180040 /* YCbCr 4:4:4 8-bit BT.709 */ +#define PFNC_YCbCr709_10_CbYCr 0x02300091 /* YCbCr 4:4:4 10-bit unpacked BT.709 */ +#define PFNC_YCbCr709_10p_CbYCr 0x021E0092 /* YCbCr 4:4:4 10-bit packed BT.709 */ +#define PFNC_YCbCr709_12_CbYCr 0x02300093 /* YCbCr 4:4:4 12-bit unpacked BT.709 */ +#define PFNC_YCbCr709_12p_CbYCr 0x02240094 /* YCbCr 4:4:4 12-bit packed BT.709 */ +#define PFNC_YCbCr709_411_8_CbYYCrYY 0x020C0042 /* YCbCr 4:1:1 8-bit BT.709 */ +#define PFNC_YCbCr709_422_8 0x02100041 /* YCbCr 4:2:2 8-bit BT.709 */ +#define PFNC_YCbCr709_422_8_CbYCrY 0x02100045 /* YCbCr 4:2:2 8-bit BT.709 */ +#define PFNC_YCbCr709_422_10 0x02200095 /* YCbCr 4:2:2 10-bit unpacked BT.709 */ +#define PFNC_YCbCr709_422_10_CbYCrY 0x022000A1 /* YCbCr 4:2:2 10-bit unpacked BT.709 */ +#define PFNC_YCbCr709_422_10p 0x02140096 /* YCbCr 4:2:2 10-bit packed BT.709 */ +#define PFNC_YCbCr709_422_10p_CbYCrY 0x021400A2 /* YCbCr 4:2:2 10-bit packed BT.709 */ +#define PFNC_YCbCr709_422_12 0x02200097 /* YCbCr 4:2:2 12-bit unpacked BT.709 */ +#define PFNC_YCbCr709_422_12_CbYCrY 0x022000A3 /* YCbCr 4:2:2 12-bit unpacked BT.709 */ +#define PFNC_YCbCr709_422_12p 0x02180098 /* YCbCr 4:2:2 12-bit packed BT.709 */ +#define PFNC_YCbCr709_422_12p_CbYCrY 0x021800A4 /* YCbCr 4:2:2 12-bit packed BT.709 */ +#define PFNC_YCbCr2020_8_CbYCr 0x021800F4 /* YCbCr 4:4:4 8-bit BT.2020 */ +#define PFNC_YCbCr2020_10_CbYCr 0x023000F5 /* YCbCr 4:4:4 10-bit unpacked BT.2020 */ +#define PFNC_YCbCr2020_10p_CbYCr 0x021E00F6 /* YCbCr 4:4:4 10-bit packed BT.2020 */ +#define PFNC_YCbCr2020_12_CbYCr 0x023000F7 /* YCbCr 4:4:4 12-bit unpacked BT.2020 */ +#define PFNC_YCbCr2020_12p_CbYCr 0x022400F8 /* YCbCr 4:4:4 12-bit packed BT.2020 */ +#define PFNC_YCbCr2020_411_8_CbYYCrYY 0x020C00F9 /* YCbCr 4:1:1 8-bit BT.2020 */ +#define PFNC_YCbCr2020_422_8 0x021000FA /* YCbCr 4:2:2 8-bit BT.2020 */ +#define PFNC_YCbCr2020_422_8_CbYCrY 0x021000FB /* YCbCr 4:2:2 8-bit BT.2020 */ +#define PFNC_YCbCr2020_422_10 0x022000FC /* YCbCr 4:2:2 10-bit unpacked BT.2020 */ +#define PFNC_YCbCr2020_422_10_CbYCrY 0x022000FD /* YCbCr 4:2:2 10-bit unpacked BT.2020 */ +#define PFNC_YCbCr2020_422_10p 0x021400FE /* YCbCr 4:2:2 10-bit packed BT.2020 */ +#define PFNC_YCbCr2020_422_10p_CbYCrY 0x021400FF /* YCbCr 4:2:2 10-bit packed BT.2020 */ +#define PFNC_YCbCr2020_422_12 0x02200100 /* YCbCr 4:2:2 12-bit unpacked BT.2020 */ +#define PFNC_YCbCr2020_422_12_CbYCrY 0x02200101 /* YCbCr 4:2:2 12-bit unpacked BT.2020 */ +#define PFNC_YCbCr2020_422_12p 0x02180102 /* YCbCr 4:2:2 12-bit packed BT.2020 */ +#define PFNC_YCbCr2020_422_12p_CbYCrY 0x02180103 /* YCbCr 4:2:2 12-bit packed BT.2020 */ +#define PFNC_YUV8_UYV 0x02180020 /* YUV 4:4:4 8-bit */ +#define PFNC_YUV411_8_UYYVYY 0x020C001E /* YUV 4:1:1 8-bit */ +#define PFNC_YUV422_8 0x02100032 /* YUV 4:2:2 8-bit */ +#define PFNC_YUV422_8_UYVY 0x0210001F /* YUV 4:2:2 8-bit */ + +/* Following formats are not PFNC compliant, but are still kept in the list for legacy purposes. */ + +#define GVSP_Mono10Packed 0x010C0004 /* GigE Vision specific format, Monochrome 10-bit packed */ +#define GVSP_Mono12Packed 0x010C0006 /* GigE Vision specific format, Monochrome 12-bit packed */ +#define GVSP_BayerBG10Packed 0x010C0029 /* GigE Vision specific format, Bayer Blue-Green 10-bit packed */ +#define GVSP_BayerBG12Packed 0x010C002D /* GigE Vision specific format, Bayer Blue-Green 12-bit packed */ +#define GVSP_BayerGB10Packed 0x010C0028 /* GigE Vision specific format, Bayer Green-Blue 10-bit packed */ +#define GVSP_BayerGB12Packed 0x010C002C /* GigE Vision specific format, Bayer Green-Blue 12-bit packed */ +#define GVSP_BayerGR10Packed 0x010C0026 /* GigE Vision specific format, Bayer Green-Red 10-bit packed */ +#define GVSP_BayerGR12Packed 0x010C002A /* GigE Vision specific format, Bayer Green-Red 12-bit packed */ +#define GVSP_BayerRG10Packed 0x010C0027 /* GigE Vision specific format, Bayer Red-Green 10-bit packed */ +#define GVSP_BayerRG12Packed 0x010C002B /* GigE Vision specific format, Bayer Red-Green 12-bit packed */ +#define GVSP_RGB10V1Packed 0x0220001C /* GigE Vision specific format, Red-Green-Blue 10-bit packed - variant 1 */ +#define GVSP_RGB12V1Packed 0x02240034 /* GigE Vision specific format, Red-Green-Blue 12-bit packed - variant 1 */ + +/* Following formats are Lucid Vision custom pixel formats */ +#define LUCID_PolarizeMono8 0x81080001 +#define LUCID_PolarizeMono12p 0x810C0047 +#define LUCID_PolarizeMono12Packed 0x810C0006 +#define LUCID_PolarizeMono16 0x81100007 + +/* Identical set of formats in the form of an enum */ + +typedef enum PfncFormat_ +{ + Mono1p = 0x01010037, /* Monochrome 1-bit packed */ + Mono2p = 0x01020038, /* Monochrome 2-bit packed */ + Mono4p = 0x01040039, /* Monochrome 4-bit packed */ + Mono8 = 0x01080001, /* Monochrome 8-bit */ + Mono8s = 0x01080002, /* Monochrome 8-bit signed */ + Mono10 = 0x01100003, /* Monochrome 10-bit unpacked */ + Mono10p = 0x010A0046, /* Monochrome 10-bit packed */ + Mono12 = 0x01100005, /* Monochrome 12-bit unpacked */ + Mono12p = 0x010C0047, /* Monochrome 12-bit packed */ + Mono14 = 0x01100025, /* Monochrome 14-bit unpacked */ + Mono16 = 0x01100007, /* Monochrome 16-bit */ + BayerBG8 = 0x0108000B, /* Bayer Blue-Green 8-bit */ + BayerBG10 = 0x0110000F, /* Bayer Blue-Green 10-bit unpacked */ + BayerBG10p = 0x010A0052, /* Bayer Blue-Green 10-bit packed */ + BayerBG12 = 0x01100013, /* Bayer Blue-Green 12-bit unpacked */ + BayerBG12p = 0x010C0053, /* Bayer Blue-Green 12-bit packed */ + BayerBG16 = 0x01100031, /* Bayer Blue-Green 16-bit */ + BayerGB8 = 0x0108000A, /* Bayer Green-Blue 8-bit */ + BayerGB10 = 0x0110000E, /* Bayer Green-Blue 10-bit unpacked */ + BayerGB10p = 0x010A0054, /* Bayer Green-Blue 10-bit packed */ + BayerGB12 = 0x01100012, /* Bayer Green-Blue 12-bit unpacked */ + BayerGB12p = 0x010C0055, /* Bayer Green-Blue 12-bit packed */ + BayerGB16 = 0x01100030, /* Bayer Green-Blue 16-bit */ + BayerGR8 = 0x01080008, /* Bayer Green-Red 8-bit */ + BayerGR10 = 0x0110000C, /* Bayer Green-Red 10-bit unpacked */ + BayerGR10p = 0x010A0056, /* Bayer Green-Red 10-bit packed */ + BayerGR12 = 0x01100010, /* Bayer Green-Red 12-bit unpacked */ + BayerGR12p = 0x010C0057, /* Bayer Green-Red 12-bit packed */ + BayerGR16 = 0x0110002E, /* Bayer Green-Red 16-bit */ + BayerRG8 = 0x01080009, /* Bayer Red-Green 8-bit */ + BayerRG10 = 0x0110000D, /* Bayer Red-Green 10-bit unpacked */ + BayerRG10p = 0x010A0058, /* Bayer Red-Green 10-bit packed */ + BayerRG12 = 0x01100011, /* Bayer Red-Green 12-bit unpacked */ + BayerRG12p = 0x010C0059, /* Bayer Red-Green 12-bit packed */ + BayerRG16 = 0x0110002F, /* Bayer Red-Green 16-bit */ + RGBa8 = 0x02200016, /* Red-Green-Blue-alpha 8-bit */ + RGBa10 = 0x0240005F, /* Red-Green-Blue-alpha 10-bit unpacked */ + RGBa10p = 0x02280060, /* Red-Green-Blue-alpha 10-bit packed */ + RGBa12 = 0x02400061, /* Red-Green-Blue-alpha 12-bit unpacked */ + RGBa12p = 0x02300062, /* Red-Green-Blue-alpha 12-bit packed */ + RGBa14 = 0x02400063, /* Red-Green-Blue-alpha 14-bit unpacked */ + RGBa16 = 0x02400064, /* Red-Green-Blue-alpha 16-bit */ + RGB8 = 0x02180014, /* Red-Green-Blue 8-bit */ + RGB8_Planar = 0x02180021, /* Red-Green-Blue 8-bit planar */ + RGB10 = 0x02300018, /* Red-Green-Blue 10-bit unpacked */ + RGB10_Planar = 0x02300022, /* Red-Green-Blue 10-bit unpacked planar */ + RGB10p = 0x021E005C, /* Red-Green-Blue 10-bit packed */ + RGB10p32 = 0x0220001D, /* Red-Green-Blue 10-bit packed into 32-bit */ + RGB12 = 0x0230001A, /* Red-Green-Blue 12-bit unpacked */ + RGB12_Planar = 0x02300023, /* Red-Green-Blue 12-bit unpacked planar */ + RGB12p = 0x0224005D, /* Red-Green-Blue 12-bit packed */ + RGB14 = 0x0230005E, /* Red-Green-Blue 14-bit unpacked */ + RGB16 = 0x02300033, /* Red-Green-Blue 16-bit */ + RGB16_Planar = 0x02300024, /* Red-Green-Blue 16-bit planar */ + RGB565p = 0x02100035, /* Red-Green-Blue 5/6/5-bit packed */ + BGRa8 = 0x02200017, /* Blue-Green-Red-alpha 8-bit */ + BGRa10 = 0x0240004C, /* Blue-Green-Red-alpha 10-bit unpacked */ + BGRa10p = 0x0228004D, /* Blue-Green-Red-alpha 10-bit packed */ + BGRa12 = 0x0240004E, /* Blue-Green-Red-alpha 12-bit unpacked */ + BGRa12p = 0x0230004F, /* Blue-Green-Red-alpha 12-bit packed */ + BGRa14 = 0x02400050, /* Blue-Green-Red-alpha 14-bit unpacked */ + BGRa16 = 0x02400051, /* Blue-Green-Red-alpha 16-bit */ + BGR8 = 0x02180015, /* Blue-Green-Red 8-bit */ + BGR10 = 0x02300019, /* Blue-Green-Red 10-bit unpacked */ + BGR10p = 0x021E0048, /* Blue-Green-Red 10-bit packed */ + BGR12 = 0x0230001B, /* Blue-Green-Red 12-bit unpacked */ + BGR12p = 0x02240049, /* Blue-Green-Red 12-bit packed */ + BGR14 = 0x0230004A, /* Blue-Green-Red 14-bit unpacked */ + BGR16 = 0x0230004B, /* Blue-Green-Red 16-bit */ + BGR565p = 0x02100036, /* Blue-Green-Red 5/6/5-bit packed */ + R8 = 0x010800C9, /* Red 8-bit */ + R10 = 0x010A00CA, /* Red 10-bit */ + R12 = 0x010C00CB, /* Red 12-bit */ + R16 = 0x011000CC, /* Red 16-bit */ + G8 = 0x010800CD, /* Green 8-bit */ + G10 = 0x010A00CE, /* Green 10-bit */ + G12 = 0x010C00CF, /* Green 12-bit */ + G16 = 0x011000D0, /* Green 16-bit */ + B8 = 0x010800D1, /* Blue 8-bit */ + B10 = 0x010A00D2, /* Blue 10-bit */ + B12 = 0x010C00D3, /* Blue 12-bit */ + B16 = 0x011000D4, /* Blue 16-bit */ + Coord3D_ABC8 = 0x021800B2, /* 3D coordinate A-B-C 8-bit */ + Coord3D_ABC8_Planar = 0x021800B3, /* 3D coordinate A-B-C 8-bit planar */ + Coord3D_ABC10p = 0x021E00DB, /* 3D coordinate A-B-C 10-bit packed */ + Coord3D_ABC10p_Planar = 0x021E00DC, /* 3D coordinate A-B-C 10-bit packed planar */ + Coord3D_ABC12p = 0x022400DE, /* 3D coordinate A-B-C 12-bit packed */ + Coord3D_ABC12p_Planar = 0x022400DF, /* 3D coordinate A-B-C 12-bit packed planar */ + Coord3D_ABC16 = 0x023000B9, /* 3D coordinate A-B-C 16-bit */ + Coord3D_ABC16_Planar = 0x023000BA, /* 3D coordinate A-B-C 16-bit planar */ + Coord3D_ABC32f = 0x026000C0, /* 3D coordinate A-B-C 32-bit floating point */ + Coord3D_ABC32f_Planar = 0x026000C1, /* 3D coordinate A-B-C 32-bit floating point planar */ + Coord3D_AC8 = 0x021000B4, /* 3D coordinate A-C 8-bit */ + Coord3D_AC8_Planar = 0x021000B5, /* 3D coordinate A-C 8-bit planar */ + Coord3D_AC10p = 0x021400F0, /* 3D coordinate A-C 10-bit packed */ + Coord3D_AC10p_Planar = 0x021400F1, /* 3D coordinate A-C 10-bit packed planar */ + Coord3D_AC12p = 0x021800F2, /* 3D coordinate A-C 12-bit packed */ + Coord3D_AC12p_Planar = 0x021800F3, /* 3D coordinate A-C 12-bit packed planar */ + Coord3D_AC16 = 0x022000BB, /* 3D coordinate A-C 16-bit */ + Coord3D_AC16_Planar = 0x022000BC, /* 3D coordinate A-C 16-bit planar */ + Coord3D_AC32f = 0x024000C2, /* 3D coordinate A-C 32-bit floating point */ + Coord3D_AC32f_Planar = 0x024000C3, /* 3D coordinate A-C 32-bit floating point planar */ + Coord3D_A8 = 0x010800AF, /* 3D coordinate A 8-bit */ + Coord3D_A10p = 0x010A00D5, /* 3D coordinate A 10-bit packed */ + Coord3D_A12p = 0x010C00D8, /* 3D coordinate A 12-bit packed */ + Coord3D_A16 = 0x011000B6, /* 3D coordinate A 16-bit */ + Coord3D_A32f = 0x012000BD, /* 3D coordinate A 32-bit floating point */ + Coord3D_B8 = 0x010800B0, /* 3D coordinate B 8-bit */ + Coord3D_B10p = 0x010A00D6, /* 3D coordinate B 10-bit packed */ + Coord3D_B12p = 0x010C00D9, /* 3D coordinate B 12-bit packed */ + Coord3D_B16 = 0x011000B7, /* 3D coordinate B 16-bit */ + Coord3D_B32f = 0x012000BE, /* 3D coordinate B 32-bit floating point */ + Coord3D_C8 = 0x010800B1, /* 3D coordinate C 8-bit */ + Coord3D_C10p = 0x010A00D7, /* 3D coordinate C 10-bit packed */ + Coord3D_C12p = 0x010C00DA, /* 3D coordinate C 12-bit packed */ + Coord3D_C16 = 0x011000B8, /* 3D coordinate C 16-bit */ + Coord3D_C32f = 0x012000BF, /* 3D coordinate C 32-bit floating point */ + Confidence1 = 0x010800C4, /* Confidence 1-bit unpacked */ + Confidence1p = 0x010100C5, /* Confidence 1-bit packed */ + Confidence8 = 0x010800C6, /* Confidence 8-bit */ + Confidence16 = 0x011000C7, /* Confidence 16-bit */ + Confidence32f = 0x012000C8, /* Confidence 32-bit floating point */ + BiColorBGRG8 = 0x021000A6, /* Bi-color Blue/Green - Red/Green 8-bit */ + BiColorBGRG10 = 0x022000A9, /* Bi-color Blue/Green - Red/Green 10-bit unpacked */ + BiColorBGRG10p = 0x021400AA, /* Bi-color Blue/Green - Red/Green 10-bit packed */ + BiColorBGRG12 = 0x022000AD, /* Bi-color Blue/Green - Red/Green 12-bit unpacked */ + BiColorBGRG12p = 0x021800AE, /* Bi-color Blue/Green - Red/Green 12-bit packed */ + BiColorRGBG8 = 0x021000A5, /* Bi-color Red/Green - Blue/Green 8-bit */ + BiColorRGBG10 = 0x022000A7, /* Bi-color Red/Green - Blue/Green 10-bit unpacked */ + BiColorRGBG10p = 0x021400A8, /* Bi-color Red/Green - Blue/Green 10-bit packed */ + BiColorRGBG12 = 0x022000AB, /* Bi-color Red/Green - Blue/Green 12-bit unpacked */ + BiColorRGBG12p = 0x021800AC, /* Bi-color Red/Green - Blue/Green 12-bit packed */ + SCF1WBWG8 = 0x01080067, /* Sparse Color Filter #1 White-Blue-White-Green 8-bit */ + SCF1WBWG10 = 0x01100068, /* Sparse Color Filter #1 White-Blue-White-Green 10-bit unpacked */ + SCF1WBWG10p = 0x010A0069, /* Sparse Color Filter #1 White-Blue-White-Green 10-bit packed */ + SCF1WBWG12 = 0x0110006A, /* Sparse Color Filter #1 White-Blue-White-Green 12-bit unpacked */ + SCF1WBWG12p = 0x010C006B, /* Sparse Color Filter #1 White-Blue-White-Green 12-bit packed */ + SCF1WBWG14 = 0x0110006C, /* Sparse Color Filter #1 White-Blue-White-Green 14-bit unpacked */ + SCF1WBWG16 = 0x0110006D, /* Sparse Color Filter #1 White-Blue-White-Green 16-bit unpacked */ + SCF1WGWB8 = 0x0108006E, /* Sparse Color Filter #1 White-Green-White-Blue 8-bit */ + SCF1WGWB10 = 0x0110006F, /* Sparse Color Filter #1 White-Green-White-Blue 10-bit unpacked */ + SCF1WGWB10p = 0x010A0070, /* Sparse Color Filter #1 White-Green-White-Blue 10-bit packed */ + SCF1WGWB12 = 0x01100071, /* Sparse Color Filter #1 White-Green-White-Blue 12-bit unpacked */ + SCF1WGWB12p = 0x010C0072, /* Sparse Color Filter #1 White-Green-White-Blue 12-bit packed */ + SCF1WGWB14 = 0x01100073, /* Sparse Color Filter #1 White-Green-White-Blue 14-bit unpacked */ + SCF1WGWB16 = 0x01100074, /* Sparse Color Filter #1 White-Green-White-Blue 16-bit */ + SCF1WGWR8 = 0x01080075, /* Sparse Color Filter #1 White-Green-White-Red 8-bit */ + SCF1WGWR10 = 0x01100076, /* Sparse Color Filter #1 White-Green-White-Red 10-bit unpacked */ + SCF1WGWR10p = 0x010A0077, /* Sparse Color Filter #1 White-Green-White-Red 10-bit packed */ + SCF1WGWR12 = 0x01100078, /* Sparse Color Filter #1 White-Green-White-Red 12-bit unpacked */ + SCF1WGWR12p = 0x010C0079, /* Sparse Color Filter #1 White-Green-White-Red 12-bit packed */ + SCF1WGWR14 = 0x0110007A, /* Sparse Color Filter #1 White-Green-White-Red 14-bit unpacked */ + SCF1WGWR16 = 0x0110007B, /* Sparse Color Filter #1 White-Green-White-Red 16-bit */ + SCF1WRWG8 = 0x0108007C, /* Sparse Color Filter #1 White-Red-White-Green 8-bit */ + SCF1WRWG10 = 0x0110007D, /* Sparse Color Filter #1 White-Red-White-Green 10-bit unpacked */ + SCF1WRWG10p = 0x010A007E, /* Sparse Color Filter #1 White-Red-White-Green 10-bit packed */ + SCF1WRWG12 = 0x0110007F, /* Sparse Color Filter #1 White-Red-White-Green 12-bit unpacked */ + SCF1WRWG12p = 0x010C0080, /* Sparse Color Filter #1 White-Red-White-Green 12-bit packed */ + SCF1WRWG14 = 0x01100081, /* Sparse Color Filter #1 White-Red-White-Green 14-bit unpacked */ + SCF1WRWG16 = 0x01100082, /* Sparse Color Filter #1 White-Red-White-Green 16-bit */ + YCbCr8 = 0x0218005B, /* YCbCr 4:4:4 8-bit */ + YCbCr8_CbYCr = 0x0218003A, /* YCbCr 4:4:4 8-bit */ + YCbCr10_CbYCr = 0x02300083, /* YCbCr 4:4:4 10-bit unpacked */ + YCbCr10p_CbYCr = 0x021E0084, /* YCbCr 4:4:4 10-bit packed */ + YCbCr12_CbYCr = 0x02300085, /* YCbCr 4:4:4 12-bit unpacked */ + YCbCr12p_CbYCr = 0x02240086, /* YCbCr 4:4:4 12-bit packed */ + YCbCr411_8 = 0x020C005A, /* YCbCr 4:1:1 8-bit */ + YCbCr411_8_CbYYCrYY = 0x020C003C, /* YCbCr 4:1:1 8-bit */ + YCbCr422_8 = 0x0210003B, /* YCbCr 4:2:2 8-bit */ + YCbCr422_8_CbYCrY = 0x02100043, /* YCbCr 4:2:2 8-bit */ + YCbCr422_10 = 0x02200065, /* YCbCr 4:2:2 10-bit unpacked */ + YCbCr422_10_CbYCrY = 0x02200099, /* YCbCr 4:2:2 10-bit unpacked */ + YCbCr422_10p = 0x02140087, /* YCbCr 4:2:2 10-bit packed */ + YCbCr422_10p_CbYCrY = 0x0214009A, /* YCbCr 4:2:2 10-bit packed */ + YCbCr422_12 = 0x02200066, /* YCbCr 4:2:2 12-bit unpacked */ + YCbCr422_12_CbYCrY = 0x0220009B, /* YCbCr 4:2:2 12-bit unpacked */ + YCbCr422_12p = 0x02180088, /* YCbCr 4:2:2 12-bit packed */ + YCbCr422_12p_CbYCrY = 0x0218009C, /* YCbCr 4:2:2 12-bit packed */ + YCbCr601_8_CbYCr = 0x0218003D, /* YCbCr 4:4:4 8-bit BT.601 */ + YCbCr601_10_CbYCr = 0x02300089, /* YCbCr 4:4:4 10-bit unpacked BT.601 */ + YCbCr601_10p_CbYCr = 0x021E008A, /* YCbCr 4:4:4 10-bit packed BT.601 */ + YCbCr601_12_CbYCr = 0x0230008B, /* YCbCr 4:4:4 12-bit unpacked BT.601 */ + YCbCr601_12p_CbYCr = 0x0224008C, /* YCbCr 4:4:4 12-bit packed BT.601 */ + YCbCr601_411_8_CbYYCrYY = 0x020C003F, /* YCbCr 4:1:1 8-bit BT.601 */ + YCbCr601_422_8 = 0x0210003E, /* YCbCr 4:2:2 8-bit BT.601 */ + YCbCr601_422_8_CbYCrY = 0x02100044, /* YCbCr 4:2:2 8-bit BT.601 */ + YCbCr601_422_10 = 0x0220008D, /* YCbCr 4:2:2 10-bit unpacked BT.601 */ + YCbCr601_422_10_CbYCrY = 0x0220009D, /* YCbCr 4:2:2 10-bit unpacked BT.601 */ + YCbCr601_422_10p = 0x0214008E, /* YCbCr 4:2:2 10-bit packed BT.601 */ + YCbCr601_422_10p_CbYCrY = 0x0214009E, /* YCbCr 4:2:2 10-bit packed BT.601 */ + YCbCr601_422_12 = 0x0220008F, /* YCbCr 4:2:2 12-bit unpacked BT.601 */ + YCbCr601_422_12_CbYCrY = 0x0220009F, /* YCbCr 4:2:2 12-bit unpacked BT.601 */ + YCbCr601_422_12p = 0x02180090, /* YCbCr 4:2:2 12-bit packed BT.601 */ + YCbCr601_422_12p_CbYCrY = 0x021800A0, /* YCbCr 4:2:2 12-bit packed BT.601 */ + YCbCr709_8_CbYCr = 0x02180040, /* YCbCr 4:4:4 8-bit BT.709 */ + YCbCr709_10_CbYCr = 0x02300091, /* YCbCr 4:4:4 10-bit unpacked BT.709 */ + YCbCr709_10p_CbYCr = 0x021E0092, /* YCbCr 4:4:4 10-bit packed BT.709 */ + YCbCr709_12_CbYCr = 0x02300093, /* YCbCr 4:4:4 12-bit unpacked BT.709 */ + YCbCr709_12p_CbYCr = 0x02240094, /* YCbCr 4:4:4 12-bit packed BT.709 */ + YCbCr709_411_8_CbYYCrYY = 0x020C0042, /* YCbCr 4:1:1 8-bit BT.709 */ + YCbCr709_422_8 = 0x02100041, /* YCbCr 4:2:2 8-bit BT.709 */ + YCbCr709_422_8_CbYCrY = 0x02100045, /* YCbCr 4:2:2 8-bit BT.709 */ + YCbCr709_422_10 = 0x02200095, /* YCbCr 4:2:2 10-bit unpacked BT.709 */ + YCbCr709_422_10_CbYCrY = 0x022000A1, /* YCbCr 4:2:2 10-bit unpacked BT.709 */ + YCbCr709_422_10p = 0x02140096, /* YCbCr 4:2:2 10-bit packed BT.709 */ + YCbCr709_422_10p_CbYCrY = 0x021400A2, /* YCbCr 4:2:2 10-bit packed BT.709 */ + YCbCr709_422_12 = 0x02200097, /* YCbCr 4:2:2 12-bit unpacked BT.709 */ + YCbCr709_422_12_CbYCrY = 0x022000A3, /* YCbCr 4:2:2 12-bit unpacked BT.709 */ + YCbCr709_422_12p = 0x02180098, /* YCbCr 4:2:2 12-bit packed BT.709 */ + YCbCr709_422_12p_CbYCrY = 0x021800A4, /* YCbCr 4:2:2 12-bit packed BT.709 */ + YCbCr2020_8_CbYCr = 0x021800F4, /* YCbCr 4:4:4 8-bit BT.2020 */ + YCbCr2020_10_CbYCr = 0x023000F5, /* YCbCr 4:4:4 10-bit unpacked BT.2020 */ + YCbCr2020_10p_CbYCr = 0x021E00F6, /* YCbCr 4:4:4 10-bit packed BT.2020 */ + YCbCr2020_12_CbYCr = 0x023000F7, /* YCbCr 4:4:4 12-bit unpacked BT.2020 */ + YCbCr2020_12p_CbYCr = 0x022400F8, /* YCbCr 4:4:4 12-bit packed BT.2020 */ + YCbCr2020_411_8_CbYYCrYY = 0x020C00F9, /* YCbCr 4:1:1 8-bit BT.2020 */ + YCbCr2020_422_8 = 0x021000FA, /* YCbCr 4:2:2 8-bit BT.2020 */ + YCbCr2020_422_8_CbYCrY = 0x021000FB, /* YCbCr 4:2:2 8-bit BT.2020 */ + YCbCr2020_422_10 = 0x022000FC, /* YCbCr 4:2:2 10-bit unpacked BT.2020 */ + YCbCr2020_422_10_CbYCrY = 0x022000FD, /* YCbCr 4:2:2 10-bit unpacked BT.2020 */ + YCbCr2020_422_10p = 0x021400FE, /* YCbCr 4:2:2 10-bit packed BT.2020 */ + YCbCr2020_422_10p_CbYCrY = 0x021400FF, /* YCbCr 4:2:2 10-bit packed BT.2020 */ + YCbCr2020_422_12 = 0x02200100, /* YCbCr 4:2:2 12-bit unpacked BT.2020 */ + YCbCr2020_422_12_CbYCrY = 0x02200101, /* YCbCr 4:2:2 12-bit unpacked BT.2020 */ + YCbCr2020_422_12p = 0x02180102, /* YCbCr 4:2:2 12-bit packed BT.2020 */ + YCbCr2020_422_12p_CbYCrY = 0x02180103, /* YCbCr 4:2:2 12-bit packed BT.2020 */ + YUV8_UYV = 0x02180020, /* YUV 4:4:4 8-bit */ + YUV411_8_UYYVYY = 0x020C001E, /* YUV 4:1:1 8-bit */ + YUV422_8 = 0x02100032, /* YUV 4:2:2 8-bit */ + YUV422_8_UYVY = 0x0210001F, /* YUV 4:2:2 8-bit */ + Mono10Packed = 0x010C0004, /* GigE Vision specific format, Monochrome 10-bit packed */ + Mono12Packed = 0x010C0006, /* GigE Vision specific format, Monochrome 12-bit packed */ + BayerBG10Packed = 0x010C0029, /* GigE Vision specific format, Bayer Blue-Green 10-bit packed */ + BayerBG12Packed = 0x010C002D, /* GigE Vision specific format, Bayer Blue-Green 12-bit packed */ + BayerGB10Packed = 0x010C0028, /* GigE Vision specific format, Bayer Green-Blue 10-bit packed */ + BayerGB12Packed = 0x010C002C, /* GigE Vision specific format, Bayer Green-Blue 12-bit packed */ + BayerGR10Packed = 0x010C0026, /* GigE Vision specific format, Bayer Green-Red 10-bit packed */ + BayerGR12Packed = 0x010C002A, /* GigE Vision specific format, Bayer Green-Red 12-bit packed */ + BayerRG10Packed = 0x010C0027, /* GigE Vision specific format, Bayer Red-Green 10-bit packed */ + BayerRG12Packed = 0x010C002B, /* GigE Vision specific format, Bayer Red-Green 12-bit packed */ + RGB10V1Packed = 0x0220001C, /* GigE Vision specific format, Red-Green-Blue 10-bit packed - variant 1 */ + RGB12V1Packed = 0x02240034, /* GigE Vision specific format, Red-Green-Blue 12-bit packed - variant 1 */ + + // Custom pixel formats + PolarizeMono8 = LUCID_PolarizeMono8, + PolarizeMono12p = LUCID_PolarizeMono12p, + PolarizeMono12Packed = LUCID_PolarizeMono12Packed, + PolarizeMono16 = LUCID_PolarizeMono16, + + InvalidPixelFormat = 0 +} PfncFormat; + +/* 32-bit value layout */ +/* |31 24|23 16|15 08|07 00| */ +/* | C| Comp. Layout| Effective Size | Pixel ID | */ + +/* Custom flag */ +#define PFNC_CUSTOM 0x80000000 +/* Component layout */ +#define PFNC_SINGLE_COMPONENT 0x01000000 +#define PFNC_MULTIPLE_COMPONENT 0x02000000 +#define PFNC_COMPONENT_MASK 0x7F000000 +/* Effective size */ +#define PFNC_OCCUPY1BIT 0x00010000 +#define PFNC_OCCUPY2BIT 0x00020000 +#define PFNC_OCCUPY4BIT 0x00040000 +#define PFNC_OCCUPY8BIT 0x00080000 +#define PFNC_OCCUPY10BIT 0x000A0000 +#define PFNC_OCCUPY12BIT 0x000C0000 +#define PFNC_OCCUPY16BIT 0x00100000 +#define PFNC_OCCUPY24BIT 0x00180000 +#define PFNC_OCCUPY30BIT 0x001E0000 +#define PFNC_OCCUPY32BIT 0x00200000 +#define PFNC_OCCUPY36BIT 0x00240000 +#define PFNC_OCCUPY40BIT 0x00280000 +#define PFNC_OCCUPY48BIT 0x00300000 +#define PFNC_OCCUPY64BIT 0x00400000 +#define PFNC_PIXEL_SIZE_MASK 0x00FF0000 +#define PFNC_PIXEL_SIZE_SHIFT 16 +/* Pixel ID */ +#define PFNC_PIXEL_ID_MASK 0x0000FFFF +/* Pixel format value dissection helpers */ +#define PFNC_PIXEL_SIZE(X) ((X & PFNC_PIXEL_SIZE_MASK) >> PFNC_PIXEL_SIZE_SHIFT) +#define PFNC_IS_PIXEL_SINGLE_COMPONENT(X) ((X & PFNC_COMPONENT_MASK) == PFNC_SINGLE_COMPONENT) +#define PFNC_IS_PIXEL_MULTIPLE_COMPONENT(X) ((X & PFNC_COMPONENT_MASK) == PFNC_MULTIPLE_COMPONENT) +#define PFNC_IS_PIXEL_CUSTOM(X) ((X & PFNC_CUSTOM) == PFNC_CUSTOM) +#define PFNC_PIXEL_ID(X) (X & PFNC_PIXEL_ID_MASK) + + +/* Additional helpers */ +#define PFNC_INCLUDE_HELPERS + +#ifdef PFNC_INCLUDE_HELPERS +#ifdef _MSC_VER +# define PFNC_INLINE __inline +#else +# define PFNC_INLINE inline +#endif +static PFNC_INLINE const char* GetPixelFormatName(PfncFormat format) +{ + switch (format) + { + case Mono1p: return "Mono1p"; + case Mono2p: return "Mono2p"; + case Mono4p: return "Mono4p"; + case Mono8: return "Mono8"; + case Mono8s: return "Mono8s"; + case Mono10: return "Mono10"; + case Mono10p: return "Mono10p"; + case Mono12: return "Mono12"; + case Mono12p: return "Mono12p"; + case Mono14: return "Mono14"; + case Mono16: return "Mono16"; + case BayerBG8: return "BayerBG8"; + case BayerBG10: return "BayerBG10"; + case BayerBG10p: return "BayerBG10p"; + case BayerBG12: return "BayerBG12"; + case BayerBG12p: return "BayerBG12p"; + case BayerBG16: return "BayerBG16"; + case BayerGB8: return "BayerGB8"; + case BayerGB10: return "BayerGB10"; + case BayerGB10p: return "BayerGB10p"; + case BayerGB12: return "BayerGB12"; + case BayerGB12p: return "BayerGB12p"; + case BayerGB16: return "BayerGB16"; + case BayerGR8: return "BayerGR8"; + case BayerGR10: return "BayerGR10"; + case BayerGR10p: return "BayerGR10p"; + case BayerGR12: return "BayerGR12"; + case BayerGR12p: return "BayerGR12p"; + case BayerGR16: return "BayerGR16"; + case BayerRG8: return "BayerRG8"; + case BayerRG10: return "BayerRG10"; + case BayerRG10p: return "BayerRG10p"; + case BayerRG12: return "BayerRG12"; + case BayerRG12p: return "BayerRG12p"; + case BayerRG16: return "BayerRG16"; + case RGBa8: return "RGBa8"; + case RGBa10: return "RGBa10"; + case RGBa10p: return "RGBa10p"; + case RGBa12: return "RGBa12"; + case RGBa12p: return "RGBa12p"; + case RGBa14: return "RGBa14"; + case RGBa16: return "RGBa16"; + case RGB8: return "RGB8"; + case RGB8_Planar: return "RGB8_Planar"; + case RGB10: return "RGB10"; + case RGB10_Planar: return "RGB10_Planar"; + case RGB10p: return "RGB10p"; + case RGB10p32: return "RGB10p32"; + case RGB12: return "RGB12"; + case RGB12_Planar: return "RGB12_Planar"; + case RGB12p: return "RGB12p"; + case RGB14: return "RGB14"; + case RGB16: return "RGB16"; + case RGB16_Planar: return "RGB16_Planar"; + case RGB565p: return "RGB565p"; + case BGRa8: return "BGRa8"; + case BGRa10: return "BGRa10"; + case BGRa10p: return "BGRa10p"; + case BGRa12: return "BGRa12"; + case BGRa12p: return "BGRa12p"; + case BGRa14: return "BGRa14"; + case BGRa16: return "BGRa16"; + case BGR8: return "BGR8"; + case BGR10: return "BGR10"; + case BGR10p: return "BGR10p"; + case BGR12: return "BGR12"; + case BGR12p: return "BGR12p"; + case BGR14: return "BGR14"; + case BGR16: return "BGR16"; + case BGR565p: return "BGR565p"; + case R8: return "R8"; + case R10: return "R10"; + case R12: return "R12"; + case R16: return "R16"; + case G8: return "G8"; + case G10: return "G10"; + case G12: return "G12"; + case G16: return "G16"; + case B8: return "B8"; + case B10: return "B10"; + case B12: return "B12"; + case B16: return "B16"; + case Coord3D_ABC8: return "Coord3D_ABC8"; + case Coord3D_ABC8_Planar: return "Coord3D_ABC8_Planar"; + case Coord3D_ABC10p: return "Coord3D_ABC10p"; + case Coord3D_ABC10p_Planar: return "Coord3D_ABC10p_Planar"; + case Coord3D_ABC12p: return "Coord3D_ABC12p"; + case Coord3D_ABC12p_Planar: return "Coord3D_ABC12p_Planar"; + case Coord3D_ABC16: return "Coord3D_ABC16"; + case Coord3D_ABC16_Planar: return "Coord3D_ABC16_Planar"; + case Coord3D_ABC32f: return "Coord3D_ABC32f"; + case Coord3D_ABC32f_Planar: return "Coord3D_ABC32f_Planar"; + case Coord3D_AC8: return "Coord3D_AC8"; + case Coord3D_AC8_Planar: return "Coord3D_AC8_Planar"; + case Coord3D_AC10p: return "Coord3D_AC10p"; + case Coord3D_AC10p_Planar: return "Coord3D_AC10p_Planar"; + case Coord3D_AC12p: return "Coord3D_AC12p"; + case Coord3D_AC12p_Planar: return "Coord3D_AC12p_Planar"; + case Coord3D_AC16: return "Coord3D_AC16"; + case Coord3D_AC16_Planar: return "Coord3D_AC16_Planar"; + case Coord3D_AC32f: return "Coord3D_AC32f"; + case Coord3D_AC32f_Planar: return "Coord3D_AC32f_Planar"; + case Coord3D_A8: return "Coord3D_A8"; + case Coord3D_A10p: return "Coord3D_A10p"; + case Coord3D_A12p: return "Coord3D_A12p"; + case Coord3D_A16: return "Coord3D_A16"; + case Coord3D_A32f: return "Coord3D_A32f"; + case Coord3D_B8: return "Coord3D_B8"; + case Coord3D_B10p: return "Coord3D_B10p"; + case Coord3D_B12p: return "Coord3D_B12p"; + case Coord3D_B16: return "Coord3D_B16"; + case Coord3D_B32f: return "Coord3D_B32f"; + case Coord3D_C8: return "Coord3D_C8"; + case Coord3D_C10p: return "Coord3D_C10p"; + case Coord3D_C12p: return "Coord3D_C12p"; + case Coord3D_C16: return "Coord3D_C16"; + case Coord3D_C32f: return "Coord3D_C32f"; + case Confidence1: return "Confidence1"; + case Confidence1p: return "Confidence1p"; + case Confidence8: return "Confidence8"; + case Confidence16: return "Confidence16"; + case Confidence32f: return "Confidence32f"; + case BiColorBGRG8: return "BiColorBGRG8"; + case BiColorBGRG10: return "BiColorBGRG10"; + case BiColorBGRG10p: return "BiColorBGRG10p"; + case BiColorBGRG12: return "BiColorBGRG12"; + case BiColorBGRG12p: return "BiColorBGRG12p"; + case BiColorRGBG8: return "BiColorRGBG8"; + case BiColorRGBG10: return "BiColorRGBG10"; + case BiColorRGBG10p: return "BiColorRGBG10p"; + case BiColorRGBG12: return "BiColorRGBG12"; + case BiColorRGBG12p: return "BiColorRGBG12p"; + case SCF1WBWG8: return "SCF1WBWG8"; + case SCF1WBWG10: return "SCF1WBWG10"; + case SCF1WBWG10p: return "SCF1WBWG10p"; + case SCF1WBWG12: return "SCF1WBWG12"; + case SCF1WBWG12p: return "SCF1WBWG12p"; + case SCF1WBWG14: return "SCF1WBWG14"; + case SCF1WBWG16: return "SCF1WBWG16"; + case SCF1WGWB8: return "SCF1WGWB8"; + case SCF1WGWB10: return "SCF1WGWB10"; + case SCF1WGWB10p: return "SCF1WGWB10p"; + case SCF1WGWB12: return "SCF1WGWB12"; + case SCF1WGWB12p: return "SCF1WGWB12p"; + case SCF1WGWB14: return "SCF1WGWB14"; + case SCF1WGWB16: return "SCF1WGWB16"; + case SCF1WGWR8: return "SCF1WGWR8"; + case SCF1WGWR10: return "SCF1WGWR10"; + case SCF1WGWR10p: return "SCF1WGWR10p"; + case SCF1WGWR12: return "SCF1WGWR12"; + case SCF1WGWR12p: return "SCF1WGWR12p"; + case SCF1WGWR14: return "SCF1WGWR14"; + case SCF1WGWR16: return "SCF1WGWR16"; + case SCF1WRWG8: return "SCF1WRWG8"; + case SCF1WRWG10: return "SCF1WRWG10"; + case SCF1WRWG10p: return "SCF1WRWG10p"; + case SCF1WRWG12: return "SCF1WRWG12"; + case SCF1WRWG12p: return "SCF1WRWG12p"; + case SCF1WRWG14: return "SCF1WRWG14"; + case SCF1WRWG16: return "SCF1WRWG16"; + case YCbCr8: return "YCbCr8"; + case YCbCr8_CbYCr: return "YCbCr8_CbYCr"; + case YCbCr10_CbYCr: return "YCbCr10_CbYCr"; + case YCbCr10p_CbYCr: return "YCbCr10p_CbYCr"; + case YCbCr12_CbYCr: return "YCbCr12_CbYCr"; + case YCbCr12p_CbYCr: return "YCbCr12p_CbYCr"; + case YCbCr411_8: return "YCbCr411_8"; + case YCbCr411_8_CbYYCrYY: return "YCbCr411_8_CbYYCrYY"; + case YCbCr422_8: return "YCbCr422_8"; + case YCbCr422_8_CbYCrY: return "YCbCr422_8_CbYCrY"; + case YCbCr422_10: return "YCbCr422_10"; + case YCbCr422_10_CbYCrY: return "YCbCr422_10_CbYCrY"; + case YCbCr422_10p: return "YCbCr422_10p"; + case YCbCr422_10p_CbYCrY: return "YCbCr422_10p_CbYCrY"; + case YCbCr422_12: return "YCbCr422_12"; + case YCbCr422_12_CbYCrY: return "YCbCr422_12_CbYCrY"; + case YCbCr422_12p: return "YCbCr422_12p"; + case YCbCr422_12p_CbYCrY: return "YCbCr422_12p_CbYCrY"; + case YCbCr601_8_CbYCr: return "YCbCr601_8_CbYCr"; + case YCbCr601_10_CbYCr: return "YCbCr601_10_CbYCr"; + case YCbCr601_10p_CbYCr: return "YCbCr601_10p_CbYCr"; + case YCbCr601_12_CbYCr: return "YCbCr601_12_CbYCr"; + case YCbCr601_12p_CbYCr: return "YCbCr601_12p_CbYCr"; + case YCbCr601_411_8_CbYYCrYY: return "YCbCr601_411_8_CbYYCrYY"; + case YCbCr601_422_8: return "YCbCr601_422_8"; + case YCbCr601_422_8_CbYCrY: return "YCbCr601_422_8_CbYCrY"; + case YCbCr601_422_10: return "YCbCr601_422_10"; + case YCbCr601_422_10_CbYCrY: return "YCbCr601_422_10_CbYCrY"; + case YCbCr601_422_10p: return "YCbCr601_422_10p"; + case YCbCr601_422_10p_CbYCrY: return "YCbCr601_422_10p_CbYCrY"; + case YCbCr601_422_12: return "YCbCr601_422_12"; + case YCbCr601_422_12_CbYCrY: return "YCbCr601_422_12_CbYCrY"; + case YCbCr601_422_12p: return "YCbCr601_422_12p"; + case YCbCr601_422_12p_CbYCrY: return "YCbCr601_422_12p_CbYCrY"; + case YCbCr709_8_CbYCr: return "YCbCr709_8_CbYCr"; + case YCbCr709_10_CbYCr: return "YCbCr709_10_CbYCr"; + case YCbCr709_10p_CbYCr: return "YCbCr709_10p_CbYCr"; + case YCbCr709_12_CbYCr: return "YCbCr709_12_CbYCr"; + case YCbCr709_12p_CbYCr: return "YCbCr709_12p_CbYCr"; + case YCbCr709_411_8_CbYYCrYY: return "YCbCr709_411_8_CbYYCrYY"; + case YCbCr709_422_8: return "YCbCr709_422_8"; + case YCbCr709_422_8_CbYCrY: return "YCbCr709_422_8_CbYCrY"; + case YCbCr709_422_10: return "YCbCr709_422_10"; + case YCbCr709_422_10_CbYCrY: return "YCbCr709_422_10_CbYCrY"; + case YCbCr709_422_10p: return "YCbCr709_422_10p"; + case YCbCr709_422_10p_CbYCrY: return "YCbCr709_422_10p_CbYCrY"; + case YCbCr709_422_12: return "YCbCr709_422_12"; + case YCbCr709_422_12_CbYCrY: return "YCbCr709_422_12_CbYCrY"; + case YCbCr709_422_12p: return "YCbCr709_422_12p"; + case YCbCr709_422_12p_CbYCrY: return "YCbCr709_422_12p_CbYCrY"; + case YCbCr2020_8_CbYCr: return "YCbCr2020_8_CbYCr"; + case YCbCr2020_10_CbYCr: return "YCbCr2020_10_CbYCr"; + case YCbCr2020_10p_CbYCr: return "YCbCr2020_10p_CbYCr"; + case YCbCr2020_12_CbYCr: return "YCbCr2020_12_CbYCr"; + case YCbCr2020_12p_CbYCr: return "YCbCr2020_12p_CbYCr"; + case YCbCr2020_411_8_CbYYCrYY: return "YCbCr2020_411_8_CbYYCrYY"; + case YCbCr2020_422_8: return "YCbCr2020_422_8"; + case YCbCr2020_422_8_CbYCrY: return "YCbCr2020_422_8_CbYCrY"; + case YCbCr2020_422_10: return "YCbCr2020_422_10"; + case YCbCr2020_422_10_CbYCrY: return "YCbCr2020_422_10_CbYCrY"; + case YCbCr2020_422_10p: return "YCbCr2020_422_10p"; + case YCbCr2020_422_10p_CbYCrY: return "YCbCr2020_422_10p_CbYCrY"; + case YCbCr2020_422_12: return "YCbCr2020_422_12"; + case YCbCr2020_422_12_CbYCrY: return "YCbCr2020_422_12_CbYCrY"; + case YCbCr2020_422_12p: return "YCbCr2020_422_12p"; + case YCbCr2020_422_12p_CbYCrY: return "YCbCr2020_422_12p_CbYCrY"; + case YUV8_UYV: return "YUV8_UYV"; + case YUV411_8_UYYVYY: return "YUV411_8_UYYVYY"; + case YUV422_8: return "YUV422_8"; + case YUV422_8_UYVY: return "YUV422_8_UYVY"; + case Mono10Packed: return "Mono10Packed"; + case Mono12Packed: return "Mono12Packed"; + case BayerBG10Packed: return "BayerBG10Packed"; + case BayerBG12Packed: return "BayerBG12Packed"; + case BayerGB10Packed: return "BayerGB10Packed"; + case BayerGB12Packed: return "BayerGB12Packed"; + case BayerGR10Packed: return "BayerGR10Packed"; + case BayerGR12Packed: return "BayerGR12Packed"; + case BayerRG10Packed: return "BayerRG10Packed"; + case BayerRG12Packed: return "BayerRG12Packed"; + case RGB10V1Packed: return "RGB10V1Packed"; + case RGB12V1Packed: return "RGB12V1Packed"; + + case InvalidPixelFormat: return "InvalidPixelFormat"; + + default: return "UnknownPixelFormat"; + } +} +static PFNC_INLINE const char* GetPixelFormatDescription(PfncFormat format) +{ + switch (format) + { + case Mono1p: return "Monochrome 1-bit packed"; + case Mono2p: return "Monochrome 2-bit packed"; + case Mono4p: return "Monochrome 4-bit packed"; + case Mono8: return "Monochrome 8-bit"; + case Mono8s: return "Monochrome 8-bit signed"; + case Mono10: return "Monochrome 10-bit unpacked"; + case Mono10p: return "Monochrome 10-bit packed"; + case Mono12: return "Monochrome 12-bit unpacked"; + case Mono12p: return "Monochrome 12-bit packed"; + case Mono14: return "Monochrome 14-bit unpacked"; + case Mono16: return "Monochrome 16-bit"; + case BayerBG8: return "Bayer Blue-Green 8-bit"; + case BayerBG10: return "Bayer Blue-Green 10-bit unpacked"; + case BayerBG10p: return "Bayer Blue-Green 10-bit packed"; + case BayerBG12: return "Bayer Blue-Green 12-bit unpacked"; + case BayerBG12p: return "Bayer Blue-Green 12-bit packed"; + case BayerBG16: return "Bayer Blue-Green 16-bit"; + case BayerGB8: return "Bayer Green-Blue 8-bit"; + case BayerGB10: return "Bayer Green-Blue 10-bit unpacked"; + case BayerGB10p: return "Bayer Green-Blue 10-bit packed"; + case BayerGB12: return "Bayer Green-Blue 12-bit unpacked"; + case BayerGB12p: return "Bayer Green-Blue 12-bit packed"; + case BayerGB16: return "Bayer Green-Blue 16-bit"; + case BayerGR8: return "Bayer Green-Red 8-bit"; + case BayerGR10: return "Bayer Green-Red 10-bit unpacked"; + case BayerGR10p: return "Bayer Green-Red 10-bit packed"; + case BayerGR12: return "Bayer Green-Red 12-bit unpacked"; + case BayerGR12p: return "Bayer Green-Red 12-bit packed"; + case BayerGR16: return "Bayer Green-Red 16-bit"; + case BayerRG8: return "Bayer Red-Green 8-bit"; + case BayerRG10: return "Bayer Red-Green 10-bit unpacked"; + case BayerRG10p: return "Bayer Red-Green 10-bit packed"; + case BayerRG12: return "Bayer Red-Green 12-bit unpacked"; + case BayerRG12p: return "Bayer Red-Green 12-bit packed"; + case BayerRG16: return "Bayer Red-Green 16-bit"; + case RGBa8: return "Red-Green-Blue-alpha 8-bit"; + case RGBa10: return "Red-Green-Blue-alpha 10-bit unpacked"; + case RGBa10p: return "Red-Green-Blue-alpha 10-bit packed"; + case RGBa12: return "Red-Green-Blue-alpha 12-bit unpacked"; + case RGBa12p: return "Red-Green-Blue-alpha 12-bit packed"; + case RGBa14: return "Red-Green-Blue-alpha 14-bit unpacked"; + case RGBa16: return "Red-Green-Blue-alpha 16-bit"; + case RGB8: return "Red-Green-Blue 8-bit"; + case RGB8_Planar: return "Red-Green-Blue 8-bit planar"; + case RGB10: return "Red-Green-Blue 10-bit unpacked"; + case RGB10_Planar: return "Red-Green-Blue 10-bit unpacked planar"; + case RGB10p: return "Red-Green-Blue 10-bit packed"; + case RGB10p32: return "Red-Green-Blue 10-bit packed into 32-bit"; + case RGB12: return "Red-Green-Blue 12-bit unpacked"; + case RGB12_Planar: return "Red-Green-Blue 12-bit unpacked planar"; + case RGB12p: return "Red-Green-Blue 12-bit packed "; + case RGB14: return "Red-Green-Blue 14-bit unpacked"; + case RGB16: return "Red-Green-Blue 16-bit "; + case RGB16_Planar: return "Red-Green-Blue 16-bit planar"; + case RGB565p: return "Red-Green-Blue 5/6/5-bit packed"; + case BGRa8: return "Blue-Green-Red-alpha 8-bit"; + case BGRa10: return "Blue-Green-Red-alpha 10-bit unpacked"; + case BGRa10p: return "Blue-Green-Red-alpha 10-bit packed"; + case BGRa12: return "Blue-Green-Red-alpha 12-bit unpacked"; + case BGRa12p: return "Blue-Green-Red-alpha 12-bit packed"; + case BGRa14: return "Blue-Green-Red-alpha 14-bit unpacked"; + case BGRa16: return "Blue-Green-Red-alpha 16-bit"; + case BGR8: return "Blue-Green-Red 8-bit"; + case BGR10: return "Blue-Green-Red 10-bit unpacked"; + case BGR10p: return "Blue-Green-Red 10-bit packed"; + case BGR12: return "Blue-Green-Red 12-bit unpacked"; + case BGR12p: return "Blue-Green-Red 12-bit packed"; + case BGR14: return "Blue-Green-Red 14-bit unpacked"; + case BGR16: return "Blue-Green-Red 16-bit"; + case BGR565p: return "Blue-Green-Red 5/6/5-bit packed"; + case R8: return "Red 8-bit"; + case R10: return "Red 10-bit"; + case R12: return "Red 12-bit"; + case R16: return "Red 16-bit"; + case G8: return "Green 8-bit"; + case G10: return "Green 10-bit"; + case G12: return "Green 12-bit"; + case G16: return "Green 16-bit"; + case B8: return "Blue 8-bit"; + case B10: return "Blue 10-bit"; + case B12: return "Blue 12-bit"; + case B16: return "Blue 16-bit"; + case Coord3D_ABC8: return "3D coordinate A-B-C 8-bit"; + case Coord3D_ABC8_Planar: return "3D coordinate A-B-C 8-bit planar"; + case Coord3D_ABC10p: return "3D coordinate A-B-C 10-bit packed"; + case Coord3D_ABC10p_Planar: return "3D coordinate A-B-C 10-bit packed planar"; + case Coord3D_ABC12p: return "3D coordinate A-B-C 12-bit packed"; + case Coord3D_ABC12p_Planar: return "3D coordinate A-B-C 12-bit packed planar"; + case Coord3D_ABC16: return "3D coordinate A-B-C 16-bit"; + case Coord3D_ABC16_Planar: return "3D coordinate A-B-C 16-bit planar"; + case Coord3D_ABC32f: return "3D coordinate A-B-C 32-bit floating point"; + case Coord3D_ABC32f_Planar: return "3D coordinate A-B-C 32-bit floating point planar"; + case Coord3D_AC8: return "3D coordinate A-C 8-bit"; + case Coord3D_AC8_Planar: return "3D coordinate A-C 8-bit planar"; + case Coord3D_AC10p: return "3D coordinate A-C 10-bit packed"; + case Coord3D_AC10p_Planar: return "3D coordinate A-C 10-bit packed planar"; + case Coord3D_AC12p: return "3D coordinate A-C 12-bit packed"; + case Coord3D_AC12p_Planar: return "3D coordinate A-C 12-bit packed planar"; + case Coord3D_AC16: return "3D coordinate A-C 16-bit"; + case Coord3D_AC16_Planar: return "3D coordinate A-C 16-bit planar"; + case Coord3D_AC32f: return "3D coordinate A-C 32-bit floating point"; + case Coord3D_AC32f_Planar: return "3D coordinate A-C 32-bit floating point planar"; + case Coord3D_A8: return "3D coordinate A 8-bit"; + case Coord3D_A10p: return "3D coordinate A 10-bit packed"; + case Coord3D_A12p: return "3D coordinate A 12-bit packed"; + case Coord3D_A16: return "3D coordinate A 16-bit"; + case Coord3D_A32f: return "3D coordinate A 32-bit floating point"; + case Coord3D_B8: return "3D coordinate B 8-bit"; + case Coord3D_B10p: return "3D coordinate B 10-bit packed"; + case Coord3D_B12p: return "3D coordinate B 12-bit packed"; + case Coord3D_B16: return "3D coordinate B 16-bit"; + case Coord3D_B32f: return "3D coordinate B 32-bit floating point"; + case Coord3D_C8: return "3D coordinate C 8-bit"; + case Coord3D_C10p: return "3D coordinate C 10-bit packed"; + case Coord3D_C12p: return "3D coordinate C 12-bit packed"; + case Coord3D_C16: return "3D coordinate C 16-bit"; + case Coord3D_C32f: return "3D coordinate C 32-bit floating point"; + case Confidence1: return "Confidence 1-bit unpacked"; + case Confidence1p: return "Confidence 1-bit packed"; + case Confidence8: return "Confidence 8-bit"; + case Confidence16: return "Confidence 16-bit"; + case Confidence32f: return "Confidence 32-bit floating point"; + case BiColorBGRG8: return "Bi-color Blue/Green - Red/Green 8-bit"; + case BiColorBGRG10: return "Bi-color Blue/Green - Red/Green 10-bit unpacked"; + case BiColorBGRG10p: return "Bi-color Blue/Green - Red/Green 10-bit packed"; + case BiColorBGRG12: return "Bi-color Blue/Green - Red/Green 12-bit unpacked"; + case BiColorBGRG12p: return "Bi-color Blue/Green - Red/Green 12-bit packed"; + case BiColorRGBG8: return "Bi-color Red/Green - Blue/Green 8-bit"; + case BiColorRGBG10: return "Bi-color Red/Green - Blue/Green 10-bit unpacked"; + case BiColorRGBG10p: return "Bi-color Red/Green - Blue/Green 10-bit packed"; + case BiColorRGBG12: return "Bi-color Red/Green - Blue/Green 12-bit unpacked"; + case BiColorRGBG12p: return "Bi-color Red/Green - Blue/Green 12-bit packed"; + case SCF1WBWG8: return "Sparse Color Filter #1 White-Blue-White-Green 8-bit"; + case SCF1WBWG10: return "Sparse Color Filter #1 White-Blue-White-Green 10-bit unpacked"; + case SCF1WBWG10p: return "Sparse Color Filter #1 White-Blue-White-Green 10-bit packed"; + case SCF1WBWG12: return "Sparse Color Filter #1 White-Blue-White-Green 12-bit unpacked"; + case SCF1WBWG12p: return "Sparse Color Filter #1 White-Blue-White-Green 12-bit packed"; + case SCF1WBWG14: return "Sparse Color Filter #1 White-Blue-White-Green 14-bit unpacked"; + case SCF1WBWG16: return "Sparse Color Filter #1 White-Blue-White-Green 16-bit unpacked"; + case SCF1WGWB8: return "Sparse Color Filter #1 White-Green-White-Blue 8-bit"; + case SCF1WGWB10: return "Sparse Color Filter #1 White-Green-White-Blue 10-bit unpacked"; + case SCF1WGWB10p: return "Sparse Color Filter #1 White-Green-White-Blue 10-bit packed"; + case SCF1WGWB12: return "Sparse Color Filter #1 White-Green-White-Blue 12-bit unpacked"; + case SCF1WGWB12p: return "Sparse Color Filter #1 White-Green-White-Blue 12-bit packed"; + case SCF1WGWB14: return "Sparse Color Filter #1 White-Green-White-Blue 14-bit unpacked"; + case SCF1WGWB16: return "Sparse Color Filter #1 White-Green-White-Blue 16-bit"; + case SCF1WGWR8: return "Sparse Color Filter #1 White-Green-White-Red 8-bit"; + case SCF1WGWR10: return "Sparse Color Filter #1 White-Green-White-Red 10-bit unpacked"; + case SCF1WGWR10p: return "Sparse Color Filter #1 White-Green-White-Red 10-bit packed"; + case SCF1WGWR12: return "Sparse Color Filter #1 White-Green-White-Red 12-bit unpacked"; + case SCF1WGWR12p: return "Sparse Color Filter #1 White-Green-White-Red 12-bit packed"; + case SCF1WGWR14: return "Sparse Color Filter #1 White-Green-White-Red 14-bit unpacked"; + case SCF1WGWR16: return "Sparse Color Filter #1 White-Green-White-Red 16-bit"; + case SCF1WRWG8: return "Sparse Color Filter #1 White-Red-White-Green 8-bit"; + case SCF1WRWG10: return "Sparse Color Filter #1 White-Red-White-Green 10-bit unpacked"; + case SCF1WRWG10p: return "Sparse Color Filter #1 White-Red-White-Green 10-bit packed"; + case SCF1WRWG12: return "Sparse Color Filter #1 White-Red-White-Green 12-bit unpacked"; + case SCF1WRWG12p: return "Sparse Color Filter #1 White-Red-White-Green 12-bit packed"; + case SCF1WRWG14: return "Sparse Color Filter #1 White-Red-White-Green 14-bit unpacked"; + case SCF1WRWG16: return "Sparse Color Filter #1 White-Red-White-Green 16-bit"; + case YCbCr8: return "YCbCr 4:4:4 8-bit"; + case YCbCr8_CbYCr: return "YCbCr 4:4:4 8-bit"; + case YCbCr10_CbYCr: return "YCbCr 4:4:4 10-bit unpacked"; + case YCbCr10p_CbYCr: return "YCbCr 4:4:4 10-bit packed"; + case YCbCr12_CbYCr: return "YCbCr 4:4:4 12-bit unpacked"; + case YCbCr12p_CbYCr: return "YCbCr 4:4:4 12-bit packed"; + case YCbCr411_8: return "YCbCr 4:1:1 8-bit"; + case YCbCr411_8_CbYYCrYY: return "YCbCr 4:1:1 8-bit"; + case YCbCr422_8: return "YCbCr 4:2:2 8-bit"; + case YCbCr422_8_CbYCrY: return "YCbCr 4:2:2 8-bit"; + case YCbCr422_10: return "YCbCr 4:2:2 10-bit unpacked"; + case YCbCr422_10_CbYCrY: return "YCbCr 4:2:2 10-bit unpacked"; + case YCbCr422_10p: return "YCbCr 4:2:2 10-bit packed"; + case YCbCr422_10p_CbYCrY: return "YCbCr 4:2:2 10-bit packed"; + case YCbCr422_12: return "YCbCr 4:2:2 12-bit unpacked"; + case YCbCr422_12_CbYCrY: return "YCbCr 4:2:2 12-bit unpacked"; + case YCbCr422_12p: return "YCbCr 4:2:2 12-bit packed"; + case YCbCr422_12p_CbYCrY: return "YCbCr 4:2:2 12-bit packed"; + case YCbCr601_8_CbYCr: return "YCbCr 4:4:4 8-bit BT.601"; + case YCbCr601_10_CbYCr: return "YCbCr 4:4:4 10-bit unpacked BT.601"; + case YCbCr601_10p_CbYCr: return "YCbCr 4:4:4 10-bit packed BT.601"; + case YCbCr601_12_CbYCr: return "YCbCr 4:4:4 12-bit unpacked BT.601"; + case YCbCr601_12p_CbYCr: return "YCbCr 4:4:4 12-bit packed BT.601"; + case YCbCr601_411_8_CbYYCrYY: return "YCbCr 4:1:1 8-bit BT.601"; + case YCbCr601_422_8: return "YCbCr 4:2:2 8-bit BT.601"; + case YCbCr601_422_8_CbYCrY: return "YCbCr 4:2:2 8-bit BT.601"; + case YCbCr601_422_10: return "YCbCr 4:2:2 10-bit unpacked BT.601"; + case YCbCr601_422_10_CbYCrY: return "YCbCr 4:2:2 10-bit unpacked BT.601"; + case YCbCr601_422_10p: return "YCbCr 4:2:2 10-bit packed BT.601"; + case YCbCr601_422_10p_CbYCrY: return "YCbCr 4:2:2 10-bit packed BT.601"; + case YCbCr601_422_12: return "YCbCr 4:2:2 12-bit unpacked BT.601"; + case YCbCr601_422_12_CbYCrY: return "YCbCr 4:2:2 12-bit unpacked BT.601"; + case YCbCr601_422_12p: return "YCbCr 4:2:2 12-bit packed BT.601"; + case YCbCr601_422_12p_CbYCrY: return "YCbCr 4:2:2 12-bit packed BT.601"; + case YCbCr709_8_CbYCr: return "YCbCr 4:4:4 8-bit BT.709"; + case YCbCr709_10_CbYCr: return "YCbCr 4:4:4 10-bit unpacked BT.709"; + case YCbCr709_10p_CbYCr: return "YCbCr 4:4:4 10-bit packed BT.709"; + case YCbCr709_12_CbYCr: return "YCbCr 4:4:4 12-bit unpacked BT.709"; + case YCbCr709_12p_CbYCr: return "YCbCr 4:4:4 12-bit packed BT.709"; + case YCbCr709_411_8_CbYYCrYY: return "YCbCr 4:1:1 8-bit BT.709"; + case YCbCr709_422_8: return "YCbCr 4:2:2 8-bit BT.709"; + case YCbCr709_422_8_CbYCrY: return "YCbCr 4:2:2 8-bit BT.709"; + case YCbCr709_422_10: return "YCbCr 4:2:2 10-bit unpacked BT.709"; + case YCbCr709_422_10_CbYCrY: return "YCbCr 4:2:2 10-bit unpacked BT.709"; + case YCbCr709_422_10p: return "YCbCr 4:2:2 10-bit packed BT.709"; + case YCbCr709_422_10p_CbYCrY: return "YCbCr 4:2:2 10-bit packed BT.709"; + case YCbCr709_422_12: return "YCbCr 4:2:2 12-bit unpacked BT.709"; + case YCbCr709_422_12_CbYCrY: return "YCbCr 4:2:2 12-bit unpacked BT.709"; + case YCbCr709_422_12p: return "YCbCr 4:2:2 12-bit packed BT.709"; + case YCbCr709_422_12p_CbYCrY: return "YCbCr 4:2:2 12-bit packed BT.709"; + case YCbCr2020_8_CbYCr: return "YCbCr 4:4:4 8-bit BT.2020"; + case YCbCr2020_10_CbYCr: return "YCbCr 4:4:4 10-bit unpacked BT.2020"; + case YCbCr2020_10p_CbYCr: return "YCbCr 4:4:4 10-bit packed BT.2020"; + case YCbCr2020_12_CbYCr: return "YCbCr 4:4:4 12-bit unpacked BT.2020 "; + case YCbCr2020_12p_CbYCr: return "YCbCr 4:4:4 12-bit packed BT.2020"; + case YCbCr2020_411_8_CbYYCrYY: return "YCbCr 4:1:1 8-bit BT.2020"; + case YCbCr2020_422_8: return "YCbCr 4:2:2 8-bit BT.2020 "; + case YCbCr2020_422_8_CbYCrY: return "YCbCr 4:2:2 8-bit BT.2020"; + case YCbCr2020_422_10: return "YCbCr 4:2:2 10-bit unpacked BT.2020"; + case YCbCr2020_422_10_CbYCrY: return "YCbCr 4:2:2 10-bit unpacked BT.2020"; + case YCbCr2020_422_10p: return "YCbCr 4:2:2 10-bit packed BT.2020"; + case YCbCr2020_422_10p_CbYCrY: return "YCbCr 4:2:2 10-bit packed BT.2020"; + case YCbCr2020_422_12: return "YCbCr 4:2:2 12-bit unpacked BT.2020"; + case YCbCr2020_422_12_CbYCrY: return "YCbCr 4:2:2 12-bit unpacked BT.2020"; + case YCbCr2020_422_12p: return "YCbCr 4:2:2 12-bit packed BT.2020"; + case YCbCr2020_422_12p_CbYCrY: return "YCbCr 4:2:2 12-bit packed BT.2020"; + case YUV8_UYV: return "YUV 4:4:4 8-bit"; + case YUV411_8_UYYVYY: return "YUV 4:1:1 8-bit"; + case YUV422_8: return "YUV 4:2:2 8-bit"; + case YUV422_8_UYVY: return "YUV 4:2:2 8-bit"; + case Mono10Packed: return "Monochrome 10-bit packed"; + case Mono12Packed: return "Monochrome 12-bit packed"; + case BayerBG10Packed: return "Bayer Blue-Green 10-bit packed"; + case BayerBG12Packed: return "Bayer Blue-Green 12-bit packed"; + case BayerGB10Packed: return "Bayer Green-Blue 10-bit packed"; + case BayerGB12Packed: return "Bayer Green-Blue 12-bit packed"; + case BayerGR10Packed: return "Bayer Green-Red 10-bit packed"; + case BayerGR12Packed: return "Bayer Green-Red 12-bit packed"; + case BayerRG10Packed: return "Bayer Red-Green 10-bit packed"; + case BayerRG12Packed: return "Bayer Red-Green 12-bit packed"; + case RGB10V1Packed: return "Red-Green-Blue 10-bit packed - variant 1"; + case RGB12V1Packed: return "Red-Green-Blue 12-bit packed - variant 1"; + + case InvalidPixelFormat: return "Invalid pixel format value"; + + default: return "Unknown pixel format value"; + } +} +#endif /* PFNC_INCLUDE_HELPERS */ + + +#endif /* PFNC_H */ \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/PFNCCustom.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/PFNCCustom.h new file mode 100644 index 00000000..4745feae --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/PFNCCustom.h @@ -0,0 +1,41 @@ +/*************************************************************************************** + *** *** + *** Copyright (c) 2018, Lucid Vision Labs, Inc. *** + *** *** + *** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *** + *** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *** + *** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *** + *** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *** + *** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *** + *** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *** + *** SOFTWARE. *** + *** *** + ***************************************************************************************/ +#pragma once + +namespace Arena +{ + /** + * @fn size_t GetBitsPerPixel(uint64_t pixelFormat) + * + * @param pixelFormat + * - Type: uint64_t + * - Represents: PfncFormat + * - Pixel format to get bits per pixel + * + * @return + * - Type: size_t + * - Number of bits per pixel for given pixel format + * + * GetBitsPerPixel get the number of bits per pixel of the image from + * the integer value of the pixel format (PfncFormat). + * + * Pixel format values are determined by the PFNC (Pixel Format Naming + * Convention) specification. The PFNC assigns a name and number to each pixel + * format helping to standardize pixel formats. The number of bits per pixel + * can be found in each integer at bytes 5 and 6 (mask 0x00FF0000). The pixel + * format can be determined by the integer using the GetPixelFormatName + * function provided by the PFNC. + */ + ARENA_API size_t GetBitsPerPixel(uint64_t pixelFormat); +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/Port.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/Port.h new file mode 100644 index 00000000..cd1f204a --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/Port.h @@ -0,0 +1,26 @@ +#pragma once + +#include "GTestAPI.h" + +namespace Arena +{ + class TLBase; + class ARENA_TEST_API Port : public GenApi::IPort + { + public: + Port(TLBase* pPort); + virtual ~Port(); + Port(); + + //! Reads a chunk of bytes from the port + virtual void Read(void* pBuffer, int64_t Address, int64_t Length); + + //! Writes a chunk of bytes to the port + virtual void Write(const void* pBuffer, int64_t Address, int64_t Length); + + virtual GenApi::EAccessMode GetAccessMode() const; + + private: + TLBase* m_pPort; + }; +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/PrivateGlobals.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/PrivateGlobals.h new file mode 100644 index 00000000..78a6d152 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/PrivateGlobals.h @@ -0,0 +1,7 @@ +#pragma once + +namespace Arena +{ + void OpenImageFactory(); + void CloseImageFactory(); +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/ReadMe.txt b/catkin_ws/src/arena_camera/include/arena_camera/internal/ReadMe.txt new file mode 100644 index 00000000..21c7e817 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/ReadMe.txt @@ -0,0 +1,40 @@ +======================================================================== + DYNAMIC LINK LIBRARY : GenICapture Project Overview +======================================================================== + +AppWizard has created this GenICapture DLL for you. + +This file contains a summary of what you will find in each of the files that +make up your GenICapture application. + + +GenICapture.vcxproj + This is the main project file for VC++ projects generated using an Application Wizard. + It contains information about the version of Visual C++ that generated the file, and + information about the platforms, configurations, and project features selected with the + Application Wizard. + +GenICapture.vcxproj.filters + This is the filters file for VC++ projects generated using an Application Wizard. + It contains information about the association between the files in your project + and the filters. This association is used in the IDE to show grouping of files with + similar extensions under a specific node (for e.g. ".cpp" files are associated with the + "Source Files" filter). + +GenICapture.cpp + This is the main DLL source file. + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named GenICapture.pch and a precompiled types file named StdAfx.obj. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" comments to indicate parts of the source code you +should add to or customize. + +///////////////////////////////////////////////////////////////////////////// diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/StreamInfo.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/StreamInfo.h new file mode 100644 index 00000000..a35b34c0 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/StreamInfo.h @@ -0,0 +1,35 @@ +#pragma once +#include "TLDataStream.h" +#include "Port.h" + +namespace Arena +{ + class ARENA_TEST_API StreamInfo + { + public: + StreamInfo(TLDataStream* pStream); + virtual ~StreamInfo(); + + virtual TLDataStream* GetTLDataStream(); + virtual GenApi::INodeMap* GetNodeMap(); + +#if defined(_ARENA_UNIT_TEST_API) + StreamInfo(TLDataStream* pStream, GenApi::INodeMap* pNodeMap) : + m_pStream(pStream), + m_port(pStream), + m_pNodeMap(pNodeMap) + { + } + + void nullifyNodeMap() + { + m_pNodeMap = NULL; + } +#endif + + private: + TLDataStream* m_pStream; + Port m_port; + GenApi::INodeMap* m_pNodeMap; + }; +} diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/System.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/System.h new file mode 100644 index 00000000..33aeb19c --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/System.h @@ -0,0 +1,74 @@ +#pragma once +#include "ISystem.h" +#include "TLSystem.h" +#include "TLInterface.h" +#include "Interface.h" +#include "Port.h" +#include "PrivateGlobals.h" + +#include + +namespace Arena +{ + class ARENA_TEST_API System : public ISystem + { + public: + static ISystem* Open(); + static void Close(); + + System(TLSystem* system); + + virtual ~System(); + + virtual bool UpdateDevices(uint64_t timeout = 100); + + virtual bool UpdateDevices(InterfaceInfo ifaceInfo, uint64_t timeout = 100); + + virtual std::vector GetDevices(); + + virtual std::vector GetInterfaces(); + + virtual IDevice* CreateDevice(DeviceInfo info); + + virtual void DestroyDevice(IDevice* pDevice); + + virtual GenApi::INodeMap* GetTLSystemNodeMap(); + + virtual GenApi::INodeMap* GetTLInterfaceNodeMap(DeviceInfo devInfo); + + virtual void ForceIp(uint64_t macAddress, uint64_t ipAddress, uint64_t subnetMask, uint64_t defaultGateway); + + // used in testing +#if defined(_ARENA_UNIT_TEST_API) + System(TLSystem* system, std::vector interfaces, GenApi::INodeMap* pNodeMap) : + m_pSystem(system), + m_interfaces(interfaces), + m_pSystemNodeMap(pNodeMap), + m_pPort(system) + { + OpenImageFactory(); + } + + void nullifyNodeMap() + { + m_pSystemNodeMap = NULL; + } +#endif + + private: + System(); + + TLSystem* m_pSystem; + std::vector m_interfaces; + + GenApi::INodeMap* m_pSystemNodeMap; + Port m_pPort; + + //protects interface list from changing while accessing it + std::mutex m_ifaceListMtx; + + // protects updated devices from being called from two threads at same time + std::mutex m_updateDevsMtx; + + }; +} // namespace Arena diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/TLBase.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLBase.h new file mode 100644 index 00000000..a7bff757 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLBase.h @@ -0,0 +1,30 @@ +#pragma once + +#include "stdafx.h" + +namespace Arena +{ + class ARENA_TEST_API TLBase + { + public: + TLBase(); + TLBase(GenTL::PORT_HANDLE hModule); + virtual ~TLBase(); + + virtual GenTL::GC_ERROR ReadPort(uint64_t iAddress, void* pBuffer, size_t* piSize); + virtual GenTL::GC_ERROR WritePort(uint64_t iAddress, const void* pBuffer, size_t* piSize); + virtual GenTL::GC_ERROR GetPortURL(char* sURL, size_t* piSize); + virtual GenTL::GC_ERROR GetPortInfo(GenTL::PORT_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE* piType, void* pBuffer, size_t* piSize); + virtual GenTL::GC_ERROR RegisterEvent(GenTL::EVENT_TYPE iEventID, GenTL::EVENT_HANDLE* phEvent); + virtual GenTL::GC_ERROR UnregisterEvent(GenTL::EVENT_TYPE iEventID); + virtual GenTL::GC_ERROR GetNumPortURLs(uint32_t* piNumURLs); + virtual GenTL::GC_ERROR GetPortURLInfo(uint32_t iURLIndex, GenTL::URL_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE* piType, void* pBuffer, size_t* piSize); + virtual GenTL::GC_ERROR ReadPortStacked(GenTL::PORT_REGISTER_STACK_ENTRY* pEntries, size_t* piNumEntries); + virtual GenTL::GC_ERROR WritePortStacked(GenTL::PORT_REGISTER_STACK_ENTRY* pEntries, size_t* piNumEntries); + + virtual GenTL::PORT_HANDLE GetHandle(); + + protected: + GenTL::PORT_HANDLE m_hModule; + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/TLDataStream.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLDataStream.h new file mode 100644 index 00000000..f9acc260 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLDataStream.h @@ -0,0 +1,27 @@ +#pragma once +#include "stdafx.h" +#include "TLBase.h" + +namespace Arena +{ + class ARENA_TEST_API TLDataStream : public TLBase + { + public: + TLDataStream(GenTL::DS_HANDLE hDataStream); + TLDataStream(); + ~TLDataStream(); + + virtual GenTL::GC_ERROR Close(); + virtual GenTL::GC_ERROR AnnounceBuffer(void* pBuffer, size_t iSize, void* pPrivate, GenTL::BUFFER_HANDLE* phBuffer); + virtual GenTL::GC_ERROR AllocAndAnnounceBuffer(size_t iSize, void* pPrivate, GenTL::BUFFER_HANDLE* phBuffer); + virtual GenTL::GC_ERROR QueueBuffer(GenTL::BUFFER_HANDLE hBuffer); + virtual GenTL::GC_ERROR RevokeBuffer(GenTL::BUFFER_HANDLE hBuffer, void** pBuffer, void** pPrivate); + virtual GenTL::GC_ERROR FlushQueue(GenTL::ACQ_QUEUE_TYPE iOperation); + virtual GenTL::GC_ERROR GetInfo(GenTL::STREAM_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE* piType, void* pBuffer, size_t* piSize); + virtual GenTL::GC_ERROR GetBufferID(uint32_t iIndex, GenTL::BUFFER_HANDLE* phBuffer); + virtual GenTL::GC_ERROR GetBufferInfo(GenTL::BUFFER_HANDLE hBuffer, GenTL::BUFFER_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE* piType, void* pBuffer, size_t* piSize); + virtual GenTL::GC_ERROR StartAcquisition(GenTL::ACQ_START_FLAGS iStartFlags, uint64_t iNumToAcquire); + virtual GenTL::GC_ERROR StopAcquisition(GenTL::ACQ_STOP_FLAGS iStopFlags); + virtual GenTL::GC_ERROR GetBufferChunkData(GenTL::BUFFER_HANDLE hBuffer, GenTL::SINGLE_CHUNK_DATA* pChunkData, size_t* piNumChunks); + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/TLDevice.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLDevice.h new file mode 100644 index 00000000..35f88905 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLDevice.h @@ -0,0 +1,23 @@ +#pragma once +#include "stdafx.h" +#include "TLBase.h" + +namespace Arena +{ + class ARENA_TEST_API TLDevice : public TLBase + { + public: + TLDevice(GenTL::DEV_HANDLE hDevice); + TLDevice(); + ~TLDevice(); + + virtual GenTL::GC_ERROR Close(); + virtual GenTL::GC_ERROR GetInfo(GenTL::DEVICE_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE* piType, void* pBuffer, size_t* piSize); + virtual GenTL::GC_ERROR GetPort(GenTL::PORT_HANDLE* phRemoteDevice); + virtual GenTL::GC_ERROR GetNumDataStreams(uint32_t* piNumDataStreams); + virtual GenTL::GC_ERROR GetDataStreamID(uint32_t iIndex, char* sDataStreamID, size_t* piSize); + virtual GenTL::GC_ERROR OpenDataStream(const char* sDataStreamID, GenTL::DS_HANDLE* phDataStream); + + virtual GenTL::GC_ERROR SendActionCommand(uint32_t deviceKey, uint32_t groupKey, uint32_t groupMask, uint64_t actionTime); + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/TLEvent.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLEvent.h new file mode 100644 index 00000000..867bc200 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLEvent.h @@ -0,0 +1,20 @@ +#pragma once +#include "stdafx.h" +#include "TLBase.h" + +namespace Arena +{ + class ARENA_TEST_API TLEvent : public TLBase + { + public: + TLEvent(GenTL::EVENT_HANDLE hDevice); + TLEvent(); + ~TLEvent(); + + virtual GenTL::GC_ERROR GetData(void* pBuffer, size_t* piSize, uint64_t iTimeout); + virtual GenTL::GC_ERROR GetDataInfo(const void* pInBuffer, size_t iInSize, GenTL::EVENT_DATA_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE* piType, void* pOutBuffer, size_t* piOutSize); + virtual GenTL::GC_ERROR GetInfo(GenTL::EVENT_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE* piType, void* pBuffer, size_t* piSize); + virtual GenTL::GC_ERROR Flush(); + virtual GenTL::GC_ERROR Kill(); + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/TLInterface.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLInterface.h new file mode 100644 index 00000000..7588dbc9 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLInterface.h @@ -0,0 +1,25 @@ +#pragma once +#include "stdafx.h" +#include "TLBase.h" + +namespace Arena +{ + class ARENA_TEST_API TLInterface : public TLBase + { + public: + TLInterface(GenTL::IF_HANDLE hInterface); + TLInterface(); + virtual ~TLInterface(); + + //Interface operations + virtual GenTL::GC_ERROR Close(); + virtual GenTL::GC_ERROR UpdateDeviceList(bool8_t *pbChanged, uint64_t iTimeout); + virtual GenTL::GC_ERROR GetInfo(GenTL::INTERFACE_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + virtual GenTL::GC_ERROR GetNumDevices(uint32_t *piNumDevices); + virtual GenTL::GC_ERROR GetDeviceID(uint32_t iIndex, char *sIDeviceID, size_t *piSize); + virtual GenTL::GC_ERROR GetDeviceInfo(const char *sDeviceID, GenTL::DEVICE_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE *piType, void *pBuffer, size_t *piSize); + virtual GenTL::GC_ERROR OpenDevice(const char *sDeviceID, GenTL::DEVICE_ACCESS_FLAGS iOpenFlags, GenTL::DEV_HANDLE *phDevice); + virtual GenTL::GC_ERROR ForceIp(uint64_t mac, uint32_t ip, uint32_t subnet, uint32_t gateway); + + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/TLSystem.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLSystem.h new file mode 100644 index 00000000..17f29b58 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/TLSystem.h @@ -0,0 +1,23 @@ +#pragma once +#include "stdafx.h" +#include "TLBase.h" + +namespace Arena +{ + class ARENA_TEST_API TLSystem : public TLBase + { + public: + TLSystem(GenTL::TL_HANDLE hSystem); + TLSystem(); + virtual ~TLSystem(); + + virtual GenTL::GC_ERROR Open(); + virtual GenTL::GC_ERROR Close(); + virtual GenTL::GC_ERROR GetInfo(GenTL::TL_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE* piType, void* pBuffer, size_t* piSize); + virtual GenTL::GC_ERROR GetNumInterfaces(uint32_t* piNumIfaces); + virtual GenTL::GC_ERROR GetInterfaceID(uint32_t iIndex, char* sID, size_t* piSize); + virtual GenTL::GC_ERROR GetInterfaceInfo(const char* sIfaceID, GenTL::INTERFACE_INFO_CMD iInfoCmd, GenTL::INFO_DATATYPE* piType, void* pBuffer, size_t* piSize); + virtual GenTL::GC_ERROR OpenInterface(const char* sIfaceID, GenTL::IF_HANDLE* phIface); + virtual GenTL::GC_ERROR UpdateInterfaceList(bool8_t* pbChanged, uint64_t iTimeout); + }; +} \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/arena_camera.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/arena_camera.h new file mode 100644 index 00000000..e90fbfe5 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/arena_camera.h @@ -0,0 +1,186 @@ +/****************************************************************************** + * Software License Agreement (BSD License) + * + * Copyright (C) 2016, Magazino GmbH. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of Magazino GmbH nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#ifndef ARENA_CAMERA_INTERNAL_ARENA_CAMERA_H +#define ARENA_CAMERA_INTERNAL_ARENA_CAMERA_H + +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wliteral-suffix" + +#include +#include +#include +#include + +#include +#include + +namespace arena_camera +{ + +template +class ArenaCameraImpl : public ArenaCamera +{ +public: +explicit ArenaCameraImpl(); + +virtual ~ArenaCameraImpl(); + +virtual bool registerCameraConfiguration(); + +virtual bool openCamera(); + +virtual bool isCamRemoved(); + +virtual bool setupSequencer(const std::vector& exposure_times); + +virtual bool applyCamSpecificStartupSettings(const ArenaCameraParameter& parameters); + +virtual bool startGrabbing(const ArenaCameraParameter& parameters); + +virtual bool grab(std::vector& image); + +virtual bool grab(uint8_t* image); + +virtual bool setShutterMode(const arena_camera::SHUTTER_MODE& mode); + +virtual bool setROI(const sensor_msgs::RegionOfInterest target_roi, + sensor_msgs::RegionOfInterest& reached_roi); + +virtual bool setBinningX(const size_t& target_binning_x, + size_t& reached_binning_x); + +virtual bool setBinningY(const size_t& target_binning_y, + size_t& reached_binning_y); + +virtual bool setImageEncoding(const std::string& target_ros_encoding); + +virtual bool setExposure(const float& target_exposure, float& reached_exposure); + +virtual bool setAutoflash(const std::map flash_on_lines); + +virtual bool setGain(const float& target_gain, float& reached_gain); + +virtual bool setGamma(const float& target_gamma, float& reached_gamma); + +virtual bool setBrightness(const int& target_brightness, + const float& current_brightness, + const bool& exposure_auto, + const bool& gain_auto); + +virtual std::vector detectAndCountNumUserOutputs(); + +virtual bool setUserOutput(const int& output_id, const bool& value); + +virtual size_t currentOffsetX(); + +virtual size_t currentOffsetY(); + +virtual sensor_msgs::RegionOfInterest currentROI(); + +virtual size_t currentBinningX(); + +virtual size_t currentBinningY(); + +virtual std::vector detectAvailableImageEncodings(); + +virtual std::string currentROSEncoding() const; + +virtual int imagePixelDepth() const; + +virtual float currentExposure(); + +virtual float currentAutoExposureTimeLowerLimit(); + +virtual float currentAutoExposureTimeUpperLimit(); + +virtual float currentGain(); + +virtual float currentAutoGainLowerLimit(); + +virtual float currentAutoGainUpperLimit(); + +virtual float currentGamma(); + +virtual float maxPossibleFramerate(); + +virtual bool isArenaAutoBrightnessFunctionRunning(); + +virtual bool isBrightnessSearchRunning(); + +virtual void disableAllRunningAutoBrightessFunctions(); + +virtual void enableContinuousAutoExposure(); + +virtual void enableContinuousAutoGain(); + +virtual std::string typeName() const; + +virtual float exposureStep(); + +protected: +typedef typename CameraTraitT::ExposureAutoEnums ExposureAutoEnums; +typedef typename CameraTraitT::GainAutoEnums GainAutoEnums; +typedef typename CameraTraitT::PixelFormatEnums PixelFormatEnums; +typedef typename CameraTraitT::PixelSizeEnums PixelSizeEnums; +typedef typename CameraTraitT::AutoTargetBrightnessType AutoTargetBrightnessType; +typedef typename CameraTraitT::GainType GainType; +typedef typename CameraTraitT::ShutterModeEnums ShutterModeEnums; +typedef typename CameraTraitT::UserOutputSelectorEnums UserOutputSelectorEnums; + + +// Each camera has it's own getter for GenApi accessors that are named +// differently for USB and GigE +GenApi::IFloat& exposureTime(); +GainType& gain(); +GenApi::IFloat& gamma(); +GenApi::IFloat& autoExposureTimeLowerLimit(); +GenApi::IFloat& autoExposureTimeUpperLimit(); +GainType& autoGainLowerLimit(); +GainType& autoGainUpperLimit(); +GenApi::IFloat& resultingFrameRate(); +AutoTargetBrightnessType& autoTargetBrightness(); + +virtual bool setExtendedBrightness(const int& target_brightness, + const float& current_brightness); + +//virtual bool grab(Arena::CGrabResultPtr& grab_result); + +virtual bool setupSequencer(const std::vector& exposure_times, + std::vector& exposure_times_set); +}; + +} // namespace arena_camera + +// #include +// #include +// #include +// #include + +#endif // ARENA_CAMERA_INTERNAL_ARENA_CAMERA_H diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/libarena.version b/catkin_ws/src/arena_camera/include/arena_camera/internal/libarena.version new file mode 100644 index 00000000..6dadd444 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/libarena.version @@ -0,0 +1,4 @@ +{ + global: *; + local: *boost*; +}; \ No newline at end of file diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/makefile b/catkin_ws/src/arena_camera/include/arena_camera/internal/makefile new file mode 100644 index 00000000..53905be6 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/makefile @@ -0,0 +1,84 @@ +include ../common.mk + +OUTDIR = ../OutputDirectory/$(PLATFORM_NAME) + +VER_MAJ = $(shell grep -m 1 VER_MAJ ../makefile | sed 's/^.*=\s*//g') +VER_MIN = $(shell grep -m 1 VER_MIN ../makefile | sed 's/^.*=\s*//g') +VER_PATCH = $(shell grep -m 1 VER_PATCH ../makefile | sed 's/^.*=\s*//g') + +LIBNAME = libarena$(D).so +SONAME = ${LIBNAME}.${VER_MAJ} +TARGETLIB = ${SONAME}.${VER_MIN}.${VER_PATCH} + +INCLUDE= -Iinclude \ + -I../GenTL/include \ + $(INC_BOOST_PATH) \ + $(INC_GENICAM_PATH) \ + $(INC_IPP_PATH) \ + $(INC_OPENCV_PATH) + +CFLAGS=-Wall -Werror -std=c++11 -fPIC -Wno-unknown-pragmas $(PLATFORM_DEFINES) + +LDFLAGS = -shared \ + -Wl,--whole-archive \ + $(BOOST_PATH_ROOT)/$(BOOST_LIB_DIR_NAME)/libboost_thread.a \ + $(BOOST_PATH_ROOT)/$(BOOST_LIB_DIR_NAME)/libboost_system.a \ + -Wl,--no-whole-archive \ + -Wl,-Bstatic \ + $(LIB_IPP_STATIC) \ + -Wl,-Bdynamic \ + -Wl,-soname,$(SONAME) + +SRCS = $(wildcard *.cpp) +OBJS = $(SRCS:%.cpp=%.o) +DEPS = $(OBJS:%.o=%.d) + + +.PHONY: all +all: release debug debug_test + +.PHONY: release +release: CFLAGS += -O2 +release: XML_DIR = $(OUTDIR)/$(EXE_DIR_PREFIX)Release/xml +release: build_target + +.PHONY: debug +debug: D = d +debug: CFLAGS += -g -DDEBUG +debug: build_target + +.PHONY: debug_test +debug_test: D = d +debug_test: CFLAGS += -g -DDEBUG -D_ARENA_UNIT_TEST_API -D_EXPORT_ARENA_UNIT_TEST_API -DARENA_EXPORTS +debug_test: INCLUDE += $(INC_GMOCK_PATH) $(INC_GTEST_PATH) +debug_test: build_target + +.PHONY: build_target +build_target: $(OBJS) + ${CC} ${INCLUDE} -o $(TARGETLIB) $^ $(LDFLAGS) -Wl,--version-script=libarena.version + -ln -sf $(TARGETLIB) $(SONAME) + -ln -sf $(TARGETLIB) $(LIBNAME) + -mkdir -p $(OUTDIR)/$(LIB_DIR_NAME) + cp -d $(LIBNAME)* $(OUTDIR)/$(LIB_DIR_NAME) + -mkdir -p ../include/Arena + cp include/* ../include/Arena + +.PHONY: clean +clean: clean_rel clean_deb + +.PHONY: clean_rel +clean_rel: + -${RM} ${TARGETLIB} ${SONAME} ${LIBNAME} ${OBJS} ${DEPS} + +.PHONY: clean_deb +clean_deb: D = d +clean_deb: + -$(RM) $(TARGETLIB) $(SONAME) $(LIBNAME) $(OBJS) $(DEPS) + +%.o: %.cpp + ${CC} ${INCLUDE} -o $@ $< -c ${CFLAGS} + +${DEPS}: %.cpp + ${CC} ${CLAGS} ${INCLUDE} -MM $< >$@ + +-include $(OBJS:.o=.d) diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/resource.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/resource.h new file mode 100644 index 00000000..e34ead8a --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Arena.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/stdafx.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/stdafx.h new file mode 100644 index 00000000..177162b1 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/stdafx.h @@ -0,0 +1,25 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#if (defined _WIN32 || defined _WIN64) +#include +#include +#endif + + + +#include "Exceptions.h" +#include "GTestAPI.h" +#include "GenTL.h" +#include "GenTLCustom.h" +#include "ArenaApi.h" + +// TODO: reference additional headers your program requires here diff --git a/catkin_ws/src/arena_camera/include/arena_camera/internal/targetver.h b/catkin_ws/src/arena_camera/include/arena_camera/internal/targetver.h new file mode 100644 index 00000000..77c113e0 --- /dev/null +++ b/catkin_ws/src/arena_camera/include/arena_camera/internal/targetver.h @@ -0,0 +1,10 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#if (defined _WIN32 || defined _WIN64) +#include +#endif diff --git a/launch/pylon_camera_grab_and_save_as.launch b/catkin_ws/src/arena_camera/launch/arena_camera_grab_and_save_as.launch similarity index 61% rename from launch/pylon_camera_grab_and_save_as.launch rename to catkin_ws/src/arena_camera/launch/arena_camera_grab_and_save_as.launch index 706a80cb..82553a72 100644 --- a/launch/pylon_camera_grab_and_save_as.launch +++ b/catkin_ws/src/arena_camera/launch/arena_camera_grab_and_save_as.launch @@ -1,10 +1,10 @@ - - + + - diff --git a/launch/pylon_camera_node.launch b/catkin_ws/src/arena_camera/launch/arena_camera_node.launch similarity index 71% rename from launch/pylon_camera_node.launch rename to catkin_ws/src/arena_camera/launch/arena_camera_node.launch index 5dd06742..d7fb56bd 100644 --- a/launch/pylon_camera_node.launch +++ b/catkin_ws/src/arena_camera/launch/arena_camera_node.launch @@ -2,13 +2,13 @@ - - + + - diff --git a/catkin_ws/src/arena_camera/launch/arena_camera_node_multi_device_example.launch b/catkin_ws/src/arena_camera/launch/arena_camera_node_multi_device_example.launch new file mode 100644 index 00000000..7bb55b41 --- /dev/null +++ b/catkin_ws/src/arena_camera/launch/arena_camera_node_multi_device_example.launch @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/catkin_ws/src/arena_camera/package.xml b/catkin_ws/src/arena_camera/package.xml new file mode 100644 index 00000000..0804237b --- /dev/null +++ b/catkin_ws/src/arena_camera/package.xml @@ -0,0 +1,90 @@ + + + arena_camera + 0.0.0 + + Proprietary Package for Lucid Cameras using the the Arena API. + - Supports DART, USB3 and GigE cameras. + - Setting Gain, Gamma, Exposure, Binning and Brightness using Services. + + + + + + LUCID Vision Labs + support + + + + + + BSD + + + + + + http://wiki.ros.org/arena_camera + + + + + + + + + + + + + + + + + + + + + + + + + + + + + catkin + + actionlib + camera_control_msgs + camera_info_manager + cv_bridge + diagnostic_updater + image_geometry + image_transport + arena + roscpp + sensor_msgs + roslaunch + roslint + + actionlib + camera_control_msgs + camera_info_manager + diagnostic_updater + cv_bridge + image_geometry + image_transport + arena + roscpp + roslaunch + sensor_msgs + roslint + + + + + + + + diff --git a/catkin_ws/src/arena_camera/rosdep/arena_sdk.rdmanifest b/catkin_ws/src/arena_camera/rosdep/arena_sdk.rdmanifest new file mode 100644 index 00000000..948f1afb --- /dev/null +++ b/catkin_ws/src/arena_camera/rosdep/arena_sdk.rdmanifest @@ -0,0 +1,66 @@ +uri: 'https://raw.githubusercontent.com/magazino/arena_camera/indigo-devel/rosdep/empty.tar' +md5sum: df41600634ca08cb5082877eb64220c9 +install-script: | + #!/bin/bash + if ( [ "${ARENA_CONFIG_ROOT}" != "" ] && [ -d "${ARENA_CONFIG_ROOT}" ] ); then + MAJOR=`${ARENA_CONFIG_ROOT}/bin/arena-config --version-major` + if [ "$MAJOR" = "5" ]; then + exit 0 + else + echo "Found an existing Arena installation in ARENA_CONFIG_ROOT but the version is too low" + exit 1 + fi + fi + + arch="$(uname -m)" + if [ "$arch" == "armv7l" ]; then + pkgarch="armhf" + elif [ "$arch" == "aarch64" ]; then + pkgarch="arm64" + elif [ "$arch" == "arm64" ]; then + pkgarch="arm64" + elif [ "$arch" == "x86_64" ]; then + pkgarch="amd64" + else + pkgarch="i386" + fi + + version="5.0.11.10914" + pkg=arena_${version}-deb0_${pkgarch}.deb + url="https://www.baslerweb.com/media/downloads/software/arena_software/${pkg}" + + wget --no-check-certificate -O $pkg $url + sudo dpkg -i $pkg + sudo udevadm control --reload-rules || true + +check-presence-script: | + #!/bin/bash + if [ "${ARENA_CONFIG_ROOT}" = "" ]; then + find ~ -type f -name arena-config + FOUND=$? + if [ "$FOUND" -eq 0 ]; then + for arena_install in `find ~ -type f -name arena-config`; do + MAJOR=`${arena_install} --version-major` + if [ "$MAJOR" = "5" ]; then + echo "Found a arena Installation with version 5 or greater" + exit 0 + fi + done + fi + echo "Could not find any arena Installation with version 5 or greater" + exit 1 + else + if [ -d "${ARENA_CONFIG_ROOT}" ]; then + MAJOR=`${ARENA_CONFIG_ROOT}/bin/arena-config --version-major` + if [ "$MAJOR" = "5" ]; then + exit 0 + else + echo "Found an existing Arena installation in ARENA_CONFIG_ROOT but the version is too low" + exit 2 + fi + exit 0 + else + echo "ARENA_CONFIG_ROOT is set but folder does not exist." + exit 1 + fi + fi diff --git a/catkin_ws/src/arena_camera/rosdep/arena_sdk.yaml b/catkin_ws/src/arena_camera/rosdep/arena_sdk.yaml new file mode 100644 index 00000000..04dd3d27 --- /dev/null +++ b/catkin_ws/src/arena_camera/rosdep/arena_sdk.yaml @@ -0,0 +1,4 @@ +arena: + ubuntu: + source: + uri: "https://raw.githubusercontent.com/magazino/arena_camera/indigo-devel/rosdep/arena_sdk.rdmanifest" diff --git a/rosdep/empty.tar b/catkin_ws/src/arena_camera/rosdep/empty.tar similarity index 100% rename from rosdep/empty.tar rename to catkin_ws/src/arena_camera/rosdep/empty.tar diff --git a/scripts/file_sequencer.py b/catkin_ws/src/arena_camera/scripts/file_sequencer.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/file_sequencer.py rename to catkin_ws/src/arena_camera/scripts/file_sequencer.py diff --git a/scripts/grab_and_save_image_action_server.py b/catkin_ws/src/arena_camera/scripts/grab_and_save_image_action_server.py old mode 100755 new mode 100644 similarity index 97% rename from scripts/grab_and_save_image_action_server.py rename to catkin_ws/src/arena_camera/scripts/grab_and_save_image_action_server.py index 280df4a7..e7494b73 --- a/scripts/grab_and_save_image_action_server.py +++ b/catkin_ws/src/arena_camera/scripts/grab_and_save_image_action_server.py @@ -11,7 +11,7 @@ class GrabAndSaveImageActionServers(): """ This nodes provides action server that extend the 'grab_images_raw' and - 'grab_images_rect' action servers from the pylon_camera_node. + 'grab_images_rect' action servers from the arena_camera_node. The new action servers are named 'grab_and_save_imgage_raw' and 'grab_and_save_imgage_rect'. They have basically the same action goal as the 'GrabImagesAction' in case of grabbing only one image, but they extend @@ -22,9 +22,9 @@ class GrabAndSaveImageActionServers(): def __init__(self): camera_name = rospy.get_param('~camera_name', '') if not camera_name: - rospy.logwarn("No camera name given! Assuming 'pylon_camera_node' as" + rospy.logwarn("No camera name given! Assuming 'arena_camera_node' as" " camera name") - camera_name = '/pylon_camera_node' + camera_name = '/arena_camera_node' else: rospy.loginfo('Camera name is: ' + camera_name) diff --git a/scripts/individual_flash_test b/catkin_ws/src/arena_camera/scripts/individual_flash_test old mode 100755 new mode 100644 similarity index 97% rename from scripts/individual_flash_test rename to catkin_ws/src/arena_camera/scripts/individual_flash_test index d7af8b86..a1539064 --- a/scripts/individual_flash_test +++ b/catkin_ws/src/arena_camera/scripts/individual_flash_test @@ -15,7 +15,7 @@ rospy.init_node("flash_test") # todo? check if auto_flash is activated for camera -camera_name = rospy.get_param("~camera_name", "/pylon_camera_node") +camera_name = rospy.get_param("~camera_name", "/arena_camera_node") exposure = rospy.get_param("~exposure", 10000) img_topic = camera_name + '/image_raw' bridge = CvBridge() diff --git a/scripts/result_bag_to_action.py b/catkin_ws/src/arena_camera/scripts/result_bag_to_action.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/result_bag_to_action.py rename to catkin_ws/src/arena_camera/scripts/result_bag_to_action.py diff --git a/scripts/sequence_to_file.py b/catkin_ws/src/arena_camera/scripts/sequence_to_file.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/sequence_to_file.py rename to catkin_ws/src/arena_camera/scripts/sequence_to_file.py diff --git a/scripts/toggle_camera b/catkin_ws/src/arena_camera/scripts/toggle_camera similarity index 89% rename from scripts/toggle_camera rename to catkin_ws/src/arena_camera/scripts/toggle_camera index a24e5c2a..43d78e8e 100644 --- a/scripts/toggle_camera +++ b/catkin_ws/src/arena_camera/scripts/toggle_camera @@ -1,10 +1,10 @@ #!/usr/bin/env python # This script creates a ros node that looks for nodes that publish Image msgs and advertises a to a camera_control_msgs/SetSleeping service. -# If it does find any, it assumes it is a Pylon camera and sends it either to sleep or wakes it up, depending on argument. +# If it does find any, it assumes it is a Arena camera and sends it either to sleep or wakes it up, depending on argument. # Will turn on/off all cameras at the same time. Best used as an alias in bashrc: -# alias pylonon='rosrun pylon_camera toggle_camera 0' # turns cameras off -# alias pylonoff='rosrun pylon_camera toggle_camera 1' # turns cameras on +# alias arenaon='rosrun arena_camera toggle_camera 0' # turns cameras off +# alias arenaoff='rosrun arena_camera toggle_camera 1' # turns cameras on # # valid arguments: 1 wakes cameras, every other int sleeps cameras diff --git a/scripts/triggered_image_topic.py b/catkin_ws/src/arena_camera/scripts/triggered_image_topic.py old mode 100755 new mode 100644 similarity index 95% rename from scripts/triggered_image_topic.py rename to catkin_ws/src/arena_camera/scripts/triggered_image_topic.py index 291c1086..789f7a3c --- a/scripts/triggered_image_topic.py +++ b/catkin_ws/src/arena_camera/scripts/triggered_image_topic.py @@ -18,9 +18,9 @@ def __init__(self): self.camera_name = rospy.get_param('~camera_name', '') self.output_topic_name = rospy.get_param('~triggered_image_topic', 'triggered_images') if not self.camera_name: - rospy.logwarn("No camera name given! Assuming 'pylon_camera_node' as" + rospy.logwarn("No camera name given! Assuming 'arena_camera_node' as" " camera name") - self.camera_name = '/pylon_camera_node' + self.camera_name = '/arena_camera_node' else: rospy.loginfo('Camera name is: ' + self.camera_name) diff --git a/setup.py b/catkin_ws/src/arena_camera/setup.py old mode 100755 new mode 100644 similarity index 69% rename from setup.py rename to catkin_ws/src/arena_camera/setup.py index e1d30935..cc3538f1 --- a/setup.py +++ b/catkin_ws/src/arena_camera/setup.py @@ -1,12 +1,15 @@ #!/usr/bin/env python + +# NEEDED IF "catkin_python_setup" IS USED IN CMakeLists.txt + from distutils.core import setup from catkin_pkg.python_setup import generate_distutils_setup d = generate_distutils_setup( # # don't do this unless you want a globally visible script - #packages=[], - #package_dir={} - ) + # packages=[], + # package_dir={} +) maps = d setup(**maps) diff --git a/catkin_ws/src/arena_camera/src/arena_camera.cpp b/catkin_ws/src/arena_camera/src/arena_camera.cpp new file mode 100644 index 00000000..c58dbe91 --- /dev/null +++ b/catkin_ws/src/arena_camera/src/arena_camera.cpp @@ -0,0 +1,92 @@ +/****************************************************************************** + * Software License Agreement (BSD License) + * + * Copyright (C) 2016, Magazino GmbH. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of Magazino GmbH nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ +#include +#include +#include +#include + +namespace arena_camera +{ +ArenaCamera::ArenaCamera() + : device_user_id_("") + , img_rows_(0) + , img_cols_(0) + , img_size_byte_(0) + , grab_timeout_(-1.0) + , is_ready_(false) + , max_brightness_tolerance_(2.5) +{ +} + +const std::string& ArenaCamera::deviceUserID() const +{ + return device_user_id_; +} + +const size_t& ArenaCamera::imageRows() const +{ + return img_rows_; +} + +const size_t& ArenaCamera::imageCols() const +{ + return img_cols_; +} + +const size_t& ArenaCamera::imageSize() const +{ + return img_size_byte_; +} + +const float& ArenaCamera::maxBrightnessTolerance() const +{ + return max_brightness_tolerance_; +} + +const bool& ArenaCamera::isReady() const +{ + return is_ready_; +} + +std::size_t ArenaCamera::numUserOutputs() const +{ + return user_output_selector_enums_.size(); +} + +const std::vector& ArenaCamera::sequencerExposureTimes() const +{ + return seq_exp_times_; +} + +ArenaCamera::~ArenaCamera() +{ + // Releases all Arena resources. +} + +} // namespace arena_camera diff --git a/catkin_ws/src/arena_camera/src/arena_camera_node.cpp b/catkin_ws/src/arena_camera/src/arena_camera_node.cpp new file mode 100644 index 00000000..9a39701e --- /dev/null +++ b/catkin_ws/src/arena_camera/src/arena_camera_node.cpp @@ -0,0 +1,1899 @@ +/****************************************************************************** + * Software License Agreement (BSD License) + * + * Copyright (C) 2016, Magazino GmbH. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of Magazino GmbH nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "boost/multi_array.hpp" + +using diagnostic_msgs::DiagnosticStatus; + +namespace arena_camera +{ +Arena::ISystem* pSystem_ = nullptr; +Arena::IDevice* pDevice_ = nullptr; +Arena::IImage* pImage_ = nullptr; +const uint8_t* pData_ = nullptr; +GenApi::INodeMap* pNodeMap_ = nullptr; + +using sensor_msgs::CameraInfo; +using sensor_msgs::CameraInfoPtr; + +ArenaCameraNode::ArenaCameraNode() + : nh_("~") + , arena_camera_parameter_set_() + , set_binning_srv_(nh_.advertiseService("set_binning", &ArenaCameraNode::setBinningCallback, this)) + , set_roi_srv_(nh_.advertiseService("set_roi", &ArenaCameraNode::setROICallback, this)) + , set_exposure_srv_(nh_.advertiseService("set_exposure", &ArenaCameraNode::setExposureCallback, this)) + , set_gain_srv_(nh_.advertiseService("set_gain", &ArenaCameraNode::setGainCallback, this)) + , set_gamma_srv_(nh_.advertiseService("set_gamma", &ArenaCameraNode::setGammaCallback, this)) + , set_brightness_srv_(nh_.advertiseService("set_brightness", &ArenaCameraNode::setBrightnessCallback, this)) + , set_sleeping_srv_(nh_.advertiseService("set_sleeping", &ArenaCameraNode::setSleepingCallback, this)) + , set_user_output_srvs_() + , arena_camera_(nullptr) + , it_(new image_transport::ImageTransport(nh_)) + , img_raw_pub_(it_->advertiseCamera("image_raw", 1)) + , img_rect_pub_(nullptr) + , grab_imgs_raw_as_(nh_, "grab_images_raw", boost::bind(&ArenaCameraNode::grabImagesRawActionExecuteCB, this, _1), + false) + , grab_imgs_rect_as_(nullptr) + , pinhole_model_(nullptr) + , cv_bridge_img_rect_(nullptr) + , camera_info_manager_(new camera_info_manager::CameraInfoManager(nh_)) + , sampling_indices_() + , brightness_exp_lut_() + , is_sleeping_(false) +{ + diagnostics_updater_.setHardwareID("none"); + diagnostics_updater_.add("camera_availability", this, &ArenaCameraNode::create_diagnostics); + diagnostics_updater_.add("intrinsic_calibration", this, &ArenaCameraNode::create_camera_info_diagnostics); + diagnostics_trigger_ = nh_.createTimer(ros::Duration(2), &ArenaCameraNode::diagnostics_timer_callback_, this); + + init(); +} + +void ArenaCameraNode::create_diagnostics(diagnostic_updater::DiagnosticStatusWrapper& stat) +{ +} + +void ArenaCameraNode::create_camera_info_diagnostics(diagnostic_updater::DiagnosticStatusWrapper& stat) +{ + if (camera_info_manager_->isCalibrated()) + { + stat.summaryf(DiagnosticStatus::OK, "Intrinsic calibration found"); + } + else + { + stat.summaryf(DiagnosticStatus::ERROR, "No intrinsic calibration found"); + } +} + +void ArenaCameraNode::diagnostics_timer_callback_(const ros::TimerEvent&) +{ + diagnostics_updater_.update(); +} + +void ArenaCameraNode::init() +{ + // reading all necessary parameter to open the desired camera from the + // ros-parameter-server. In case that invalid parameter values can be + // detected, the interface will reset them to the default values. + // These parameters furthermore contain the intrinsic calibration matrices, + // in case they are provided + arena_camera_parameter_set_.readFromRosParameterServer(nh_); + + // setting the camera info URL to produce rectified image. Can substitute + // any desired file path or comment out this line if only producing raw + // images. + // arena_camera_parameter_set_.setCameraInfoURL(nh_, + // "file://${ROS_HOME}/camera_info/camera.yaml"); + + // creating the target ArenaCamera-Object with the specified + // device_user_id, registering the Software-Trigger-Mode, starting the + // communication with the device and enabling the desired startup-settings + if (!initAndRegister()) + { + ros::shutdown(); + return; + } + + // starting the grabbing procedure with the desired image-settings + if (!startGrabbing()) + { + ros::shutdown(); + return; + } +} + +bool createDevice(const std::string& device_user_id_to_open) +{ + pSystem_ = Arena::OpenSystem(); + pSystem_->UpdateDevices(100); + std::vector deviceInfos = pSystem_->GetDevices(); + + if (deviceInfos.size() == 0) + { + Arena::CloseSystem(pSystem_); + pSystem_ = nullptr; + return false; + } + else + { + if (device_user_id_to_open.empty()) + { + pDevice_ = pSystem_->CreateDevice(deviceInfos[0]); + return true; + } + else + { + std::vector::iterator it; + bool found_desired_device = false; + + for (it = deviceInfos.begin(); it != deviceInfos.end(); ++it) + { + std::string device_user_id_found(it->UserDefinedName()); + if ((0 == device_user_id_to_open.compare(device_user_id_found)) || + (device_user_id_to_open.length() < device_user_id_found.length() && + (0 == + device_user_id_found.compare(device_user_id_found.length() - device_user_id_to_open.length(), + device_user_id_to_open.length(), device_user_id_to_open)))) + { + found_desired_device = true; + break; + } + } + if (found_desired_device) + { + ROS_INFO_STREAM("Found the desired camera with DeviceUserID " << device_user_id_to_open << ": "); + + pDevice_ = pSystem_->CreateDevice(*it); + return true; + } + else + { + ROS_ERROR_STREAM("Couldn't find the camera that matches the " + << "given DeviceUserID: " << device_user_id_to_open << "! " + << "Either the ID is wrong or the cam is not yet connected"); + return false; + } + } + } +} + +bool ArenaCameraNode::initAndRegister() +{ + bool device_found_ = false; + device_found_ = createDevice(arena_camera_parameter_set_.deviceUserID()); + + if (device_found_ == false) + { + // wait and retry until a camera is present + ros::Time end = ros::Time::now() + ros::Duration(15.0); + ros::Rate r(0.5); + while (ros::ok() && device_found_ == false) + { + device_found_ = createDevice(arena_camera_parameter_set_.deviceUserID()); + if (ros::Time::now() > end) + { + ROS_WARN_STREAM("No camera present. Keep waiting ..."); + end = ros::Time::now() + ros::Duration(15.0); + } + r.sleep(); + ros::spinOnce(); + } + } + else + { + ROS_INFO_STREAM("Camera " << arena_camera_parameter_set_.deviceUserID() << " is found!"); + } + + if (!ros::ok()) + { + return false; + } + + return true; +} + +sensor_msgs::RegionOfInterest currentROI() +{ + sensor_msgs::RegionOfInterest roi; + roi.width = pImage_->GetWidth(); + roi.height = pImage_->GetHeight(); + ; + roi.x_offset = pImage_->GetOffsetX(); + roi.y_offset = pImage_->GetOffsetY(); + return roi; +} + +float currentGamma() +{ + GenApi::CFloatPtr pGamma = pDevice_->GetNodeMap()->GetNode("Gamma"); + + if (!pGamma || !GenApi::IsReadable(pGamma)) + { + ROS_WARN_STREAM("No gamma value, returning -1"); + return -1.; + } + else + { + float gammaValue = pGamma->GetValue(); + return gammaValue; + } +} + +int64_t currentBinningX() +{ + GenApi::CIntegerPtr BinningHorizontal = pDevice_->GetNodeMap()->GetNode("BinningHorizontal"); + + if (!BinningHorizontal || !GenApi::IsReadable(BinningHorizontal)) + { + ROS_WARN_STREAM("No binningY value, returning -1"); + return -1; + } + else + { + float binningXValue = BinningHorizontal->GetValue(); + return binningXValue; + } +} + +int64_t currentBinningY() +{ + GenApi::CIntegerPtr BinningVertical = pDevice_->GetNodeMap()->GetNode("BinningVertical"); + + if (!BinningVertical || !GenApi::IsReadable(BinningVertical)) + { + ROS_WARN_STREAM("No binningY value, returning -1"); + return -1; + } + else + { + float binningYValue = BinningVertical->GetValue(); + return binningYValue; + } +} + +float currentGain() +{ + GenApi::CFloatPtr pGain = pDevice_->GetNodeMap()->GetNode("Gain"); + + if (!pGain || !GenApi::IsReadable(pGain)) + { + ROS_WARN_STREAM("No gain value"); + return -1.; + } + else + { + float gainValue = pGain->GetValue(); + return gainValue; + } +} + +float currentExposure() +{ + GenApi::CFloatPtr pExposureTime = pDevice_->GetNodeMap()->GetNode("ExposureTime"); + + if (!pExposureTime || !GenApi::IsReadable(pExposureTime)) + { + ROS_WARN_STREAM("No exposure time value, returning -1"); + return -1.; + } + else + { + float exposureValue = pExposureTime->GetValue(); + return exposureValue; + } +} + +std::string currentROSEncoding() +{ + std::string gen_api_encoding(Arena::GetNodeValue(pDevice_->GetNodeMap(), "PixelFormat")); + std::string ros_encoding(""); + if (!encoding_conversions::genAPI2Ros(gen_api_encoding, ros_encoding)) + { + std::stringstream ss; + ss << "No ROS equivalent to GenApi encoding '" << gen_api_encoding << "' found! This is bad because this case " + "should never occur!"; + throw std::runtime_error(ss.str()); + return "NO_ENCODING"; + } + return ros_encoding; +} + +bool ArenaCameraNode::setImageEncoding(const std::string& ros_encoding) +{ + std::string gen_api_encoding; + bool conversion_found = encoding_conversions::ros2GenAPI(ros_encoding, gen_api_encoding); + if (!conversion_found) + { + if (ros_encoding.empty()) + { + return false; + } + else + { + ROS_ERROR_STREAM("Can't convert ROS encoding '" << ros_encoding + << "' to a corresponding GenAPI encoding! Will use current " + << "pixel format as fallback!"); + return false; + } + } + try + { + GenApi::CEnumerationPtr pPixelFormat = pDevice_->GetNodeMap()->GetNode("PixelFormat"); + if (GenApi::IsWritable(pPixelFormat)) + { + Arena::SetNodeValue(pDevice_->GetNodeMap(), "PixelFormat", gen_api_encoding.c_str()); + if (currentROSEncoding() == "16UC3" || currentROSEncoding() == "16UC4") + ROS_WARN_STREAM("ROS grabbing image data from 3D pixel format, unable to display in image viewer"); + } + } + catch (const GenICam::GenericException& e) + { + ROS_ERROR_STREAM("An exception while setting target image encoding to '" << ros_encoding + << "' occurred: " << e.GetDescription()); + return false; + } + return true; +} + +bool ArenaCameraNode::startGrabbing() +{ + try + { + setImageEncoding(arena_camera_parameter_set_.imageEncoding()); + + GenApi::CStringPtr pTriggerMode = pDevice_->GetNodeMap()->GetNode("TriggerMode"); + if (GenApi::IsWritable(pTriggerMode)) + { + Arena::SetNodeValue(pDevice_->GetNodeMap(), "TriggerMode", "On"); + Arena::SetNodeValue(pDevice_->GetNodeMap(), "TriggerSource", "Software"); + } + + // + // // Initial setting of the CameraInfo-msg, assuming no calibration given + CameraInfo initial_cam_info; + setupInitialCameraInfo(initial_cam_info); + camera_info_manager_->setCameraInfo(initial_cam_info); + + if (arena_camera_parameter_set_.cameraInfoURL().empty() || + !camera_info_manager_->validateURL(arena_camera_parameter_set_.cameraInfoURL())) + { + ROS_INFO_STREAM("CameraInfoURL needed for rectification! ROS-Param: " + << "'" << nh_.getNamespace() << "/camera_info_url' = '" + << arena_camera_parameter_set_.cameraInfoURL() << "' is invalid!"); + ROS_DEBUG_STREAM("CameraInfoURL should have following style: " + << "'file:///full/path/to/local/file.yaml' or " + << "'file://${ROS_HOME}/camera_info/${NAME}.yaml'"); + ROS_WARN("Will only provide distorted /image_raw images!"); + } + else + { + // override initial camera info if the url is valid + if (camera_info_manager_->loadCameraInfo(arena_camera_parameter_set_.cameraInfoURL())) + { + setupRectification(); + // set the correct tf frame_id + CameraInfoPtr cam_info(new CameraInfo(camera_info_manager_->getCameraInfo())); + cam_info->header.frame_id = img_raw_msg_.header.frame_id; + camera_info_manager_->setCameraInfo(*cam_info); + } + else + { + ROS_WARN("Will only provide distorted /image_raw images!"); + } + } + + if (arena_camera_parameter_set_.binning_x_given_) + { + size_t reached_binning_x; + if (setBinningX(arena_camera_parameter_set_.binning_x_, reached_binning_x)) + { + ROS_INFO_STREAM("Setting horizontal binning_x to " << arena_camera_parameter_set_.binning_x_); + ROS_WARN_STREAM("The image width of the camera_info-msg will " + << "be adapted, so that the binning_x value in this msg remains 1"); + } + } + + if (arena_camera_parameter_set_.binning_y_given_) + { + size_t reached_binning_y; + if (setBinningY(arena_camera_parameter_set_.binning_y_, reached_binning_y)) + { + ROS_INFO_STREAM("Setting vertical binning_y to " << arena_camera_parameter_set_.binning_y_); + ROS_WARN_STREAM("The image height of the camera_info-msg will " + << "be adapted, so that the binning_y value in this msg remains 1"); + } + } + + if (arena_camera_parameter_set_.exposure_given_) + { + float reached_exposure; + if (setExposure(arena_camera_parameter_set_.exposure_, reached_exposure)) + { + ROS_INFO_STREAM("Setting exposure to " << arena_camera_parameter_set_.exposure_ + << ", reached: " << reached_exposure); + } + } + else if (arena_camera_parameter_set_.brightness_given_ && arena_camera_parameter_set_.exposure_auto_) + { + Arena::SetNodeValue(pDevice_->GetNodeMap(), "ExposureAuto", "Continuous"); + ROS_INFO_STREAM("Settings Exposure to auto"); + } + + // if (arena_camera_parameter_set_.image_encoding_given_) + // { + // float reached_image_encoding; + // if (setImageEncoding(arena_camera_parameter_set_.image_encoding_)) + // { + // ROS_INFO_STREAM("Setting exposure to " + // << arena_camera_parameter_set_.image_encoding_); + // } + // } + + if (arena_camera_parameter_set_.gain_given_) + { + float reached_gain; + if (setGain(arena_camera_parameter_set_.gain_, reached_gain)) + { + ROS_INFO_STREAM("Setting gain to: " << arena_camera_parameter_set_.gain_ << ", reached: " << reached_gain); + } + } + + if (arena_camera_parameter_set_.gamma_given_) + { + float reached_gamma; + if (setGamma(arena_camera_parameter_set_.gamma_, reached_gamma)) + { + ROS_INFO_STREAM("Setting gamma to " << arena_camera_parameter_set_.gamma_ << ", reached: " << reached_gamma); + } + } + + Arena::SetNodeValue(pDevice_->GetTLStreamNodeMap(), "StreamBufferHandlingMode", "NewestOnly"); + + pDevice_->StartStream(); + bool isTriggerArmed = false; + + if (GenApi::IsWritable(pTriggerMode)) + { + do + { + isTriggerArmed = Arena::GetNodeValue(pDevice_->GetNodeMap(), "TriggerArmed"); + } while (isTriggerArmed == false); + Arena::ExecuteNode(pDevice_->GetNodeMap(), "TriggerSoftware"); + } + + pImage_ = pDevice_->GetImage(5000); + pData_ = pImage_->GetData(); + + img_raw_msg_.data.resize(img_raw_msg_.height * img_raw_msg_.step); + memcpy(&img_raw_msg_.data[0], pImage_->GetData(), img_raw_msg_.height * img_raw_msg_.step); + } + catch (GenICam::GenericException& e) + { + ROS_ERROR_STREAM("Error while grabbing first image occurred: \r\n" << e.GetDescription()); + return false; + } + + img_raw_msg_.header.frame_id = arena_camera_parameter_set_.cameraFrame(); + // Encoding of pixels -- channel meaning, ordering, size + // taken from the list of strings in include/sensor_msgs/image_encodings.h + img_raw_msg_.encoding = currentROSEncoding(); + img_raw_msg_.height = pImage_->GetHeight(); + img_raw_msg_.width = pImage_->GetWidth(); + // step = full row length in bytes, img_size = (step * rows), imagePixelDepth + // already contains the number of channels + img_raw_msg_.step = img_raw_msg_.width * (pImage_->GetBitsPerPixel() / 8); + + if (!camera_info_manager_->setCameraName( + std::string(Arena::GetNodeValue(pDevice_->GetNodeMap(), "DeviceUserID").c_str()))) + { + // valid name contains only alphanumeric signs and '_' + ROS_WARN_STREAM( + "[" << std::string(Arena::GetNodeValue(pDevice_->GetNodeMap(), "DeviceUserID").c_str()) + << "] name not valid for camera_info_manager"); + } + + grab_imgs_raw_as_.start(); + + ROS_INFO_STREAM("Startup settings: " + << "encoding = '" << currentROSEncoding() << "', " + << "binning = [" << currentBinningX() << ", " << currentBinningY() << "], " + << "exposure = " << currentExposure() << ", " + << "gain = " << currentGain() << ", " + << "gamma = " << currentGamma() << ", " + << "shutter mode = " << arena_camera_parameter_set_.shutterModeString()); + + // Framerate Settings + pNodeMap_ = pDevice_->GetNodeMap(); + auto maximumFrameRate = GenApi::CFloatPtr(pNodeMap_->GetNode("AcquisitionFrameRate"))->GetMax(); + + if (maximumFrameRate < arena_camera_parameter_set_.frameRate()) + { + ROS_INFO("Desired framerate %.2f is higher than max possible. Will limit " + "framerate to: %.2f Hz", + arena_camera_parameter_set_.frameRate(), maximumFrameRate); + arena_camera_parameter_set_.setFrameRate(nh_, maximumFrameRate); + } + else if (arena_camera_parameter_set_.frameRate() == -1) + { + arena_camera_parameter_set_.setFrameRate(nh_, maximumFrameRate); + ROS_INFO("Max possible framerate is %.2f Hz", maximumFrameRate); + } + + pDevice_->RequeueBuffer(pImage_); + return true; +} + +void ArenaCameraNode::setupRectification() +{ + if (!img_rect_pub_) + { + img_rect_pub_ = new ros::Publisher(nh_.advertise("image_rect", 1)); + } + + if (!grab_imgs_rect_as_) + { + grab_imgs_rect_as_ = new GrabImagesAS( + nh_, "grab_images_rect", boost::bind(&ArenaCameraNode::grabImagesRectActionExecuteCB, this, _1), false); + grab_imgs_rect_as_->start(); + } + + if (!pinhole_model_) + { + pinhole_model_ = new image_geometry::PinholeCameraModel(); + } + + pinhole_model_->fromCameraInfo(camera_info_manager_->getCameraInfo()); + if (!cv_bridge_img_rect_) + { + cv_bridge_img_rect_ = new cv_bridge::CvImage(); + } + cv_bridge_img_rect_->header = img_raw_msg_.header; + cv_bridge_img_rect_->encoding = img_raw_msg_.encoding; +} + +struct CameraPublisherImpl +{ + image_transport::Publisher image_pub_; + ros::Publisher info_pub_; + bool unadvertised_; + // double constructed_; +}; + +class CameraPublisherLocal +{ +public: + struct Impl; + typedef boost::shared_ptr ImplPtr; + typedef boost::weak_ptr ImplWPtr; + + CameraPublisherImpl* impl_; +}; + +uint32_t ArenaCameraNode::getNumSubscribersRaw() const +{ + return ((CameraPublisherLocal*)(&img_raw_pub_))->impl_->image_pub_.getNumSubscribers(); +} + +void ArenaCameraNode::spin() +{ + if (camera_info_manager_->isCalibrated()) + { + ROS_INFO_ONCE("Camera is calibrated"); + } + else + { + ROS_INFO_ONCE("Camera not calibrated"); + } + + if (pDevice_->IsConnected() == false) + { + ROS_ERROR("Arena camera has been removed, trying to reset"); + pSystem_->DestroyDevice(pDevice_); + pDevice_ = nullptr; + Arena::CloseSystem(pSystem_); + pSystem_ = nullptr; + for (ros::ServiceServer& user_output_srv : set_user_output_srvs_) + { + user_output_srv.shutdown(); + } + ros::Duration(0.5).sleep(); // sleep for half a second + init(); + return; + } + + if (!isSleeping() && (img_raw_pub_.getNumSubscribers() || getNumSubscribersRect())) + { + if (getNumSubscribersRaw() || getNumSubscribersRect()) + { + if (!grabImage()) + { + ROS_INFO("did not get image"); + return; + } + } + + if (img_raw_pub_.getNumSubscribers() > 0) + { + // get actual cam_info-object in every frame, because it might have + // changed due to a 'set_camera_info'-service call + sensor_msgs::CameraInfoPtr cam_info(new sensor_msgs::CameraInfo(camera_info_manager_->getCameraInfo())); + cam_info->header.stamp = img_raw_msg_.header.stamp; + + // Publish via image_transport + img_raw_pub_.publish(img_raw_msg_, *cam_info); + ROS_INFO_ONCE("Number subscribers received"); + } + + if (getNumSubscribersRect() > 0 && camera_info_manager_->isCalibrated()) + { + cv_bridge_img_rect_->header.stamp = img_raw_msg_.header.stamp; + assert(pinhole_model_->initialized()); + cv_bridge::CvImagePtr cv_img_raw = cv_bridge::toCvCopy(img_raw_msg_, img_raw_msg_.encoding); + pinhole_model_->fromCameraInfo(camera_info_manager_->getCameraInfo()); + pinhole_model_->rectifyImage(cv_img_raw->image, cv_bridge_img_rect_->image); + img_rect_pub_->publish(*cv_bridge_img_rect_); + ROS_INFO_ONCE("Number subscribers rect received"); + } + } +} + +bool ArenaCameraNode::grabImage() +{ + boost::lock_guard lock(grab_mutex_); + try + { + GenApi::CStringPtr pTriggerMode = pDevice_->GetNodeMap()->GetNode("TriggerMode"); + if (GenApi::IsWritable(pTriggerMode)) + { + bool isTriggerArmed = false; + + do + { + isTriggerArmed = Arena::GetNodeValue(pDevice_->GetNodeMap(), "TriggerArmed"); + } while (isTriggerArmed == false); + Arena::ExecuteNode(pDevice_->GetNodeMap(), "TriggerSoftware"); + } + pImage_ = pDevice_->GetImage(5000); + pData_ = pImage_->GetData(); + + img_raw_msg_.data.resize(img_raw_msg_.height * img_raw_msg_.step); + memcpy(&img_raw_msg_.data[0], pImage_->GetData(), img_raw_msg_.height * img_raw_msg_.step); + + img_raw_msg_.header.stamp = ros::Time::now(); + + pDevice_->RequeueBuffer(pImage_); + return true; + } + catch (GenICam::GenericException& e) + { + return false; + } +} + +void ArenaCameraNode::grabImagesRawActionExecuteCB(const camera_control_msgs::GrabImagesGoal::ConstPtr& goal) +{ + camera_control_msgs::GrabImagesResult result; + result = grabImagesRaw(goal, &grab_imgs_raw_as_); + grab_imgs_raw_as_.setSucceeded(result); +} + +void ArenaCameraNode::grabImagesRectActionExecuteCB(const camera_control_msgs::GrabImagesGoal::ConstPtr& goal) +{ + camera_control_msgs::GrabImagesResult result; + if (!camera_info_manager_->isCalibrated()) + { + result.success = false; + grab_imgs_rect_as_->setSucceeded(result); + return; + } + else + { + result = grabImagesRaw(goal, std::ref(grab_imgs_rect_as_)); + if (!result.success) + { + grab_imgs_rect_as_->setSucceeded(result); + return; + } + + for (std::size_t i = 0; i < result.images.size(); ++i) + { + cv_bridge::CvImagePtr cv_img_raw = cv_bridge::toCvCopy(result.images[i], result.images[i].encoding); + pinhole_model_->fromCameraInfo(camera_info_manager_->getCameraInfo()); + cv_bridge::CvImage cv_bridge_img_rect; + cv_bridge_img_rect.header = result.images[i].header; + cv_bridge_img_rect.encoding = result.images[i].encoding; + pinhole_model_->rectifyImage(cv_img_raw->image, cv_bridge_img_rect.image); + cv_bridge_img_rect.toImageMsg(result.images[i]); + } + grab_imgs_rect_as_->setSucceeded(result); + } +} + +camera_control_msgs::GrabImagesResult +ArenaCameraNode::grabImagesRaw(const camera_control_msgs::GrabImagesGoal::ConstPtr& goal, GrabImagesAS* action_server) +{ + camera_control_msgs::GrabImagesResult result; + camera_control_msgs::GrabImagesFeedback feedback; + +#if DEBUG + std::cout << *goal << std::endl; +#endif + + if (goal->exposure_given && goal->exposure_times.empty()) + { + ROS_ERROR_STREAM("GrabImagesRaw action server received request and " + << "'exposure_given' is true, but the 'exposure_times' vector is " + << "empty! Not enough information to execute acquisition!"); + result.success = false; + return result; + } + + if (goal->gain_given && goal->gain_values.empty()) + { + ROS_ERROR_STREAM("GrabImagesRaw action server received request and " + << "'gain_given' is true, but the 'gain_values' vector is " + << "empty! Not enough information to execute acquisition!"); + result.success = false; + return result; + } + + if (goal->brightness_given && goal->brightness_values.empty()) + { + ROS_ERROR_STREAM("GrabImagesRaw action server received request and " + << "'brightness_given' is true, but the 'brightness_values' vector" + << " is empty! Not enough information to execute acquisition!"); + result.success = false; + return result; + } + + if (goal->gamma_given && goal->gamma_values.empty()) + { + ROS_ERROR_STREAM("GrabImagesRaw action server received request and " + << "'gamma_given' is true, but the 'gamma_values' vector is " + << "empty! Not enough information to execute acquisition!"); + result.success = false; + return result; + } + + std::vector candidates; + candidates.resize(4); // gain, exposure, gamma, brightness + candidates.at(0) = goal->gain_given ? goal->gain_values.size() : 0; + candidates.at(1) = goal->exposure_given ? goal->exposure_times.size() : 0; + candidates.at(2) = goal->brightness_given ? goal->brightness_values.size() : 0; + candidates.at(3) = goal->gamma_given ? goal->gamma_values.size() : 0; + + size_t n_images = *std::max_element(candidates.begin(), candidates.end()); + + if (goal->exposure_given && goal->exposure_times.size() != n_images) + { + ROS_ERROR_STREAM("Size of requested exposure times does not match to " + << "the size of the requested vaules of brightness, gain or " + << "gamma! Can't grab!"); + result.success = false; + return result; + } + + if (goal->gain_given && goal->gain_values.size() != n_images) + { + ROS_ERROR_STREAM("Size of requested gain values does not match to " + << "the size of the requested exposure times or the vaules of " + << "brightness or gamma! Can't grab!"); + result.success = false; + return result; + } + + if (goal->gamma_given && goal->gamma_values.size() != n_images) + { + ROS_ERROR_STREAM("Size of requested gamma values does not match to " + << "the size of the requested exposure times or the vaules of " + << "brightness or gain! Can't grab!"); + result.success = false; + return result; + } + + if (goal->brightness_given && goal->brightness_values.size() != n_images) + { + ROS_ERROR_STREAM("Size of requested brightness values does not match to " + << "the size of the requested exposure times or the vaules of gain or " + << "gamma! Can't grab!"); + result.success = false; + return result; + } + + if (goal->brightness_given && !(goal->exposure_auto || goal->gain_auto)) + { + ROS_ERROR_STREAM("Error while executing the GrabImagesRawAction: A " + << "target brightness is provided but Exposure time AND gain are " + << "declared as fix, so its impossible to reach the brightness"); + result.success = false; + return result; + } + + result.images.resize(n_images); + result.reached_exposure_times.resize(n_images); + result.reached_gain_values.resize(n_images); + result.reached_gamma_values.resize(n_images); + result.reached_brightness_values.resize(n_images); + + result.success = true; + + boost::lock_guard lock(grab_mutex_); + + float previous_exp, previous_gain, previous_gamma; + if (goal->exposure_given) + { + previous_exp = Arena::GetNodeValue(pDevice_->GetNodeMap(), "ExposureTime"); + } + if (goal->gain_given) + { + previous_gain = currentGain(); + } + if (goal->gamma_given) + { + previous_gamma = currentGamma(); + } + if (goal->brightness_given) + { + previous_gain = currentGain(); + previous_exp = currentExposure(); + } + + for (std::size_t i = 0; i < n_images; ++i) + { + if (goal->exposure_given) + { + result.success = setExposure(goal->exposure_times[i], result.reached_exposure_times[i]); + } + if (goal->gain_given) + { + result.success = setGain(goal->gain_values[i], result.reached_gain_values[i]); + } + if (goal->gamma_given) + { + result.success = setGamma(goal->gamma_values[i], result.reached_gamma_values[i]); + } + if (goal->brightness_given) + { + int reached_brightness; + result.success = + setBrightness(goal->brightness_values[i], reached_brightness, goal->exposure_auto, goal->gain_auto); + result.reached_brightness_values[i] = static_cast(reached_brightness); + // result.reached_exposure_times[i] = currentExposure(); + // result.reached_gain_values[i] = currentGain(); + } + if (!result.success) + { + ROS_ERROR_STREAM("Error while setting one of the desired image " + << "properties in the GrabImagesRawActionCB. Aborting!"); + break; + } + + sensor_msgs::Image& img = result.images[i]; + img.encoding = currentROSEncoding(); + img.height = pImage_->GetHeight(); + img.width = pImage_->GetWidth(); + // step = full row length in bytes, img_size = (step * rows), + // imagePixelDepth already contains the number of channels + img_raw_msg_.step = img_raw_msg_.width * (pImage_->GetBitsPerPixel() / 8); + + img.header.stamp = ros::Time::now(); + img.header.frame_id = cameraFrame(); + feedback.curr_nr_images_taken = i + 1; + + if (action_server != nullptr) + { + action_server->publishFeedback(feedback); + } + } + if (camera_info_manager_) + { + sensor_msgs::CameraInfoPtr cam_info(new sensor_msgs::CameraInfo(camera_info_manager_->getCameraInfo())); + result.cam_info = *cam_info; + } + + // restore previous settings: + float reached_val; + if (goal->exposure_given) + { + setExposure(previous_exp, reached_val); + } + if (goal->gain_given) + { + setGain(previous_gain, reached_val); + } + if (goal->gamma_given) + { + setGamma(previous_gamma, reached_val); + } + if (goal->brightness_given) + { + setGain(previous_gain, reached_val); + setExposure(previous_exp, reached_val); + } + return result; +} + +bool ArenaCameraNode::setUserOutputCB(const int output_id, camera_control_msgs::SetBool::Request& req, + camera_control_msgs::SetBool::Response& res) +{ + // res.success = arena_camera_->setUserOutput(output_id, req.data); + return true; +} + +bool ArenaCameraNode::setAutoflash(const int output_id, camera_control_msgs::SetBool::Request& req, + camera_control_msgs::SetBool::Response& res) +{ + ROS_INFO("AutoFlashCB: %i -> %i", output_id, req.data); + std::map auto_flashs; + auto_flashs[output_id] = req.data; + // arena_camera_->setAutoflash(auto_flashs); + res.success = true; + return true; +} + +const double& ArenaCameraNode::frameRate() const +{ + return arena_camera_parameter_set_.frameRate(); +} + +const std::string& ArenaCameraNode::cameraFrame() const +{ + return arena_camera_parameter_set_.cameraFrame(); +} + +uint32_t ArenaCameraNode::getNumSubscribersRect() const +{ + return camera_info_manager_->isCalibrated() ? img_rect_pub_->getNumSubscribers() : 0; +} + +uint32_t ArenaCameraNode::getNumSubscribers() const +{ + return img_raw_pub_.getNumSubscribers() + img_rect_pub_->getNumSubscribers(); +} + +void ArenaCameraNode::setupInitialCameraInfo(sensor_msgs::CameraInfo& cam_info_msg) +{ + std_msgs::Header header; + header.frame_id = arena_camera_parameter_set_.cameraFrame(); + header.stamp = ros::Time::now(); + + // http://www.ros.org/reps/rep-0104.html + // If the camera is uncalibrated, the matrices D, K, R, P should be left + // zeroed out. In particular, clients may assume that K[0] == 0.0 + // indicates an uncalibrated camera. + cam_info_msg.header = header; + + // The image dimensions with which the camera was calibrated. Normally + // this will be the full camera resolution in pixels. They remain fix, even + // if binning is applied + // rows and colums + cam_info_msg.height = Arena::GetNodeValue(pDevice_->GetNodeMap(), "Height"); + cam_info_msg.width = Arena::GetNodeValue(pDevice_->GetNodeMap(), "Width"); + + // The distortion model used. Supported models are listed in + // sensor_msgs/distortion_models.h. For most cameras, "plumb_bob" - a + // simple model of radial and tangential distortion - is sufficient. + // Empty D and distortion_model indicate that the CameraInfo cannot be used + // to rectify points or images, either because the camera is not calibrated + // or because the rectified image was produced using an unsupported + // distortion model, e.g. the proprietary one used by Bumblebee cameras + // [http://www.ros.org/reps/rep-0104.html]. + cam_info_msg.distortion_model = ""; + + // The distortion parameters, size depending on the distortion model. + // For "plumb_bob", the 5 parameters are: (k1, k2, t1, t2, k3) -> float64[] D. + cam_info_msg.D = std::vector(5, 0.); + + // Intrinsic camera matrix for the raw (distorted) images. + // [fx 0 cx] + // K = [ 0 fy cy] --> 3x3 row-major matrix + // [ 0 0 1] + // Projects 3D points in the camera coordinate frame to 2D pixel coordinates + // using the focal lengths (fx, fy) and principal point (cx, cy). + cam_info_msg.K.assign(0.0); + + // Rectification matrix (stereo cameras only) + // A rotation matrix aligning the camera coordinate system to the ideal + // stereo image plane so that epipolar lines in both stereo images are + // parallel. + cam_info_msg.R.assign(0.0); + + // Projection/camera matrix + // [fx' 0 cx' Tx] + // P = [ 0 fy' cy' Ty] --> # 3x4 row-major matrix + // [ 0 0 1 0] + // By convention, this matrix specifies the intrinsic (camera) matrix of the + // processed (rectified) image. That is, the left 3x3 portion is the normal + // camera intrinsic matrix for the rectified image. It projects 3D points + // in the camera coordinate frame to 2D pixel coordinates using the focal + // lengths (fx', fy') and principal point (cx', cy') - these may differ from + // the values in K. For monocular cameras, Tx = Ty = 0. Normally, monocular + // cameras will also have R = the identity and P[1:3,1:3] = K. + // For a stereo pair, the fourth column [Tx Ty 0]' is related to the + // position of the optical center of the second camera in the first + // camera's frame. We assume Tz = 0 so both cameras are in the same + // stereo image plane. The first camera always has Tx = Ty = 0. + // For the right (second) camera of a horizontal stereo pair, + // Ty = 0 and Tx = -fx' * B, where B is the baseline between the cameras. + // Given a 3D point [X Y Z]', the projection (x, y) of the point onto the + // rectified image is given by: + // [u v w]' = P * [X Y Z 1]' + // x = u / w + // y = v / w + // This holds for both images of a stereo pair. + cam_info_msg.P.assign(0.0); + + // Binning refers here to any camera setting which combines rectangular + // neighborhoods of pixels into larger "super-pixels." It reduces the + // resolution of the output image to (width / binning_x) x (height / + // binning_y). The default values binning_x = binning_y = 0 is considered the + // same as binning_x = binning_y = 1 (no subsampling). + // cam_info_msg.binning_x = currentBinningX(); + // cam_info_msg.binning_y = currentBinningY(); + + // Region of interest (subwindow of full camera resolution), given in full + // resolution (unbinned) image coordinates. A particular ROI always denotes + // the same window of pixels on the camera sensor, regardless of binning + // settings. The default setting of roi (all values 0) is considered the same + // as full resolution (roi.width = width, roi.height = height). + cam_info_msg.roi.x_offset = cam_info_msg.roi.y_offset = 0; + cam_info_msg.roi.height = cam_info_msg.roi.width = 0; +} + +bool ArenaCameraNode::setROI(const sensor_msgs::RegionOfInterest target_roi, sensor_msgs::RegionOfInterest& reached_roi) +{ + boost::lock_guard lock(grab_mutex_); + // TODO: set ROI + return true; +} + +bool setBinningXValue(const size_t& target_binning_x, size_t& reached_binning_x) +{ + try + { + GenApi::CIntegerPtr pBinningHorizontal = pDevice_->GetNodeMap()->GetNode("BinningHorizontal"); + if (GenApi::IsWritable(pBinningHorizontal)) + { + size_t binning_x_to_set = target_binning_x; + if (binning_x_to_set < pBinningHorizontal->GetMin()) + { + ROS_WARN_STREAM("Desired horizontal binning_x factor(" << binning_x_to_set << ") unreachable! Setting to lower " + << "limit: " << pBinningHorizontal->GetMin()); + binning_x_to_set = pBinningHorizontal->GetMin(); + } + else if (binning_x_to_set > pBinningHorizontal->GetMax()) + { + ROS_WARN_STREAM("Desired horizontal binning_x factor(" << binning_x_to_set << ") unreachable! Setting to upper " + << "limit: " << pBinningHorizontal->GetMax()); + binning_x_to_set = pBinningHorizontal->GetMax(); + } + + pBinningHorizontal->SetValue(binning_x_to_set); + reached_binning_x = currentBinningX(); + } + else + { + ROS_WARN_STREAM("Camera does not support binning. Will keep the " + << "current settings"); + reached_binning_x = currentBinningX(); + } + } + + catch (const GenICam::GenericException& e) + { + ROS_ERROR_STREAM("An exception while setting target horizontal " + << "binning_x factor to " << target_binning_x << " occurred: " << e.GetDescription()); + return false; + } + return true; +} + +bool ArenaCameraNode::setBinningX(const size_t& target_binning_x, size_t& reached_binning_x) +{ + boost::lock_guard lock(grab_mutex_); + + if (!setBinningXValue(target_binning_x, reached_binning_x)) + { + // retry till timeout + ros::Rate r(10.0); + ros::Time timeout(ros::Time::now() + ros::Duration(2.0)); + while (ros::ok()) + { + if (setBinningXValue(target_binning_x, reached_binning_x)) + { + break; + } + if (ros::Time::now() > timeout) + { + ROS_ERROR_STREAM("Error in setBinningX(): Unable to set target " + << "binning_x factor before timeout"); + CameraInfoPtr cam_info(new CameraInfo(camera_info_manager_->getCameraInfo())); + cam_info->binning_x = currentBinningX(); + camera_info_manager_->setCameraInfo(*cam_info); + // img_raw_msg_.width = pImage_->GetWidth(); + // step = full row length in bytes, img_size = (step * rows), + // imagePixelDepth already contains the number of channels + // img_raw_msg_.step = img_raw_msg_.width * (pImage_->GetBitsPerPixel() + // / 8); + return false; + } + r.sleep(); + } + } + + return true; +} + +bool setBinningYValue(const size_t& target_binning_y, size_t& reached_binning_y) +{ + try + { + GenApi::CIntegerPtr pBinningVertical = pDevice_->GetNodeMap()->GetNode("BinningVertical"); + if (GenApi::IsWritable(pBinningVertical)) + { + size_t binning_y_to_set = target_binning_y; + if (binning_y_to_set < pBinningVertical->GetMin()) + { + ROS_WARN_STREAM("Desired horizontal binning_y factor(" << binning_y_to_set << ") unreachable! Setting to lower " + << "limit: " << pBinningVertical->GetMin()); + binning_y_to_set = pBinningVertical->GetMin(); + } + else if (binning_y_to_set > pBinningVertical->GetMax()) + { + ROS_WARN_STREAM("Desired horizontal binning_y factor(" << binning_y_to_set << ") unreachable! Setting to upper " + << "limit: " << pBinningVertical->GetMax()); + binning_y_to_set = pBinningVertical->GetMax(); + } + + pBinningVertical->SetValue(binning_y_to_set); + reached_binning_y = currentBinningY(); + } + else + { + ROS_WARN_STREAM("Camera does not support binning. Will keep the " + << "current settings"); + reached_binning_y = currentBinningY(); + } + } + + catch (const GenICam::GenericException& e) + { + ROS_ERROR_STREAM("An exception while setting target horizontal " + << "binning_y factor to " << target_binning_y << " occurred: " << e.GetDescription()); + return false; + } + return true; +} + +bool ArenaCameraNode::setBinningY(const size_t& target_binning_y, size_t& reached_binning_y) +{ + boost::lock_guard lock(grab_mutex_); + + if (!setBinningYValue(target_binning_y, reached_binning_y)) + { + // retry till timeout + ros::Rate r(10.0); + ros::Time timeout(ros::Time::now() + ros::Duration(2.0)); + while (ros::ok()) + { + if (setBinningYValue(target_binning_y, reached_binning_y)) + { + break; + } + if (ros::Time::now() > timeout) + { + ROS_ERROR_STREAM("Error in setBinningY(): Unable to set target " + << "binning_y factor before timeout"); + CameraInfoPtr cam_info(new CameraInfo(camera_info_manager_->getCameraInfo())); + cam_info->binning_y = currentBinningY(); + camera_info_manager_->setCameraInfo(*cam_info); + img_raw_msg_.width = pImage_->GetWidth(); + // step = full row length in bytes, img_size = (step * rows), + // imagePixelDepth already contains the number of channels + img_raw_msg_.step = img_raw_msg_.width * (pImage_->GetBitsPerPixel() / 8); + return false; + } + r.sleep(); + } + } + + return true; +} + +bool ArenaCameraNode::setBinningCallback(camera_control_msgs::SetBinning::Request& req, + camera_control_msgs::SetBinning::Response& res) +{ + size_t reached_binning_x, reached_binning_y; + bool success_x = setBinningX(req.target_binning_x, reached_binning_x); + bool success_y = setBinningY(req.target_binning_y, reached_binning_y); + res.reached_binning_x = static_cast(reached_binning_x); + res.reached_binning_y = static_cast(reached_binning_y); + res.success = success_x && success_y; + return true; +} + +bool ArenaCameraNode::setROICallback(camera_control_msgs::SetROI::Request& req, + camera_control_msgs::SetROI::Response& res) +{ + res.success = setROI(req.target_roi, res.reached_roi); + return true; +} + +bool ArenaCameraNode::setExposureValue(const float& target_exposure, float& reached_exposure) +{ + try + { + Arena::SetNodeValue(pDevice_->GetNodeMap(), "ExposureAuto", "Off"); + + GenApi::CFloatPtr pExposureTime = pDevice_->GetNodeMap()->GetNode("ExposureTime"); + + float exposure_to_set = target_exposure; + if (exposure_to_set < pExposureTime->GetMin()) + { + ROS_WARN_STREAM("Desired exposure (" << exposure_to_set << ") " + << "time unreachable! Setting to lower limit: " << pExposureTime->GetMin()); + exposure_to_set = pExposureTime->GetMin(); + } + else if (exposure_to_set > pExposureTime->GetMax()) + { + ROS_WARN_STREAM("Desired exposure (" << exposure_to_set << ") " + << "time unreachable! Setting to upper limit: " << pExposureTime->GetMax()); + exposure_to_set = pExposureTime->GetMax(); + } + + pExposureTime->SetValue(exposure_to_set); + reached_exposure = pExposureTime->GetValue(); + + // if ( std::fabs(reached_exposure - exposure_to_set) > exposureStep() ) + // { + // // no success if the delta between target and reached exposure + // // is greater then the exposure step in ms + // return false; + // } + } + catch (const GenICam::GenericException& e) + { + ROS_ERROR_STREAM("An exception while setting target exposure to " << target_exposure + << " occurred:" << e.GetDescription()); + return false; + } + return true; +} + +bool ArenaCameraNode::setExposure(const float& target_exposure, float& reached_exposure) +{ + boost::lock_guard lock(grab_mutex_); + // if ( !pylon_camera_->isReady() ) + // { + // ROS_WARN("Error in setExposure(): pylon_camera_ is not ready!"); + // return false; + // } + + if (ArenaCameraNode::setExposureValue(target_exposure, reached_exposure)) + { + // success if the delta is smaller then the exposure step + return true; + } + else // retry till timeout + { + // wait for max 5s till the cam has updated the exposure + ros::Rate r(10.0); + ros::Time timeout(ros::Time::now() + ros::Duration(5.0)); + while (ros::ok()) + { + if (ArenaCameraNode::setExposureValue(target_exposure, reached_exposure)) + { + // success if the delta is smaller then the exposure step + return true; + } + + if (ros::Time::now() > timeout) + { + break; + } + r.sleep(); + } + ROS_ERROR_STREAM("Error in setExposure(): Unable to set target" + << " exposure before timeout"); + return false; + } +} + +bool ArenaCameraNode::setExposureCallback(camera_control_msgs::SetExposure::Request& req, + camera_control_msgs::SetExposure::Response& res) +{ + res.success = setExposure(req.target_exposure, res.reached_exposure); + return true; +} + +bool ArenaCameraNode::setGainValue(const float& target_gain, float& reached_gain) +{ + try + { + Arena::SetNodeValue(pDevice_->GetNodeMap(), "GainAuto", "Off"); + + GenApi::CFloatPtr pGain = pDevice_->GetNodeMap()->GetNode("Gain"); + float truncated_gain = target_gain; + if (truncated_gain < pGain->GetMin()) + { + ROS_WARN_STREAM("Desired gain (" << target_gain << ") in " + << "percent out of range [0.0 - 1.0]! Setting to lower " + << "limit: 0.0"); + truncated_gain = pGain->GetMin(); + } + else if (truncated_gain > pGain->GetMax()) + { + ROS_WARN_STREAM("Desired gain (" << target_gain << ") in " + << "percent out of range [0.0 - 1.0]! Setting to upper " + << "limit: 1.0"); + truncated_gain = pGain->GetMax(); + } + + float gain_to_set = pGain->GetMin() + truncated_gain * (pGain->GetMax() - pGain->GetMin()); + pGain->SetValue(gain_to_set); + reached_gain = currentGain(); + } + catch (const GenICam::GenericException& e) + { + ROS_ERROR_STREAM("An exception while setting target gain to " << target_gain + << " occurred: " << e.GetDescription()); + return false; + } + return true; +} + +bool ArenaCameraNode::setGain(const float& target_gain, float& reached_gain) +{ + boost::lock_guard lock(grab_mutex_); + // if ( !arena_camera_->isReady() ) + // { + // ROS_WARN("Error in setGain(): arena_camera_ is not ready!"); + // return false; + // } + // + if (ArenaCameraNode::setGainValue(target_gain, reached_gain)) + { + return true; + } + else // retry till timeout + { + // wait for max 5s till the cam has updated the exposure + ros::Rate r(10.0); + ros::Time timeout(ros::Time::now() + ros::Duration(5.0)); + while (ros::ok()) + { + if (ArenaCameraNode::setGainValue(target_gain, reached_gain)) + { + return true; + } + + if (ros::Time::now() > timeout) + { + break; + } + r.sleep(); + } + ROS_ERROR_STREAM("Error in setGain(): Unable to set target " + << "gain before timeout"); + return false; + } +} + +bool ArenaCameraNode::setGainCallback(camera_control_msgs::SetGain::Request& req, + camera_control_msgs::SetGain::Response& res) +{ + res.success = setGain(req.target_gain, res.reached_gain); + return true; +} + +void disableAllRunningAutoBrightessFunctions() +{ + GenApi::CStringPtr pExposureAuto = pNodeMap_->GetNode("ExposureAuto"); + GenApi::CStringPtr pGainAuto = pNodeMap_->GetNode("GainAuto"); + if (!pExposureAuto || !GenApi::IsWritable(pExposureAuto) || !pGainAuto || !GenApi::IsWritable(pGainAuto)) + { + ROS_WARN_STREAM("Unable to disable auto brightness"); + return; + } + + else + { + Arena::SetNodeValue(pDevice_->GetNodeMap(), "ExposureAuto", "Off"); + Arena::SetNodeValue(pDevice_->GetNodeMap(), "GainAuto", "Off"); + } +} + +bool ArenaCameraNode::setGammaValue(const float& target_gamma, float& reached_gamma) +{ + // for GigE cameras you have to enable gamma first + + GenApi::CBooleanPtr pGammaEnable = pDevice_->GetNodeMap()->GetNode("GammaEnable"); + if (GenApi::IsWritable(pGammaEnable)) + { + pGammaEnable->SetValue(true); + } + + GenApi::CFloatPtr pGamma = pDevice_->GetNodeMap()->GetNode("Gamma"); + if (!pGamma || !GenApi::IsWritable(pGamma)) + { + reached_gamma = -1; + return true; + } + else + { + try + { + float gamma_to_set = target_gamma; + if (pGamma->GetMin() > gamma_to_set) + { + gamma_to_set = pGamma->GetMin(); + ROS_WARN_STREAM("Desired gamma unreachable! Setting to lower limit: " << gamma_to_set); + } + else if (pGamma->GetMax() < gamma_to_set) + { + gamma_to_set = pGamma->GetMax(); + ROS_WARN_STREAM("Desired gamma unreachable! Setting to upper limit: " << gamma_to_set); + } + pGamma->SetValue(gamma_to_set); + reached_gamma = currentGamma(); + } + catch (const GenICam::GenericException& e) + { + ROS_ERROR_STREAM("An exception while setting target gamma to " << target_gamma + << " occurred: " << e.GetDescription()); + return false; + } + } + return true; +} + +bool ArenaCameraNode::setGamma(const float& target_gamma, float& reached_gamma) +{ + boost::lock_guard lock(grab_mutex_); + if (ArenaCameraNode::setGammaValue(target_gamma, reached_gamma)) + { + return true; + } + else // retry till timeout + { + // wait for max 5s till the cam has updated the gamma value + ros::Rate r(10.0); + ros::Time timeout(ros::Time::now() + ros::Duration(5.0)); + while (ros::ok()) + { + if (ArenaCameraNode::setGammaValue(target_gamma, reached_gamma)) + { + return true; + } + + if (ros::Time::now() > timeout) + { + break; + } + r.sleep(); + } + ROS_ERROR_STREAM("Error in setGamma(): Unable to set target " + << "gamma before timeout"); + return false; + } + return true; +} + +bool ArenaCameraNode::setGammaCallback(camera_control_msgs::SetGamma::Request& req, + camera_control_msgs::SetGamma::Response& res) +{ + res.success = setGamma(req.target_gamma, res.reached_gamma); + return true; +} + +bool ArenaCameraNode::setBrightness(const int& target_brightness, int& reached_brightness, const bool& exposure_auto, + const bool& gain_auto) +{ + boost::lock_guard lock(grab_mutex_); + ros::Time begin = ros::Time::now(); // time measurement for the exposure search + + // brightness service can only work, if an image has already been grabbed, + // because it calculates the mean on the current image. The interface is + // ready if the grab-result-pointer of the first acquisition contains + // valid data + int target_brightness_co = std::min(255, target_brightness); + // smart brightness search initially sets the last rememberd exposure time + if (brightness_exp_lut_.at(target_brightness_co) != 0.0) + { + float reached_exp; + if (!setExposure(brightness_exp_lut_.at(target_brightness_co), reached_exp)) + { + ROS_WARN_STREAM("Tried to speed-up exposure search with initial" + << " guess, but setting the exposure failed!"); + } + else + { + ROS_DEBUG_STREAM("Speed-up exposure search with initial exposure" + << " guess of " << reached_exp); + } + } + + // get actual image -> fills img_raw_msg_.data vector + if (!grabImage()) + { + ROS_ERROR("Failed to grab image, can't calculate current brightness!"); + return false; + } + + // calculates current brightness by generating the mean over all pixels + // stored in img_raw_msg_.data vector + float current_brightness = calcCurrentBrightness(); + + ROS_DEBUG_STREAM("New brightness request for target brightness " << target_brightness_co + << ", current brightness = " << current_brightness); + + if (std::fabs(current_brightness - static_cast(target_brightness_co)) <= 1.0) + { + reached_brightness = static_cast(current_brightness); + ros::Time end = ros::Time::now(); + ROS_DEBUG_STREAM("Brightness reached without exposure search, duration: " << (end - begin).toSec()); + return true; // target brightness already reached + } + + // initially cancel all running exposure search by deactivating + // ExposureAuto & AutoGain + disableAllRunningAutoBrightessFunctions(); + + if (target_brightness_co <= 50) + { + // own binary-exp search: we need to have the upper bound -> ArenaAuto + // exposure to a initial start value of 50 provides it + if (brightness_exp_lut_.at(50) != 0.0) + { + float reached_exp; + if (!setExposure(brightness_exp_lut_.at(50), reached_exp)) + { + ROS_WARN_STREAM("Tried to speed-up exposure search with initial" + << " guess, but setting the exposure failed!"); + } + else + { + ROS_DEBUG_STREAM("Speed-up exposure search with initial exposure" + << " guess of " << reached_exp); + } + } + } + + if (!exposure_auto && !gain_auto) + { + ROS_WARN_STREAM("Neither Auto Exposure Time ('exposure_auto') nor Auto " + << "Gain ('gain_auto') are enabled! Hence gain and exposure time " + << "are assumed to be fix and the target brightness (" << target_brightness_co + << ") can not be reached!"); + return false; + } + + bool is_brightness_reached = false; + size_t fail_safe_ctr = 0; + size_t fail_safe_ctr_limit = 10; + + float last_brightness = std::numeric_limits::max(); + + // timeout for the exposure search -> need more time for great exposure values + ros::Time start_time = ros::Time::now(); + ros::Time timeout = start_time; + if (target_brightness_co < 205) + { + timeout += ros::Duration(arena_camera_parameter_set_.exposure_search_timeout_); + } + else + { + timeout += ros::Duration(10.0 + arena_camera_parameter_set_.exposure_search_timeout_); + } + + while (ros::ok()) + { + // calling setBrightness in every cycle would not be necessary for the arena + // auto brightness search. But for the case that the target brightness is + // out of the arena range which is from [50 - 205] a binary exposure search + // will be executed where we have to update the search parameter in every + // cycle if ( !arena_camera_->setBrightness(target_brightness_co, + // current_brightness, + // exposure_auto, + // gain_auto) ) + // { + // disableAllRunningAutoBrightessFunctions(); + // break; + // } + + if (!grabImage()) + { + return false; + } + + // if ( arena_camera_->isArenaAutoBrightnessFunctionRunning() ) + // { + // // do nothing if the arena auto function is running, we need to + // // wait till it's finished + // /* + // ROS_DEBUG_STREAM("ArenaAutoBrightnessFunction still running! " + // << " Current brightness: " << calcCurrentBrightness() + // << ", current exposure: " << currentExposure()); + // */ + // continue; + // } + + current_brightness = calcCurrentBrightness(); + // is_brightness_reached = fabs(current_brightness - + // static_cast(target_brightness_co)) + // < arena_camera_->maxBrightnessTolerance(); + // + // if ( is_brightness_reached ) + // { + // disableAllRunningAutoBrightessFunctions(); + // break; + // } + + if (std::fabs(last_brightness - current_brightness) <= 1.0) + { + fail_safe_ctr++; + } + else + { + fail_safe_ctr = 0; + } + + last_brightness = current_brightness; + + if ((fail_safe_ctr > fail_safe_ctr_limit) && !is_brightness_reached) + { + ROS_WARN_STREAM("Seems like the desired brightness (" + << target_brightness_co << ") is not reachable! Stuck at brightness " << current_brightness + << " and exposure " << currentExposure() << "us"); + disableAllRunningAutoBrightessFunctions(); + reached_brightness = static_cast(current_brightness); + return false; + } + + if (ros::Time::now() > timeout) + { + // cancel all running brightness search by deactivating ExposureAuto + disableAllRunningAutoBrightessFunctions(); + ROS_WARN_STREAM("Did not reach the target brightness before " + << "timeout of " << (timeout - start_time).sec << " sec! Stuck at brightness " + << current_brightness << " and exposure " << currentExposure() << "us"); + reached_brightness = static_cast(current_brightness); + return false; + } + } + + ROS_DEBUG_STREAM("Finally reached brightness: " << current_brightness); + reached_brightness = static_cast(current_brightness); + + // store reached brightness - exposure tuple for next times search + if (is_brightness_reached) + { + if (brightness_exp_lut_.at(reached_brightness) == 0.0) + { + brightness_exp_lut_.at(reached_brightness) = currentExposure(); + } + else + { + brightness_exp_lut_.at(reached_brightness) += currentExposure(); + brightness_exp_lut_.at(reached_brightness) *= 0.5; + } + if (brightness_exp_lut_.at(target_brightness_co) == 0.0) + { + brightness_exp_lut_.at(target_brightness_co) = currentExposure(); + } + else + { + brightness_exp_lut_.at(target_brightness_co) += currentExposure(); + brightness_exp_lut_.at(target_brightness_co) *= 0.5; + } + } + ros::Time end = ros::Time::now(); + ROS_DEBUG_STREAM("Brightness search duration: " << (end - begin).toSec()); + return is_brightness_reached; +} + +bool ArenaCameraNode::setBrightnessCallback(camera_control_msgs::SetBrightness::Request& req, + camera_control_msgs::SetBrightness::Response& res) +{ + res.success = setBrightness(req.target_brightness, res.reached_brightness, req.exposure_auto, req.gain_auto); + if (req.brightness_continuous) + { + if (req.exposure_auto) + { + // arena_camera_->enableContinuousAutoExposure(); + } + if (req.gain_auto) + { + // arena_camera_->enableContinuousAutoGain(); + } + } + res.reached_exposure_time = currentExposure(); + res.reached_gain_value = currentGain(); + return true; +} + +void ArenaCameraNode::setupSamplingIndices(std::vector& indices, std::size_t rows, std::size_t cols, + int downsampling_factor) +{ + indices.clear(); + std::size_t min_window_height = static_cast(rows) / static_cast(downsampling_factor); + cv::Point2i start_pt(0, 0); + cv::Point2i end_pt(cols, rows); + // add the iamge center point only once + sampling_indices_.push_back(0.5 * rows * cols); + genSamplingIndicesRec(indices, min_window_height, start_pt, end_pt); + std::sort(indices.begin(), indices.end()); + return; +} + +void ArenaCameraNode::genSamplingIndicesRec(std::vector& indices, const std::size_t& min_window_height, + const cv::Point2i& s, // start + const cv::Point2i& e) // end +{ + if (static_cast(std::abs(e.y - s.y)) <= min_window_height) + { + return; // abort criteria -> shrinked window has the min_col_size + } + /* + * sampled img: point: idx: + * s 0 0 0 0 0 0 a) [(e.x-s.x)*0.5, (e.y-s.y)*0.5] a.x*a.y*0.5 + * 0 0 0 d 0 0 0 b) [a.x, 1.5*a.y] b.y*img_rows+b.x + * 0 0 0 0 0 0 0 c) [0.5*a.x, a.y] c.y*img_rows+c.x + * 0 c 0 a 0 f 0 d) [a.x, 0.5*a.y] d.y*img_rows+d.x + * 0 0 0 0 0 0 0 f) [1.5*a.x, a.y] f.y*img_rows+f.x + * 0 0 0 b 0 0 0 + * 0 0 0 0 0 0 e + */ + cv::Point2i a, b, c, d, f, delta; + a = s + 0.5 * (e - s); // center point + delta = 0.5 * (e - s); + b = s + cv::Point2i(delta.x, 1.5 * delta.y); + c = s + cv::Point2i(0.5 * delta.x, delta.y); + d = s + cv::Point2i(delta.x, 0.5 * delta.y); + f = s + cv::Point2i(1.5 * delta.x, delta.y); + indices.push_back(b.y * pImage_->GetWidth() + b.x); + indices.push_back(c.y * pImage_->GetWidth() + c.x); + indices.push_back(d.y * pImage_->GetWidth() + d.x); + indices.push_back(f.y * pImage_->GetWidth() + f.x); + genSamplingIndicesRec(indices, min_window_height, s, a); + genSamplingIndicesRec(indices, min_window_height, a, e); + genSamplingIndicesRec(indices, min_window_height, cv::Point2i(s.x, a.y), cv::Point2i(a.x, e.y)); + genSamplingIndicesRec(indices, min_window_height, cv::Point2i(a.x, s.y), cv::Point2i(e.x, a.y)); + return; +} + +float ArenaCameraNode::calcCurrentBrightness() +{ + boost::lock_guard lock(grab_mutex_); + if (img_raw_msg_.data.empty()) + { + return 0.0; + } + float sum = 0.0; + if (sensor_msgs::image_encodings::isMono(img_raw_msg_.encoding)) + { + // The mean brightness is calculated using a subset of all pixels + for (const std::size_t& idx : sampling_indices_) + { + sum += img_raw_msg_.data.at(idx); + } + if (sum > 0.0) + { + sum /= static_cast(sampling_indices_.size()); + } + } + else + { + // The mean brightness is calculated using all pixels and all channels + sum = std::accumulate(img_raw_msg_.data.begin(), img_raw_msg_.data.end(), 0); + if (sum > 0.0) + { + sum /= static_cast(img_raw_msg_.data.size()); + } + } + return sum; +} + +bool ArenaCameraNode::setSleepingCallback(camera_control_msgs::SetSleeping::Request& req, + camera_control_msgs::SetSleeping::Response& res) +{ + is_sleeping_ = req.set_sleeping; + + if (is_sleeping_) + { + ROS_INFO("Setting Arena Camera Node to sleep..."); + } + else + { + ROS_INFO("Arena Camera Node continues grabbing"); + } + + res.success = true; + return true; +} + +bool ArenaCameraNode::isSleeping() +{ + return is_sleeping_; +} + +ArenaCameraNode::~ArenaCameraNode() +{ + if (pDevice_ != nullptr) + { + pSystem_->DestroyDevice(pDevice_); + } + + if (pSystem_ != nullptr) + { + Arena::CloseSystem(pSystem_); + } + + if (it_) + { + delete it_; + it_ = nullptr; + } + if (grab_imgs_rect_as_) + { + grab_imgs_rect_as_->shutdown(); + delete grab_imgs_rect_as_; + grab_imgs_rect_as_ = nullptr; + } + + if (img_rect_pub_) + { + delete img_rect_pub_; + img_rect_pub_ = nullptr; + } + + if (cv_bridge_img_rect_) + { + delete cv_bridge_img_rect_; + cv_bridge_img_rect_ = nullptr; + } + + if (pinhole_model_) + { + delete pinhole_model_; + pinhole_model_ = nullptr; + } +} + +} // namespace arena_camera diff --git a/catkin_ws/src/arena_camera/src/arena_camera_parameter.cpp b/catkin_ws/src/arena_camera/src/arena_camera_parameter.cpp new file mode 100644 index 00000000..058ba080 --- /dev/null +++ b/catkin_ws/src/arena_camera/src/arena_camera_parameter.cpp @@ -0,0 +1,367 @@ +/****************************************************************************** + * Software License Agreement (BSD License) + * + * Copyright (C) 2016, Magazino GmbH. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of Magazino GmbH nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include + +namespace arena_camera +{ +ArenaCameraParameter::ArenaCameraParameter() + : camera_frame_("arena_camera") + , device_user_id_("") + , frame_rate_(5.0) + , camera_info_url_("") + , image_encoding_("") + , image_encoding_given_(false) + , binning_x_(1) + , binning_y_(1) + , binning_x_given_(false) + , binning_y_given_(false) + , downsampling_factor_exp_search_(1) + , + // ########################## + // image intensity settings + // ########################## + exposure_(10000.0) + , exposure_given_(false) + , gain_(0.5) + , gain_given_(false) + , gamma_(1.0) + , gamma_given_(false) + , brightness_(100) + , brightness_given_(false) + , brightness_continuous_(false) + , exposure_auto_(true) + , gain_auto_(true) + , + // ######################### + exposure_search_timeout_(5.) + , auto_exp_upper_lim_(0.0) + , mtu_size_(3000) + , inter_pkg_delay_(1000) + , shutter_mode_(SM_DEFAULT) + , auto_flash_(false) +{ +} + +ArenaCameraParameter::~ArenaCameraParameter() +{ +} + +void ArenaCameraParameter::readFromRosParameterServer(const ros::NodeHandle& nh) +{ + nh.param("camera_frame", camera_frame_, "arena_camera"); + + nh.param("device_user_id", device_user_id_, ""); + + if (nh.hasParam("frame_rate")) + { + nh.getParam("frame_rate", frame_rate_); + } + + nh.param("camera_info_url", camera_info_url_, ""); + if (nh.hasParam("camera_info_url")) + { + nh.getParam("camera_info_url", camera_info_url_); + } + + binning_x_given_ = nh.hasParam("binning_x"); + if (binning_x_given_) + { + int binning_x; + nh.getParam("binning_x", binning_x); + std::cout << "binning x is given and has value " << binning_x << std::endl; + if (binning_x > 32 || binning_x < 0) + { + ROS_WARN_STREAM("Desired horizontal binning_x factor not in valid " + << "range! Binning x = " << binning_x << ". Will reset it to " + << "default value (1)"); + binning_x_given_ = false; + } + else + { + binning_x_ = static_cast(binning_x); + } + } + binning_y_given_ = nh.hasParam("binning_y"); + if (binning_y_given_) + { + int binning_y; + nh.getParam("binning_y", binning_y); + std::cout << "binning y is given and has value " << binning_y << std::endl; + if (binning_y > 32 || binning_y < 0) + { + ROS_WARN_STREAM("Desired vertical binning_y factor not in valid " + << "range! Binning y = " << binning_y << ". Will reset it to " + << "default value (1)"); + binning_y_given_ = false; + } + else + { + binning_y_ = static_cast(binning_y); + } + } + nh.param("downsampling_factor_exposure_search", downsampling_factor_exp_search_, 20); + image_encoding_given_ = nh.hasParam("image_encoding"); + if (nh.hasParam("image_encoding")) + { + std::string encoding; + nh.getParam("image_encoding", encoding); + // if (!encoding.empty() && + // !sensor_msgs::image_encodings::isMono(encoding) && + // !sensor_msgs::image_encodings::isColor(encoding) && + // !sensor_msgs::image_encodings::isBayer(encoding) && + // encoding != sensor_msgs::image_encodings::YUV422) + // { + // ROS_WARN_STREAM("Desired image encoding parameter: '" << encoding + // << "' is not part of the 'sensor_msgs/image_encodings.h' + // list!" + // << " Will not set encoding"); + // encoding = std::string(""); + // } + image_encoding_ = encoding; + } + + // ########################## + // image intensity settings + // ########################## + + // > 0: Exposure time in microseconds + exposure_given_ = nh.hasParam("exposure"); + if (exposure_given_) + { + nh.getParam("exposure", exposure_); + std::cout << "exposure is given and has value " << exposure_ << std::endl; + } + + gain_given_ = nh.hasParam("gain"); + if (gain_given_) + { + nh.getParam("gain", gain_); + std::cout << "gain is given and has value " << gain_ << std::endl; + } + + gamma_given_ = nh.hasParam("gamma"); + if (gamma_given_) + { + nh.getParam("gamma", gamma_); + std::cout << "gamma is given and has value " << gamma_ << std::endl; + } + + brightness_given_ = nh.hasParam("brightness"); + if (brightness_given_) + { + nh.getParam("brightness", brightness_); + std::cout << "brightness is given and has value " << brightness_ << std::endl; + if (gain_given_ && exposure_given_) + { + ROS_WARN_STREAM("Gain ('gain') and Exposure Time ('exposure') " + << "are given as startup ros-parameter and hence assumed to be " + << "fix! The desired brightness (" << brightness_ << ") can't " + << "be reached! Will ignore the brightness by only " + << "setting gain and exposure . . ."); + brightness_given_ = false; + } + else + { + if (nh.hasParam("brightness_continuous")) + { + nh.getParam("brightness_continuous", brightness_continuous_); + std::cout << "brightness is continuous" << std::endl; + } + if (nh.hasParam("exposure_auto")) + { + nh.getParam("exposure_auto", exposure_auto_); + std::cout << "exposure is set to auto" << std::endl; + } + if (nh.hasParam("gain_auto")) + { + nh.getParam("gain_auto", gain_auto_); + std::cout << "gain is set to auto" << std::endl; + } + } + } + // ########################## + + nh.param("exposure_search_timeout", exposure_search_timeout_, 5.); + nh.param("auto_exposure_upper_limit", auto_exp_upper_lim_, 10000000.); + + if (nh.hasParam("gige/mtu_size")) + { + nh.getParam("gige/mtu_size", mtu_size_); + } + + if (nh.hasParam("gige/inter_pkg_delay")) + { + nh.getParam("gige/inter_pkg_delay", inter_pkg_delay_); + } + + std::string shutter_param_string; + nh.param("shutter_mode", shutter_param_string, ""); + if (shutter_param_string == "rolling") + { + shutter_mode_ = SM_ROLLING; + } + else if (shutter_param_string == "global") + { + shutter_mode_ = SM_GLOBAL; + } + else if (shutter_param_string == "global_reset") + { + shutter_mode_ = SM_GLOBAL_RESET_RELEASE; + } + else + { + shutter_mode_ = SM_DEFAULT; + } + + nh.param("auto_flash", auto_flash_, false); + nh.param("auto_flash_line_2", auto_flash_line_2_, true); + nh.param("auto_flash_line_3", auto_flash_line_3_, true); + + ROS_WARN("Autoflash: %i, line2: %i , line3: %i ", auto_flash_, auto_flash_line_2_, auto_flash_line_3_); + validateParameterSet(nh); + return; +} + +void ArenaCameraParameter::adaptDeviceUserId(const ros::NodeHandle& nh, const std::string& device_user_id) +{ + device_user_id_ = device_user_id; + nh.setParam("device_user_id", device_user_id_); +} + +void ArenaCameraParameter::validateParameterSet(const ros::NodeHandle& nh) +{ + if (!device_user_id_.empty()) + { + ROS_INFO_STREAM("Trying to open the following camera: " << device_user_id_.c_str()); + } + else + { + ROS_INFO_STREAM("No Device User ID set -> Will open the camera device " + << "found first"); + } + + if (frame_rate_ < 0 && frame_rate_ != -1) + { + ROS_WARN_STREAM("Unexpected frame rate (" << frame_rate_ << "). Will " + << "reset it to default value which is 5 Hz"); + frame_rate_ = 5.0; + nh.setParam("frame_rate", frame_rate_); + } + + if (exposure_given_ && (exposure_ <= 0.0 || exposure_ > 1e7)) + { + ROS_WARN_STREAM("Desired exposure measured in microseconds not in " + << "valid range! Exposure time = " << exposure_ << ". Will " + << "reset it to default value!"); + exposure_given_ = false; + } + + if (gain_given_ && (gain_ < 0.0 || gain_ > 1.0)) + { + ROS_WARN_STREAM("Desired gain (in percent) not in allowed range! " + << "Gain = " << gain_ << ". Will reset it to default value!"); + gain_given_ = false; + } + + if (brightness_given_ && (brightness_ < 0.0 || brightness_ > 255)) + { + ROS_WARN_STREAM("Desired brightness not in allowed range [0 - 255]! " + << "Brightness = " << brightness_ << ". Will reset it to " + << "default value!"); + brightness_given_ = false; + } + + if (exposure_search_timeout_ < 5.) + { + ROS_WARN_STREAM("Low timeout for exposure search detected! Exposure " + << "search may fail."); + } + return; +} + +const std::string& ArenaCameraParameter::deviceUserID() const +{ + return device_user_id_; +} + +std::string ArenaCameraParameter::shutterModeString() const +{ + if (shutter_mode_ == SM_ROLLING) + { + return "rolling"; + } + else if (shutter_mode_ == SM_GLOBAL) + { + return "global"; + } + else if (shutter_mode_ == SM_GLOBAL_RESET_RELEASE) + { + return "global_reset"; + } + else + { + return "default_shutter_mode"; + } +} + +const std::string& ArenaCameraParameter::imageEncoding() const +{ + return image_encoding_; +} + +const std::string& ArenaCameraParameter::cameraFrame() const +{ + return camera_frame_; +} + +const double& ArenaCameraParameter::frameRate() const +{ + return frame_rate_; +} + +void ArenaCameraParameter::setFrameRate(const ros::NodeHandle& nh, const double& frame_rate) +{ + frame_rate_ = frame_rate; + nh.setParam("frame_rate", frame_rate_); +} + +const std::string& ArenaCameraParameter::cameraInfoURL() const +{ + return camera_info_url_; +} + +void ArenaCameraParameter::setCameraInfoURL(const ros::NodeHandle& nh, const std::string& camera_info_url) +{ + camera_info_url_ = camera_info_url; + nh.setParam("camera_info_url", camera_info_url_); +} + +} // namespace arena_camera diff --git a/catkin_ws/src/arena_camera/src/encoding_conversions.cpp b/catkin_ws/src/arena_camera/src/encoding_conversions.cpp new file mode 100644 index 00000000..a0497c22 --- /dev/null +++ b/catkin_ws/src/arena_camera/src/encoding_conversions.cpp @@ -0,0 +1,198 @@ +/****************************************************************************** + * Software License Agreement (BSD License) + * + * Copyright (C) 2016, Magazino GmbH. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of Magazino GmbH nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#include +#include +#include + +namespace arena_camera +{ +namespace encoding_conversions +{ +bool ros2GenAPI(const std::string& ros_enc, std::string& gen_api_enc) +{ + /* + * http://docs.ros.org/kinetic/api/sensor_msgs/html/image__encodings_8h_source.html + */ + if (ros_enc == sensor_msgs::image_encodings::MONO8) + { + gen_api_enc = "Mono8"; + } + else if (ros_enc == sensor_msgs::image_encodings::MONO16) + { + gen_api_enc = "Mono16"; + } + else if (ros_enc == sensor_msgs::image_encodings::CONFIDENCE16) + { + gen_api_enc = "Confidence16"; + } + else if (ros_enc == sensor_msgs::image_encodings::COORD3D_ABC16) + { + gen_api_enc = "Coord3D_ABC16"; + } + else if (ros_enc == sensor_msgs::image_encodings::COORD3D_ABCY16) + { + gen_api_enc = "Coord3D_ABCY16"; + } + else if (ros_enc == sensor_msgs::image_encodings::BGR8) + { + gen_api_enc = "BGR8"; + } + else if (ros_enc == sensor_msgs::image_encodings::RGB8) + { + gen_api_enc = "RGB8"; + } + else if (ros_enc == sensor_msgs::image_encodings::BAYER_BGGR8) + { + gen_api_enc = "BayerBG8"; + } + else if (ros_enc == sensor_msgs::image_encodings::BAYER_GBRG8) + { + gen_api_enc = "BayerGB8"; + } + else if (ros_enc == sensor_msgs::image_encodings::BAYER_RGGB8) + { + gen_api_enc = "BayerRG8"; + } + else if (ros_enc == sensor_msgs::image_encodings::BAYER_RGGB16) + { + gen_api_enc = "BayerRG16"; + } + /* + else if ( ros_enc == sensor_msgs::image_encodings::YUV422 ) + { + // This is the UYVY version of YUV422 codec http://www.fourcc.org/yuv.php#UYVY + // with an 8-bit depth + gen_api_enc = "YCbCr422_8"; + } + */ + else + { + /* No gen-api pendant existant for following ROS-encodings: + * - sensor_msgs::image_encodings::MONO16 + * - sensor_msgs::image_encodings::BGRA8 + * - sensor_msgs::image_encodings::BGR16 + * - sensor_msgs::image_encodings::BGRA16 + * - sensor_msgs::image_encodings::RGBA8 + * - sensor_msgs::image_encodings::RGB16 + * - sensor_msgs::image_encodings::RGBA16 + * - sensor_msgs::image_encodings::BAYER_BGGR16 + * - sensor_msgs::image_encodings::BAYER_GBRG16 + * - sensor_msgs::image_encodings::BAYER_GRBG16 + * - sensor_msgs::image_encodings::BAYER_GRBG8 + * - sensor_msgs::image_encodings::YUV422 + */ + return false; + } + return true; +} + +bool genAPI2Ros(const std::string& gen_api_enc, std::string& ros_enc) +{ + if (gen_api_enc == "Mono8") + { + ros_enc = sensor_msgs::image_encodings::MONO8; + } + else if (gen_api_enc == "Mono16") + { + ros_enc = sensor_msgs::image_encodings::TYPE_16UC1; + } + else if (gen_api_enc == "Confidence16") + { + ros_enc = sensor_msgs::image_encodings::TYPE_16UC1; + } + else if (gen_api_enc == "Coord3D_ABC16") + { + ros_enc = sensor_msgs::image_encodings::TYPE_16UC3; + } + else if (gen_api_enc == "Coord3D_ABCY16") + { + ros_enc = sensor_msgs::image_encodings::TYPE_16UC4; + } + else if (gen_api_enc == "BGR8") + { + ros_enc = sensor_msgs::image_encodings::BGR8; + } + else if (gen_api_enc == "RGB8") + { + ros_enc = sensor_msgs::image_encodings::RGB8; + } + else if (gen_api_enc == "BayerBG8") + { + ros_enc = sensor_msgs::image_encodings::BAYER_BGGR8; + } + else if (gen_api_enc == "BayerGB8") + { + ros_enc = sensor_msgs::image_encodings::BAYER_GBRG8; + } + else if (gen_api_enc == "BayerRG8") + { + ros_enc = sensor_msgs::image_encodings::BAYER_RGGB8; + } + else if (gen_api_enc == "BayerRG16") + { + ros_enc = sensor_msgs::image_encodings::BAYER_RGGB16; + } + /* + else if ( gen_api_enc == "YCbCr422_8" ) + { + ros_enc = sensor_msgs::image_encodings::YUV422; + } + */ + else + { + /* Unsupported are: + * - Mono10 + * - Mono10p + * - Mono12 + * - Mono12p + * - BayerGR10 + * - BayerGR10p + * - BayerRG10 + * - BayerRG10p + * - BayerGB10 + * - BayerGB10p + * - BayerBG10 + * - BayerBG10p + * - BayerGR12 + * - BayerGR12p + * - BayerRG12 + * - BayerRG12p + * - BayerGB12 + * - BayerGB12p + * - BayerBG12 + * - BayerBG12p + * - YCbCr422_8 + */ + return false; + } + return true; +} +} // namespace encoding_conversions +} // namespace arena_camera diff --git a/src/pylon_camera/main.cpp b/catkin_ws/src/arena_camera/src/main.cpp similarity index 76% rename from src/pylon_camera/main.cpp rename to catkin_ws/src/arena_camera/src/main.cpp index 12b44bc9..61cca0a0 100644 --- a/src/pylon_camera/main.cpp +++ b/catkin_ws/src/arena_camera/src/main.cpp @@ -32,31 +32,31 @@ * engelhard@magazino.eu */ +#include +#include #include #include -#include - int main(int argc, char **argv) { - ros::init(argc, argv, "pylon_camera_node"); + ros::init(argc, argv, "arena_camera_node"); - pylon_camera::PylonCameraNode pylon_camera_node; + arena_camera::ArenaCameraNode arena_camera_node; - ros::Rate r(pylon_camera_node.frameRate()); + ros::Rate r(arena_camera_node.frameRate()); - ROS_INFO_STREAM("Start image grabbing if node connects to topic with " - << "a frame_rate of: " << pylon_camera_node.frameRate() << " Hz"); + ROS_INFO_STREAM("Start image grabbing if node connects to topic with " + << "a frame_rate of: " << arena_camera_node.frameRate() << " Hz"); - // Main thread and brightness-service thread - boost::thread th(boost::bind(&ros::spin)); + // Main thread and brightness-service thread + boost::thread th(boost::bind(&ros::spin)); - while ( ros::ok() ) - { - pylon_camera_node.spin(); - r.sleep(); - } + while (ros::ok()) + { + arena_camera_node.spin(); + r.sleep(); + } - ROS_INFO("Terminate PylonCameraNode"); - return EXIT_SUCCESS; + ROS_INFO("Terminate ArenaCameraNode"); + return EXIT_SUCCESS; } diff --git a/catkin_ws/src/arena_camera/src/write_device_user_id_to_camera.cpp b/catkin_ws/src/arena_camera/src/write_device_user_id_to_camera.cpp new file mode 100644 index 00000000..408de590 --- /dev/null +++ b/catkin_ws/src/arena_camera/src/write_device_user_id_to_camera.cpp @@ -0,0 +1,93 @@ +/****************************************************************************** + * Software License Agreement (BSD License) + * + * Copyright (C) 2016, Magazino GmbH. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of Magazino GmbH nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +/* +This program will open a Lucid Arena Camera and write a desired camera id. +*/ + +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + std::cerr << "ERROR: No device_user_id set!" << std::endl; + std::cout << "USAGE: write_device_user_id_to_camera DEVICE_USER_ID" << std::endl; + return 1; + } + + std::string desired_device_user_id(reinterpret_cast(argv[1])); + + if (desired_device_user_id.empty()) + { + std::cout << "ERROR:" << std::endl; + std::cout << "Your desired device_user_id is empty!" << std::endl; + return 2; + } + try + { + // Create an instant camera object with the camera device found first. + Arena::ISystem* pSystem = Arena::OpenSystem(); + pSystem->UpdateDevices(100); + + std::vector deviceInfos = pSystem->GetDevices(); + if (deviceInfos.size() == 0) + + { + std::cout << "ERROR:" << std::endl; + std::cout << "No camera connected, unable to write to camera" << std::endl; + Arena::CloseSystem(pSystem); + return 2; + } + + Arena::IDevice* pDevice = pSystem->CreateDevice(deviceInfos[0]); + + GenApi::INodeMap* pNodeMap = pDevice->GetNodeMap(); + GenApi::CStringPtr current_device_user_id = pNodeMap->GetNode("DeviceUserID"); + current_device_user_id->SetValue(GenICam::gcstring(desired_device_user_id.c_str())); + + std::cout << "Successfully wrote new device user id: " << current_device_user_id->GetValue() << " to the camera " + << deviceInfos[0].ModelName() << std::endl; + + pSystem->DestroyDevice(pDevice); + Arena::CloseSystem(pSystem); + } + + catch (GenICam::GenericException& e) + { + // Error handling. + std::cerr << "An exception occurred." << std::endl << e.GetDescription() << std::endl; + return 3; + } + + return 0; +} diff --git a/catkin_ws/src/camera_control_msgs/.gitignore b/catkin_ws/src/camera_control_msgs/.gitignore new file mode 100644 index 00000000..28bf2a06 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/.gitignore @@ -0,0 +1,13 @@ +############# +## folders ## +############# +build + +############# +## files ## +############# +*.*~ +*.*user.* +*.user +*.autosave +CATKIN_IGNORE diff --git a/catkin_ws/src/camera_control_msgs/CMakeLists.txt b/catkin_ws/src/camera_control_msgs/CMakeLists.txt new file mode 100644 index 00000000..f02f75bf --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8.3) +project(camera_control_msgs) + +set(MSG_DEPS + actionlib_msgs + sensor_msgs +) + +find_package(catkin REQUIRED COMPONENTS ${MSG_DEPS} message_generation) + +add_action_files(DIRECTORY action) +add_service_files(DIRECTORY srv) + +generate_messages(DEPENDENCIES ${MSG_DEPS}) +catkin_package(CATKIN_DEPENDS ${MSG_DEPS} message_runtime) diff --git a/catkin_ws/src/camera_control_msgs/LICENSE.rst b/catkin_ws/src/camera_control_msgs/LICENSE.rst new file mode 100644 index 00000000..4a118dde --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/LICENSE.rst @@ -0,0 +1,27 @@ +Software License Agreement (BSD License) + +Copyright (C) 2018, Magazino GmbH. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the names of Magazino GmbH nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/catkin_ws/src/camera_control_msgs/README.rst b/catkin_ws/src/camera_control_msgs/README.rst new file mode 100644 index 00000000..122e15f9 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/README.rst @@ -0,0 +1,44 @@ +==== +**Message package for camera drivers** +==== +**developed by Magazino GmbH, using the arena Software Camera Suite by Lucid Vision Labs** + +.. image:: https://raw.githubusercontent.com/magazino/arena_camera/indigo-devel/wiki_imgs/logos.png + :width: 130 % + :align: center + +This package offers many service and action definitions to control a camera. + + + +****** +**Services** +****** +- **SetBinning** + Binning factor to get downsampled images. It refers here to any camera setting which combines rectangular neighborhoods of pixels into larger 'super-pixels'. It reduces the resolution of the output image to (width / binning_x) x (height / binning_y). The default values binning_x = binning_y = 0 are considered the same as binning_x = binning_y = 1 (no subsampling). Calling this service with target binning values will change the binning entry in the published camera_info_msg of the camera. + +- **SetBrightness** + The target brightness, which is average intensity values of the images. It depends the exposure time as well as the gain setting. + The brightness_continuous flag controls the auto brightness function. If it is set to false, the given brightness will only be reached once. + Hence changing light conditions lead to changing brightness values. If it is set to true, the given brightness will be reached continuously, + trying to adapt to changing light conditions. The 'brightness_contunuous' mode is is only possible for values in the possible auto range of the camera which is e.g. [50 - 205]. + If the camera should try reach or keep the desired brightness, hence adapting to changing light conditions, at least one of the following flags **MUST** be set. If both are set, the interface will use the profile that tries to keep the gain at minimum to reduce white noise. 'exposure_auto' will adapt the exposure time to reach the brightness, wheras 'gain_auto' does so by adapting the gain. + +- **SetExposure** + The target exposure time measured in microseconds. If the limits were exceeded, the desired exposure time will be truncated. + +- **SetGain** + The target gain in percent of the maximal value the camera supports. + +- **SetGamma** + The target gamma correction of pixel intensity. Adjusts the brightness of the pixel values output by the camera's sensor to account for a non-linearity in the human perception of brightness or of the display system (such as CRT). + +- **SetSleeping** + If the camera runs in topic mode (continuously publishing images over the topics respecting the desired frame_rate) this service offers the posibillity to pause the image acquisition. To restart the grabbing, this service should be called again with set_sleeping set to false + + +****** +**Actions** +****** +- **GrabImages** + Action to grab one or several images with desired image-intensity-setting each. diff --git a/catkin_ws/src/camera_control_msgs/action/GrabAndSaveImage.action b/catkin_ws/src/camera_control_msgs/action/GrabAndSaveImage.action new file mode 100644 index 00000000..4f65a7c0 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/action/GrabAndSaveImage.action @@ -0,0 +1,77 @@ +# The 'GrabAndSaveImageAction' has a similar goal as the 'GrabImagesAction' in +# case of only grabbing one image. It additionally contains a string describing +# the full storage path of the image to be captured. +# It wont provide the grabbed image via the result topic. Instead it only +# returns a flag indicating the success. + +########################################## +################## GOAL ################## +########################################## + +# Flag which indicates if the exposure time is provided and hence should be +# set before grabbing +bool exposure_given + +# Only relevant, if exposure_given is true: +# The target exposure time in microseconds. This values can be overriden from +# the brightness search, in case that the flag exposure_fixed is not true. +float32 exposure_time + +# Flag which indicates if the gain is provided and hence should be set before +# grabbing +bool gain_given + +# Only relevant, if gain_given is true: +# The target gain in percent of the maximal value the camera supports. +# For USB-Cameras, the gain is in dB, for GigE-Cameras it is given in so +# called 'device specific units'. This value can be overriden from the +# brightness search, in case that the gain_fixed flag is set to false. +float32 gain_value + +# Flag which indicates if the gamma value is provided and hence should be set +# before grabbing +bool gamma_given + +# Only relevant, if gain_given is true: +# Gamma correction of pixel intensity. +# Adjusts the brightness of the pixel values output by the camera's sensor +# to account for a non-linearity in the human perception of brightness or +# of the display system (such as CRT). +float32 gamma_value + +# Flag which indicates if the brightness value is provided and hence should +# be set before grabbing +bool brightness_given + +# Only relevant, if brightness_given is true: +# The average intensity value of the resulting image. It depends the exposure +# time as well as the gain setting. +float32 brightness_value + +# Only relevant, if brightness_given is true: +# If the camera should try reach the desired brightness, at least one of the +# following flags MUST be set. If both are set, the interface will use the +# profile that tries to keep the gain at minimum to reduce white noise. +# 'exposure_auto' will adapt the exposure time to reach the brightness, wheras +# 'gain_auto' does so by adapting the gain. If one of these flags is set to +# false, the connected property will be kept fix. +# In most of the cases trying to reach a target brightness only by varying the +# gain and keeping the exposure time fix is not a good approach, because the +# exposure range is many times higher than the gain range. +bool exposure_auto +bool gain_auto + +# File path and image name (including file-extension) to store the grabbed +# image +string img_storage_path_and_name +--- + +########################################## +################# RESULT ################# +########################################## + +# Flag which indicates the success of ithe grabbing and storage action. +bool success +--- + +# No Feedback diff --git a/catkin_ws/src/camera_control_msgs/action/GrabHDRImage.action b/catkin_ws/src/camera_control_msgs/action/GrabHDRImage.action new file mode 100644 index 00000000..b12ff1f3 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/action/GrabHDRImage.action @@ -0,0 +1,99 @@ +########################################## +################## GOAL ################## +########################################## + +# Flag which indicates if the exposure times are provided and hence should be +# set before grabbing +bool exposure_given + +# The list of target exposure time in microseconds. +# It is possible to grab only one image as well as several images with +# different exposure times. This values can be overriden from the brightness +# search, in case that the flag exposure_fixed is not true. +# The interface will only try to set these values if the exposure_given flag is +# set to true. +float32[] exposure_times + +# If the exposure_fixed flag is set, the exposure time will stay fix in +# case of a brightness search. Hence the target brightness will be reached +# only by varying the gain +bool exposure_fixed + + +# Flag which indicates if the brightness values are provided and hence should +# be set before grabbing +bool brightness_given + +# The average intensity values of the images. It depends the exposure time +# as well as the gain setting. The interface will only try to reach this +# value if the brightness_given flag is set to true. +float32[] brightness_values + +# The brightness_continuous flag controls the auto brightness function. +# If it is set to false, the given brightness will only be reached once. +# Hence changing light conditions lead to changing brightness values. +# If it is set to true, the given brightness will be reached continuously, +# trying to adapt to changing light conditions. +bool brightness_continuous + + +# Flag which indicates if the gain is provided and hence should be set before +# grabbing +bool gain_given + +# The target gain values in percent of the maximal value the camera supports +# For USB-Cameras, the gain is in dB, for GigE-Cameras it is given in so +# called 'device specific units'. This value can be overriden from the +# brightness search, in case that the gain_fixed flag is set to false. +# The interface will only try to reach this values if the gain_given flag is +# set to true. +float32[] gain_values + +# If the gain_fixed flag is set, the gain value will stay fix in +# case of a brightness search. Hence the target brightness will be reached +# only by varying the exposure +bool gain_fixed + +# Flag which indicates if the gamma value is provided and hence should be set +# before grabbing +bool gamma_given + +# Gamma correction of pixel intensity. +# Adjusts the brightness of the pixel values output by the camera's sensor +# to account for a non-linearity in the human perception of brightness or +# of the display system (such as CRT). +# The interface will only try to reach this value if the gamma_given flag is +# set to true. +float32[] gamma_values + +--- +########################################## +################# RESULT ################# +########################################## + +# The resulting HDR image generated out of several images with the inquired +# image intensity settings. +sensor_msgs/Image hdr_image + +# The CameraInfo obejct describing the camera properties for the above image +# sequence. Static in many cases, but can also support variable binning setting +sensor_msgs/CameraInfo cam_info + +# The reached values of the images e.g. the values that were set to the camera +# before the grab +float32[] reached_exposure_times + +float32[] reached_brightness_values + +float32[] reached_gain_values + +float32[] reached_gamma_values + +# Flag which indicates the success of the grabbing action +bool success + +--- +########################################## +################ FEEDBACK ################ +########################################## +int32 curr_nr_images_taken diff --git a/catkin_ws/src/camera_control_msgs/action/GrabImages.action b/catkin_ws/src/camera_control_msgs/action/GrabImages.action new file mode 100644 index 00000000..4a348f61 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/action/GrabImages.action @@ -0,0 +1,93 @@ +########################################## +################## GOAL ################## +########################################## + +# Flag which indicates if the exposure times are provided and hence should be +# set before grabbing +bool exposure_given + +# Only relevant, if exposure_given is true: +# The list of target exposure times in microseconds. +# It is possible to grab only one image as well as several images with +# different exposure times. This values can be overriden from the brightness +# search, in case that the flag exposure_fixed is not true. +float32[] exposure_times + +# Flag which indicates if the gain is provided and hence should be set before +# grabbing +bool gain_given + +# Only relevant, if gain_given is true: +# The target gain in percent of the maximal value the camera supports. +# For USB-Cameras, the gain is in dB, for GigE-Cameras it is given in so +# called 'device specific units'. This value can be overriden from the +# brightness search, in case that the gain_fixed flag is set to false. +float32[] gain_values + +# Flag which indicates if the gamma value is provided and hence should be set +# before grabbing +bool gamma_given + +# Only relevant, if gain_given is true: +# Gamma correction of pixel intensity. +# Adjusts the brightness of the pixel values output by the camera's sensor +# to account for a non-linearity in the human perception of brightness or +# of the display system (such as CRT). +float32[] gamma_values + +# Flag which indicates if the brightness values are provided and hence should +# be set before grabbing +bool brightness_given + +# Only relevant, if brightness_given is true: +# The average intensity values of the images. It depends the exposure time +# as well as the gain setting. +float32[] brightness_values + +# Only relevant, if brightness_given is true: +# If the camera should try reach the desired brightness, at least one of the +# following flags MUST be set. If both are set, the interface will use the +# profile that tries to keep the gain at minimum to reduce white noise. +# 'exposure_auto' will adapt the exposure time to reach the brightness, wheras +# 'gain_auto' does so by adapting the gain. If one of these flags is set to +# false, the connected property will be kept fix. +# In most of the cases trying to reach a target brightness only by varying the +# gain and keeping the exposure time fix is not a good approach, because the +# exposure range is many times higher than the gain range. +bool exposure_auto +bool gain_auto + +--- +########################################## +################# RESULT ################# +########################################## + +# The resulting images with the inquired image intensity settings. +# The size of the vector equals the size of the exposure_times or the +# brightness_values-vector +sensor_msgs/Image[] images + +# The CameraInfo obejct describing the camera properties for the above image +# sequence. Static in many cases, but can also support variable binning setting +sensor_msgs/CameraInfo cam_info + +# The reached values of the images e.g. the values that were set to the camera +# before the grab +float32[] reached_exposure_times + +float32[] reached_brightness_values + +float32[] reached_gain_values + +float32[] reached_gamma_values + +# Flag which indicates the success of the grabbing action +# In case of failure, the images-vector contains only the images, that could be +# grabbed before the failure occurred. +bool success +--- +########################################## +################ FEEDBACK ################ +########################################## +# The number of images already taken +int32 curr_nr_images_taken diff --git a/catkin_ws/src/camera_control_msgs/package.xml b/catkin_ws/src/camera_control_msgs/package.xml new file mode 100644 index 00000000..79e44fc2 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/package.xml @@ -0,0 +1,31 @@ + + + camera_control_msgs + + 0.2.1 + + + Message package from the arena_camera package. + Contains action and service definitions of the arena_camera interface. + + + Marcel Debout + Markus Grimm + Nikolas Engelhard + Markus Grimm + Nikolas Engelhard + + BSD + + http://www.ros.org/wiki/arena_camera + + catkin + + actionlib_msgs + message_generation + sensor_msgs + + actionlib_msgs + message_runtime + sensor_msgs + diff --git a/catkin_ws/src/camera_control_msgs/srv/GetCamProperties.srv b/catkin_ws/src/camera_control_msgs/srv/GetCamProperties.srv new file mode 100644 index 00000000..b8b80611 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/srv/GetCamProperties.srv @@ -0,0 +1,38 @@ +# This service returns the current camera properties as well as the limits +--- +bool success + +bool is_sleeping + +string device_user_id + +int32 min_binning_x +int32 max_binning_x +int32 current_binning_x + +int32 min_binning_y +int32 max_binning_y +int32 current_binning_y + +float32 max_framerate +float32 current_framerate + +float32 min_exposure +float32 max_exposure +float32 current_exposure + +float32 min_gain_in_cam_units +float32 max_gain_in_cam_units +float32 current_gain_in_cam_units + +float32 min_gain +float32 max_gain +float32 current_gain + +float32 min_gamma +float32 max_gamma +float32 current_gamma + +bool brightness_continuous +bool gain_auto +bool exposure_auto diff --git a/catkin_ws/src/camera_control_msgs/srv/SetBinning.srv b/catkin_ws/src/camera_control_msgs/srv/SetBinning.srv new file mode 100644 index 00000000..f2f08619 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/srv/SetBinning.srv @@ -0,0 +1,16 @@ +# Binning factor to get downsampled images. It refers here to any camera +# setting which combines rectangular neighborhoods of pixels into larger +# "super-pixels." It reduces the resolution of the output image to +# (width / binning_x) x (height / binning_y). +# The default values binning_x = binning_y = 0 are considered the same +# as binning_x = binning_y = 1 (no subsampling). +# Calling this service with target binning values will change the binning entry +# in the published camera_info_msg +uint32 target_binning_x +uint32 target_binning_y + +--- +# Exact match can not always be reached +uint32 reached_binning_x +uint32 reached_binning_y +bool success diff --git a/catkin_ws/src/camera_control_msgs/srv/SetBool.srv b/catkin_ws/src/camera_control_msgs/srv/SetBool.srv new file mode 100644 index 00000000..c019666d --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/srv/SetBool.srv @@ -0,0 +1,6 @@ +# Borrowed from std_srvs/SetBool until they release the package + +bool data # e.g. for hardware enabling / disabling +--- +bool success # indicate successful run of triggered service +string message # informational, e.g. for error messages diff --git a/catkin_ws/src/camera_control_msgs/srv/SetBrightness.srv b/catkin_ws/src/camera_control_msgs/srv/SetBrightness.srv new file mode 100644 index 00000000..c3cb962a --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/srv/SetBrightness.srv @@ -0,0 +1,30 @@ +# The target brightness, which is average intensity values of the images. +# It depends the exposure time as well as the gain setting. +int32 target_brightness + +# The brightness_continuous flag controls the auto brightness function. +# If it is set to false, the given brightness will only be reached once. +# Hence changing light conditions lead to changing brightness values. +# If it is set to true, the given brightness will be reached continuously, +# trying to adapt to changing light conditions. The 'brightness_contunuous' +# mode is is only possible for values in the possible auto range of the arena +# API which is e.g. [50 - 205] for acA2500-14um and acA1920-40gm +bool brightness_continuous + +# If the camera should try reach or keep the desired brightness, hence adapting +# to changing light conditions, at least one of the following flags MUST be set. +# If both are set, the interface will use the profile that tries to keep the +# gain at minimum to reduce white noise. +# 'exposure_auto' will adapt the exposure time to reach the brightness, wheras +# 'gain_auto' does so by adapting the gain. +bool exposure_auto +bool gain_auto + +--- + +# Exact match can not always be reached +int32 reached_brightness +float32 reached_exposure_time +float32 reached_gain_value + +bool success diff --git a/catkin_ws/src/camera_control_msgs/srv/SetExposure.srv b/catkin_ws/src/camera_control_msgs/srv/SetExposure.srv new file mode 100644 index 00000000..9a9ad5d3 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/srv/SetExposure.srv @@ -0,0 +1,7 @@ +# The target exposure time measured in microseconds. +# If the limits were exceeded, the desired exposure time will be truncated. +float32 target_exposure +--- +# Exact match can not always be reached +float32 reached_exposure +bool success diff --git a/catkin_ws/src/camera_control_msgs/srv/SetGain.srv b/catkin_ws/src/camera_control_msgs/srv/SetGain.srv new file mode 100644 index 00000000..6dbed776 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/srv/SetGain.srv @@ -0,0 +1,8 @@ +# The target gain in percent of the maximal value the camera supports. +# For USB-Cameras, the gain is in dB, for GigE-Cameras it is given in so +# called 'device specific units'. +float32 target_gain +--- +# Exact match can not always be reached +float32 reached_gain +bool success diff --git a/catkin_ws/src/camera_control_msgs/srv/SetGamma.srv b/catkin_ws/src/camera_control_msgs/srv/SetGamma.srv new file mode 100644 index 00000000..f715a213 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/srv/SetGamma.srv @@ -0,0 +1,10 @@ +# The target gamma correction of pixel intensity. +# Adjusts the brightness of the pixel values output by the camera's sensor +# to account for a non-linearity in the human perception of brightness or +# of the display system (such as CRT). +float32 target_gamma +--- +# Exact match can not always be reached +float32 reached_gamma +bool success + diff --git a/catkin_ws/src/camera_control_msgs/srv/SetROI.srv b/catkin_ws/src/camera_control_msgs/srv/SetROI.srv new file mode 100644 index 00000000..bd2b2515 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/srv/SetROI.srv @@ -0,0 +1,17 @@ +# Select a region of interest to get a cropped image. +# The region is defined by four parameters +# roi.width: with of the region +# roi.height: height of the region +# roi.x_offset at which pixel a long the x axis (horizontal) does the +# cropped region start +# roi.y_offset at which pixel a long the y axis (vertical) does the +# cropped region start +# The cropped image will then be Image[y_offset:y_offset+vertical, x_offset:x_offset+horizontal] +# Notice that x_offset cannot be larger than img.width - roi.width +# The same for y_offset, not larger than img.height - roi.height +sensor_msgs/RegionOfInterest target_roi + +--- +# Exact match can not always reached +sensor_msgs/RegionOfInterest reached_roi +bool success diff --git a/catkin_ws/src/camera_control_msgs/srv/SetSleeping.srv b/catkin_ws/src/camera_control_msgs/srv/SetSleeping.srv new file mode 100644 index 00000000..f7391c45 --- /dev/null +++ b/catkin_ws/src/camera_control_msgs/srv/SetSleeping.srv @@ -0,0 +1,7 @@ +# If the camera runs in topic mode (continuously publishing images over the +# topics respecting the desired frame_rate) this service offers the posibillity +# to pause the image acquisition. To restart the grabbing, this service should +# be called again with set_sleeping set to false +bool set_sleeping +--- +bool success diff --git a/cmake/FindPylon.cmake b/cmake/FindPylon.cmake deleted file mode 100644 index 40eff171..00000000 --- a/cmake/FindPylon.cmake +++ /dev/null @@ -1,30 +0,0 @@ -set(PYLON_ROOT $ENV{PYLON_ROOT}) -if (NOT DEFINED ENV{PYLON_ROOT}) - set(PYLON_ROOT "/opt/pylon5") -endif() - -set(_PYLON_CONFIG "${PYLON_ROOT}/bin/pylon-config") -if (EXISTS "${_PYLON_CONFIG}") - set(Pylon_FOUND TRUE) - execute_process(COMMAND ${_PYLON_CONFIG} --cflags-only-I OUTPUT_VARIABLE HEADERS_OUT) - execute_process(COMMAND ${_PYLON_CONFIG} --libs-only-l OUTPUT_VARIABLE LIBS_OUT) - execute_process(COMMAND ${_PYLON_CONFIG} --libs-only-L OUTPUT_VARIABLE LIBDIRS_OUT) - string(REPLACE " " ";" HEADERS_OUT "${HEADERS_OUT}") - string(REPLACE "-I" "" HEADERS_OUT "${HEADERS_OUT}") - string(REPLACE "\n" "" Pylon_INCLUDE_DIRS "${HEADERS_OUT}") - - string(REPLACE " " ";" LIBS_OUT "${LIBS_OUT}") - string(REPLACE "-l" "" LIBS_OUT "${LIBS_OUT}") - string(REPLACE "\n" "" Pylon_LIBRARIES "${LIBS_OUT}") - - string(REPLACE " " ";" LIBDIRS_OUT "${LIBDIRS_OUT}") - string(REPLACE "-L" "" LIBDIRS_OUT "${LIBDIRS_OUT}") - string(REPLACE "\n" "" LIBDIRS_OUT "${LIBDIRS_OUT}") - - set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) - foreach (LIBDIR ${LIBDIRS_OUT}) - link_directories(${LIBDIR}) - endforeach() -else() - set(Pylon_FOUND FALSE) -endif() diff --git a/include/pylon_camera/binary_exposure_search.h b/include/pylon_camera/binary_exposure_search.h deleted file mode 100644 index b4b2c24f..00000000 --- a/include/pylon_camera/binary_exposure_search.h +++ /dev/null @@ -1,131 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#ifndef PYLON_CAMERA_BINARY_EXPOSURE_SEARCH_H -#define PYLON_CAMERA_BINARY_EXPOSURE_SEARCH_H - -#include - -namespace pylon_camera -{ - -/** - * Class for the extended brightness search using a binary - * exposure search method - */ -class BinaryExposureSearch -{ -public: - /** - * Initialize the exposure search - * @param target_brightness the targeted brightness value - * @param left_lim the minimum exposure time to set - * @param right_lim the maximum exposure time to set - * @param current_exp the current exposure time - */ - BinaryExposureSearch(const float& target_brightness, - const float& left_lim, - const float& right_lim, - const float& current_exp); - - virtual ~BinaryExposureSearch(); - - /** - * Update the binary search based on the current - * brightness and exposure values - */ - bool update(const float& current_brightness, - const float& current_exposure); - - /** - * Setter for limit_reached_ - */ - void limitReached(bool reached); - - /** - * Getter for limit_reached_ - * Returns true if the search reached the physical exposure - * limit of the camera - */ - bool isLimitReached() const; - - /** - * Getter for the new exposure calculated in out of the update step - */ - const float& newExposure() const; - -private: - /** - * The targeted brightness value - */ - const float target_brightness_; - - /** - * The previous exposure value - */ - float last_exposure_; - - /** - * Counts how often the exposure remained unchanged during search - */ - size_t last_unchanged_exposure_counter_; - - /** - * Left limit for the binary search - */ - float left_limit_; - - /** - * Right limit for the binary search - */ - float right_limit_; - - /** - * The new exposure out of the update step - */ - float new_exposure_; - - /** - * Flag which tells, when the physical limit of the camera is reached. - * Hence the BinaryExposureSearch will fail, if the target brightness - * is not yet reached - */ - bool limit_reached_; - - /** - * Flag which tells if it is the first time the update() function is called - * Hence it is not necessary to update the search limits, because they - * were set correctly in the constructor - */ - bool is_initial_setting_; -}; - -} // namespace pylon_camera - -#endif // PYLON_CAMERA_BINARY_EXPOSURE_SEARCH_H diff --git a/include/pylon_camera/internal/impl/pylon_camera_base.hpp b/include/pylon_camera/internal/impl/pylon_camera_base.hpp deleted file mode 100644 index 973fc87d..00000000 --- a/include/pylon_camera/internal/impl/pylon_camera_base.hpp +++ /dev/null @@ -1,1158 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#ifndef PYLON_CAMERA_INTERNAL_BASE_HPP_ -#define PYLON_CAMERA_INTERNAL_BASE_HPP_ - -#include -#include -#include - -#include -#include -#include - - -namespace pylon_camera -{ - -template -PylonCameraImpl::PylonCameraImpl(Pylon::IPylonDevice* device) : - PylonCamera(), - cam_(new CBaslerInstantCameraT(device)) -{} - -template -PylonCameraImpl::~PylonCameraImpl() -{ - delete cam_; - cam_ = nullptr; - - if ( binary_exp_search_ ) - { - delete binary_exp_search_; - binary_exp_search_ = nullptr; - } -} - -template -bool PylonCameraImpl::registerCameraConfiguration() -{ - try - { - cam_->RegisterConfiguration(new Pylon::CSoftwareTriggerConfiguration, - Pylon::RegistrationMode_ReplaceAll, - Pylon::Cleanup_Delete); - return true; - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM(e.GetDescription()); - return false; - } -} - -template -bool PylonCameraImpl::openCamera() -{ - try - { - cam_->Open(); - return true; - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM(e.GetDescription()); - return false; - } -} - -template -bool PylonCameraImpl::isCamRemoved() -{ - return cam_->IsCameraDeviceRemoved(); -} - -template -size_t PylonCameraImpl::currentOffsetX() -{ - if ( GenApi::IsAvailable(cam_->OffsetX) ) - { - return static_cast(cam_->OffsetX.GetValue()); - } - else - { - return 0; - } -} - -template -size_t PylonCameraImpl::currentOffsetY() -{ - if ( GenApi::IsAvailable(cam_->OffsetY) ) - { - return static_cast(cam_->OffsetY.GetValue()); - } - else - { - return 0; - } -} - -template -sensor_msgs::RegionOfInterest PylonCameraImpl::currentROI() -{ - sensor_msgs::RegionOfInterest roi; - roi.width = cam_->Width.GetValue(); - roi.height = cam_->Height.GetValue(); - roi.x_offset = currentOffsetX(); - roi.y_offset = currentOffsetY(); - return roi; -} - -template -size_t PylonCameraImpl::currentBinningX() -{ - if ( GenApi::IsAvailable(cam_->BinningHorizontal) ) - { - return static_cast(cam_->BinningHorizontal.GetValue()); - } - else - { - return 1; - } -} - -template -size_t PylonCameraImpl::currentBinningY() -{ - if ( GenApi::IsAvailable(cam_->BinningVertical) ) - { - return static_cast(cam_->BinningVertical.GetValue()); - } - else - { - return 1; - } -} - -template -std::string PylonCameraImpl::currentROSEncoding() const -{ - std::string gen_api_encoding(cam_->PixelFormat.ToString().c_str()); - std::string ros_encoding(""); - if ( !encoding_conversions::genAPI2Ros(gen_api_encoding, ros_encoding) ) - { - std::stringstream ss; - ss << "No ROS equivalent to GenApi encoding '" << gen_api_encoding - << "' found! This is bad because this case should never occur!"; - throw std::runtime_error(ss.str()); - return "NO_ENCODING"; - } - return ros_encoding; -} - -template -float PylonCameraImpl::currentExposure() -{ - return static_cast(exposureTime().GetValue()); -} - -template -float PylonCameraImpl::currentGain() -{ - float curr_gain = (static_cast(gain().GetValue()) - static_cast(gain().GetMin())) / - (static_cast(gain().GetMax() - static_cast(gain().GetMin()))); - return curr_gain; -} - -template -GenApi::IFloat& PylonCameraImpl::gamma() -{ - if ( GenApi::IsAvailable(cam_->Gamma) ) - { - return cam_->Gamma; - } - else - { - throw std::runtime_error("Error while accessing Gamma in PylonCameraImpl"); - } -} - -template -float PylonCameraImpl::currentGamma() -{ - return static_cast(gamma().GetValue()); -} - -template -float PylonCameraImpl::currentAutoExposureTimeLowerLimit() -{ - return static_cast(autoExposureTimeLowerLimit().GetValue()); -} - -template -float PylonCameraImpl::currentAutoExposureTimeUpperLimit() -{ - return static_cast(autoExposureTimeUpperLimit().GetValue()); -} - -template -float PylonCameraImpl::currentAutoGainLowerLimit() -{ - return static_cast(autoGainLowerLimit().GetValue()); -} - -template -float PylonCameraImpl::currentAutoGainUpperLimit() -{ - return static_cast(autoGainUpperLimit().GetValue()); -} - -template -bool PylonCameraImpl::isPylonAutoBrightnessFunctionRunning() -{ - return cam_->ExposureAuto.GetValue() != ExposureAutoEnums::ExposureAuto_Off || - cam_->GainAuto.GetValue() != GainAutoEnums::GainAuto_Off; -} - -template -bool PylonCameraImpl::isBrightnessSearchRunning() -{ - return isBinaryExposureSearchRunning() || isPylonAutoBrightnessFunctionRunning(); -} - -template -void PylonCameraImpl::enableContinuousAutoExposure() -{ - if ( GenApi::IsAvailable(cam_->ExposureAuto) ) - { - cam_->ExposureAuto.SetValue(ExposureAutoEnums::ExposureAuto_Continuous); - } - else - { - ROS_ERROR_STREAM("Trying to enable ExposureAuto_Continuous mode, but " - << "the camera has no Auto Exposure"); - } -} - -template -void PylonCameraImpl::enableContinuousAutoGain() -{ - if ( GenApi::IsAvailable(cam_->GainAuto) ) - { - cam_->GainAuto.SetValue(GainAutoEnums::GainAuto_Continuous); - } - else - { - ROS_ERROR_STREAM("Trying to enable GainAuto_Continuous mode, but " - << "the camera has no Auto Gain"); - } -} - -template -void PylonCameraImpl::disableAllRunningAutoBrightessFunctions() -{ - is_binary_exposure_search_running_ = false; - cam_->ExposureAuto.SetValue(ExposureAutoEnums::ExposureAuto_Off); - cam_->GainAuto.SetValue(GainAutoEnums::GainAuto_Off); - if ( binary_exp_search_ ) - { - delete binary_exp_search_; - binary_exp_search_ = nullptr; - } -} - -template -bool PylonCameraImpl::setupSequencer(const std::vector& exposure_times) -{ - std::vector exposure_times_set; - if ( setupSequencer(exposure_times, exposure_times_set) ) - { - seq_exp_times_ = exposure_times_set; - std::stringstream ss; - ss << "Initialized sequencer with the following inverse exposure-times [1/s]: "; - for ( size_t i = 0; i < seq_exp_times_.size(); ++i ) - { - ss << seq_exp_times_.at(i); - if ( i != seq_exp_times_.size() - 1 ) - { - ss << ", "; - } - } - ROS_INFO_STREAM(ss.str()); - return true; - } - else - { - return false; - } -} - -template -bool PylonCameraImpl::startGrabbing(const PylonCameraParameter& parameters) -{ - try - { - if ( GenApi::IsAvailable(cam_->ShutterMode) ) - { - setShutterMode(parameters.shutter_mode_); - } - - available_image_encodings_ = detectAvailableImageEncodings(); - if ( !setImageEncoding(parameters.imageEncoding()) ) - { - return false; - } - - cam_->StartGrabbing(); - user_output_selector_enums_ = detectAndCountNumUserOutputs(); - device_user_id_ = cam_->DeviceUserID.GetValue(); - img_rows_ = static_cast(cam_->Height.GetValue()); - img_cols_ = static_cast(cam_->Width.GetValue()); - img_size_byte_ = img_cols_ * img_rows_ * imagePixelDepth(); - - grab_timeout_ = exposureTime().GetMax() * 1.05; - - // grab one image to be sure, that the communication is successful - Pylon::CGrabResultPtr grab_result; - grab(grab_result); - if ( grab_result.IsValid() ) - { - is_ready_ = true; - } - else - { - ROS_ERROR("PylonCamera not ready because the result of the initial grab is invalid"); - } - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("startGrabbing: " << e.GetDescription()); - return false; - } - return true; -} - -template -bool PylonCameraImpl::grab(std::vector& image) -{ - Pylon::CGrabResultPtr ptr_grab_result; - if ( !grab(ptr_grab_result) ) - { - ROS_ERROR("Error: Grab was not successful"); - return false; - } - - const uint8_t *pImageBuffer = reinterpret_cast(ptr_grab_result->GetBuffer()); - image.assign(pImageBuffer, pImageBuffer + img_size_byte_); - - if ( !is_ready_ ) - is_ready_ = true; - - return true; -} - -template -bool PylonCameraImpl::grab(uint8_t* image) -{ - Pylon::CGrabResultPtr ptr_grab_result; - if ( !grab(ptr_grab_result) ) - { - ROS_ERROR("Error: Grab was not successful"); - return false; - } - - memcpy(image, ptr_grab_result->GetBuffer(), img_size_byte_); - - return true; -} - -template -bool PylonCameraImpl::grab(Pylon::CGrabResultPtr& grab_result) -{ - try - { - int timeout = 5000; // ms - - // WaitForFrameTriggerReady to prevent trigger signal to get lost - // this could happen, if 2xExecuteSoftwareTrigger() is only followed by 1xgrabResult() - // -> 2nd trigger might get lost - if ( cam_->WaitForFrameTriggerReady(timeout, Pylon::TimeoutHandling_ThrowException) ) - { - cam_->ExecuteSoftwareTrigger(); - } - else - { - ROS_ERROR("Error WaitForFrameTriggerReady() timed out, impossible to ExecuteSoftwareTrigger()"); - return false; - } - cam_->RetrieveResult(grab_timeout_, grab_result, Pylon::TimeoutHandling_ThrowException); - } - catch ( const GenICam::GenericException &e ) - { - if ( cam_->IsCameraDeviceRemoved() ) - { - ROS_ERROR("Lost connection to the camera . . ."); - } - else - { - ROS_ERROR_STREAM("An image grabbing exception in pylon camera occurred: " - << e.GetDescription()); - } - return false; - } - catch (...) - { - ROS_ERROR("An unspecified image grabbing exception in pylon camera occurred"); - return false; - } - - if ( !grab_result->GrabSucceeded() ) - { - ROS_ERROR_STREAM("Error: " << grab_result->GetErrorCode() << " " - << grab_result->GetErrorDescription()); - return false; - } - return true; -} - -template -std::vector PylonCameraImpl::detectAvailableImageEncodings() -{ - std::vector available_encodings; - GenApi::INodeMap& node_map = cam_->GetNodeMap(); - GenApi::CEnumerationPtr img_encoding_enumeration_ptr( - node_map.GetNode("PixelFormat")); - GenApi::NodeList_t feature_list; - img_encoding_enumeration_ptr->GetEntries(feature_list); - std::stringstream ss; - ss << "Cam supports the following [GenAPI|ROS] image encodings: "; - for (GenApi::NodeList_t::iterator it = feature_list.begin(); - it != feature_list.end(); - ++it) - { - if ( GenApi::IsAvailable(*it) ) - { - GenApi::CEnumEntryPtr enum_entry(*it); - std::string encoding_gen_api = enum_entry->GetSymbolic().c_str(); - std::string encoding_ros("NO_ROS_EQUIVALENT"); - encoding_conversions::genAPI2Ros(encoding_gen_api, encoding_ros); - ss << "['" << encoding_gen_api << "'|'" << encoding_ros << "'] "; - available_encodings.push_back(encoding_gen_api); - } - } - ROS_INFO_STREAM(ss.str().c_str()); - return available_encodings; -} - -template -bool PylonCameraImpl::setImageEncoding(const std::string& ros_encoding) -{ - std::string gen_api_encoding; - bool conversion_found = encoding_conversions::ros2GenAPI(ros_encoding, gen_api_encoding); - if ( !conversion_found ) - { - if ( ros_encoding.empty() ) - { - ROS_WARN_STREAM("No image encoding provided. Will use 'mono8' or " - << "'rgb8' as fallback!"); - } - else - { - ROS_ERROR_STREAM("Can't convert ROS encoding '" << ros_encoding - << "' to a corresponding GenAPI encoding! Will use 'mono8' or " - << "'rgb8' as fallback!"); - } - bool fallback_found = false; - for ( const std::string& enc : available_image_encodings_ ) - { - if ( enc == "Mono8" || enc == "RGB8" ) - { - fallback_found = true; - gen_api_encoding = enc; - break; - } - } - if ( !fallback_found ) - { - ROS_ERROR_STREAM("Couldn't find a fallback solution!"); - return false; - } - } - - bool supports_desired_encoding = false; - for ( const std::string& enc : available_image_encodings_ ) - { - supports_desired_encoding = (gen_api_encoding == enc); - if ( supports_desired_encoding ) - { - break; - } - } - if ( !supports_desired_encoding ) - { - ROS_WARN_STREAM("Camera does not support the desired image pixel " - << "encoding '" << ros_encoding << "'!"); - return false; - } - try - { - if ( GenApi::IsAvailable(cam_->PixelFormat) ) - { - GenApi::INodeMap& node_map = cam_->GetNodeMap(); - GenApi::CEnumerationPtr(node_map.GetNode("PixelFormat"))->FromString(gen_api_encoding.c_str()); - } - else - { - ROS_WARN_STREAM("Camera does not support variable image pixel " - << "encoding!"); - return false; - } - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while setting target image encoding to '" - << ros_encoding << "' occurred: " << e.GetDescription()); - return false; - } - return true; -} - -template -int PylonCameraImpl::imagePixelDepth() const -{ - int pixel_depth(0); - try - { - // pylon PixelSize already contains the number of channels - // the size is given in bit, wheras ROS provides it in byte - pixel_depth = cam_->PixelSize.GetIntValue() / 8; - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while reading image pixel size occurred: " - << e.GetDescription()); - } - return pixel_depth; -} - -template -bool PylonCameraImpl::setROI(const sensor_msgs::RegionOfInterest target_roi, - sensor_msgs::RegionOfInterest& reached_roi) -{ - size_t width_to_set = target_roi.width; - size_t height_to_set = target_roi.height; - size_t offset_x_to_set = target_roi.x_offset; - size_t offset_y_to_set = target_roi.y_offset; - try - { - if ( GenApi::IsAvailable(cam_->Width) && GenApi::IsAvailable(cam_->Height) && GenApi::IsAvailable(cam_->OffsetX) && GenApi::IsAvailable(cam_->OffsetY)) - { - cam_->StopGrabbing(); - int64_t max_image_width = cam_->WidthMax.GetValue(); - int64_t min_image_width = cam_->Width.GetMin(); - int64_t max_image_height = cam_->HeightMax.GetValue(); - int64_t min_image_height = cam_->Height.GetMin(); - int64_t width_inc = cam_->Width.GetInc(); - int64_t height_inc = cam_->Height.GetInc(); - // reset roi to avoid exceptions while setting Width and Height values - cam_->OffsetX.SetValue(0); - cam_->OffsetY.SetValue(0); - cam_->Width.SetValue(max_image_width); - cam_->Height.SetValue(max_image_height); - sensor_msgs::RegionOfInterest current_roi = currentROI(); - // Force minimum increment of 2 as some cameras wrongly declare that - // they have an increment of 1 while it is 2 - if (height_inc == 1) - height_inc = 2; - if (width_inc == 1) - width_inc = 2; - - if (width_to_set > max_image_width) - { - ROS_WARN_STREAM("Desired width(" - << width_to_set << ") unreachable! Setting to upper " - << "limit: " << max_image_width); - width_to_set = max_image_width; - } - else if (width_to_set < min_image_width) - { - ROS_WARN_STREAM("Desired width(" - << width_to_set << ") unreachable! Setting to lower " - << "limit: " << min_image_width); - width_to_set = min_image_width; - } - - if (height_to_set > max_image_height) - { - ROS_WARN_STREAM("Desired height(" - < max_image_width) - { - int64_t adapted_offset_x = max_image_width - width_to_set; - adapted_offset_x -= adapted_offset_x % width_inc; - ROS_WARN_STREAM("Desired x offset(" - << offset_x_to_set << ") impossible for desired width(" - << width_to_set << ")! Setting to next possible value " - << adapted_offset_x); - offset_x_to_set = adapted_offset_x; - } - if (offset_y_to_set % height_inc != 0) - { - int64_t adapted_offset_y = offset_y_to_set + (height_inc - offset_y_to_set % height_inc); - ROS_WARN_STREAM("Desired y offset (" - << offset_y_to_set << ") not an multiple ofheight increment(" - << height_inc <<")! Setting to next possible value higher multiple: (" - << adapted_offset_y <<")"); - offset_y_to_set = adapted_offset_y; - } - if (height_to_set + offset_y_to_set > max_image_height) - { - int64_t adapted_offset_y = max_image_height - height_to_set; - adapted_offset_y -= adapted_offset_y % height_inc; - ROS_WARN_STREAM("Desired y offset(" - << offset_y_to_set << ") impossible for desired height(" - << height_to_set << ")! Setting to next possible value " - << adapted_offset_y); - offset_y_to_set = adapted_offset_y; - } - - cam_->Width.SetValue(width_to_set); - cam_->Height.SetValue(height_to_set); - cam_->OffsetX.SetValue(offset_x_to_set); - cam_->OffsetY.SetValue(offset_y_to_set); - reached_roi = currentROI(); - cam_->StartGrabbing(); - img_cols_ = static_cast(cam_->Width.GetValue()); - img_rows_ = static_cast(cam_->Height.GetValue()); - img_size_byte_ = img_cols_ * img_rows_ * imagePixelDepth(); - - } - else - { - ROS_WARN_STREAM("Camera does not support area of interest. Will keep the " - << "current settings"); - reached_roi = currentROI(); - } - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while setting target area of interest " - << "(width, height, x_offset, y_offset) to: (" << width_to_set << ", " - << height_to_set << ", " << offset_x_to_set << ", " << offset_y_to_set << - ") occurred: " - << e.GetDescription()); - return false; - } - return true; -} - - -template -bool PylonCameraImpl::setBinningX(const size_t& target_binning_x, - size_t& reached_binning_x) -{ - try - { - if ( GenApi::IsAvailable(cam_->BinningHorizontal) ) - { - cam_->StopGrabbing(); - size_t binning_x_to_set = target_binning_x; - if ( binning_x_to_set < cam_->BinningHorizontal.GetMin() ) - { - ROS_WARN_STREAM("Desired horizontal binning_x factor(" - << binning_x_to_set << ") unreachable! Setting to lower " - << "limit: " << cam_->BinningHorizontal.GetMin()); - binning_x_to_set = cam_->BinningHorizontal.GetMin(); - } - else if ( binning_x_to_set > cam_->BinningHorizontal.GetMax() ) - { - ROS_WARN_STREAM("Desired horizontal binning_x factor(" - << binning_x_to_set << ") unreachable! Setting to upper " - << "limit: " << cam_->BinningHorizontal.GetMax()); - binning_x_to_set = cam_->BinningHorizontal.GetMax(); - } - cam_->BinningHorizontal.SetValue(binning_x_to_set); - reached_binning_x = currentBinningX(); - cam_->StartGrabbing(); - img_cols_ = static_cast(cam_->Width.GetValue()); - img_size_byte_ = img_cols_ * img_rows_ * imagePixelDepth(); - } - else - { - ROS_WARN_STREAM("Camera does not support binning. Will keep the " - << "current settings"); - reached_binning_x = currentBinningX(); - } - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while setting target horizontal " - << "binning_x factor to " << target_binning_x << " occurred: " - << e.GetDescription()); - return false; - } - return true; -} - -template -bool PylonCameraImpl::setBinningY(const size_t& target_binning_y, - size_t& reached_binning_y) -{ - try - { - if ( GenApi::IsAvailable(cam_->BinningVertical) ) - { - cam_->StopGrabbing(); - size_t binning_y_to_set = target_binning_y; - if ( binning_y_to_set < cam_->BinningVertical.GetMin() ) - { - ROS_WARN_STREAM("Desired vertical binning_y factor(" - << binning_y_to_set << ") unreachable! Setting to lower " - << "limit: " << cam_->BinningVertical.GetMin()); - binning_y_to_set = cam_->BinningVertical.GetMin(); - } - else if ( binning_y_to_set > cam_->BinningVertical.GetMax() ) - { - ROS_WARN_STREAM("Desired vertical binning_y factor(" - << binning_y_to_set << ") unreachable! Setting to upper " - << "limit: " << cam_->BinningVertical.GetMax()); - binning_y_to_set = cam_->BinningVertical.GetMax(); - } - cam_->BinningVertical.SetValue(binning_y_to_set); - reached_binning_y = currentBinningY(); - cam_->StartGrabbing(); - img_rows_ = static_cast(cam_->Height.GetValue()); - img_size_byte_ = img_cols_ * img_rows_ * imagePixelDepth(); - } - else - { - ROS_WARN_STREAM("Camera does not support binning. Will keep the " - << "current settings"); - reached_binning_y = currentBinningY(); - } - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while setting target vertical " - << "binning_y factor to " << target_binning_y << " occurred: " - << e.GetDescription()); - return false; - } - return true; -} - -template -bool PylonCameraImpl::setExposure(const float& target_exposure, - float& reached_exposure) -{ - try - { - cam_->ExposureAuto.SetValue(ExposureAutoEnums::ExposureAuto_Off); - - float exposure_to_set = target_exposure; - if ( exposure_to_set < exposureTime().GetMin() ) - { - ROS_WARN_STREAM("Desired exposure (" << exposure_to_set << ") " - << "time unreachable! Setting to lower limit: " - << exposureTime().GetMin()); - exposure_to_set = exposureTime().GetMin(); - } - else if ( exposure_to_set > exposureTime().GetMax() ) - { - ROS_WARN_STREAM("Desired exposure (" << exposure_to_set << ") " - << "time unreachable! Setting to upper limit: " - << exposureTime().GetMax()); - exposure_to_set = exposureTime().GetMax(); - } - exposureTime().SetValue(exposure_to_set); - reached_exposure = currentExposure(); - - if ( std::fabs(reached_exposure - exposure_to_set) > exposureStep() ) - { - // no success if the delta between target and reached exposure - // is greater then the exposure step in ms - return false; - } - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while setting target exposure to " - << target_exposure << " occurred:" - << e.GetDescription()); - return false; - } - return true; -} - - -template -bool PylonCameraImpl::setAutoflash( - const std::map flash_on_lines) -{ - ROS_ERROR_STREAM("Not implemented for this camera type"); - return false; -} - -template -bool PylonCameraImpl::setGain(const float& target_gain, - float& reached_gain) -{ - try - { - cam_->GainAuto.SetValue(GainAutoEnums::GainAuto_Off); - float truncated_gain = target_gain; - if ( truncated_gain < 0.0 ) - { - ROS_WARN_STREAM("Desired gain (" << target_gain << ") in " - << "percent out of range [0.0 - 1.0]! Setting to lower " - << "limit: 0.0"); - truncated_gain = 0.0; - } - else if ( truncated_gain > 1.0 ) - { - ROS_WARN_STREAM("Desired gain (" << target_gain << ") in " - << "percent out of range [0.0 - 1.0]! Setting to upper " - << "limit: 1.0"); - truncated_gain = 1.0; - } - - float gain_to_set = gain().GetMin() + - truncated_gain * (gain().GetMax() - gain().GetMin()); - gain().SetValue(gain_to_set); - reached_gain = currentGain(); - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while setting target gain to " - << target_gain << " occurred: " << e.GetDescription()); - return false; - } - return true; -} - - -template -bool PylonCameraImpl::setBrightness(const int& target_brightness, - const float& current_brightness, - const bool& exposure_auto, - const bool& gain_auto) -{ - try - { - // if the target brightness is greater 255, limit it to 255 - // the brightness_to_set is a float value, regardless of the current - // pixel data output format, i.e., 0.0 -> black, 1.0 -> white. - typename CameraTraitT::AutoTargetBrightnessValueType brightness_to_set = - CameraTraitT::convertBrightness(std::min(255, target_brightness)); - -#if DEBUG - std::cout << "br = " << current_brightness << ", gain = " - << currentGain() << ", exp = " - << currentExposure() - << ", curAutoExpLimits [" - << currentAutoExposureTimeLowerLimit() - << ", " << currentAutoExposureTimeUpperLimit() - << "], autoBrightness [min: " - << autoTargetBrightness().GetMin()*255 << ", max: " - << autoTargetBrightness().GetMax()*255 << ", curr: " - << autoTargetBrightness().GetValue()*255 << "]" - << " curAutoGainLimits [min: " - << currentAutoGainLowerLimit() << ", max: " - << currentAutoGainUpperLimit() << "]" << std::endl; -#endif - - if ( isPylonAutoBrightnessFunctionRunning() ) - { - // do nothing while the pylon-auto function is active and if the - // desired brightness is inside the possible range - return true; - } - -#if DEBUG - ROS_INFO("pylon auto finished . . ."); -#endif - - if ( autoTargetBrightness().GetMin() <= brightness_to_set && - autoTargetBrightness().GetMax() >= brightness_to_set ) - { - // Use Pylon Auto Function, whenever in possible range - // -> Own binary exposure search not necessary - autoTargetBrightness().SetValue(brightness_to_set, true); - if ( exposure_auto ) - { - cam_->ExposureAuto.SetValue(ExposureAutoEnums::ExposureAuto_Once); - } - if ( gain_auto ) - { - cam_->GainAuto.SetValue(GainAutoEnums::GainAuto_Once); - } - } - else - { - if ( isBinaryExposureSearchRunning() ) - { - // pre-control using the possible limits of the pylon auto function range - // if they were reached, we continue with the extended brightness search - if ( !setExtendedBrightness(std::min(255, target_brightness), current_brightness) ) - { - return false; - } - } - else - { - // pre control to the possible pylon limits, no matter where - // we are currently - // This is not the best solution in case the current brightness - // is e.g. 35 and we want to reach e.g. 33, because we first go - // up to 50 and then start the binary exposure search to go down - // again. - // But in fact it's the only solution, because the exact exposure - // times related to the min & max limit of the pylon auto function - // are unknown, beacause they depend on the current light scene - if ( brightness_to_set < autoTargetBrightness().GetMin() ) - { - // target < 50 -> pre control to 50 - autoTargetBrightness().SetValue(autoTargetBrightness().GetMin(), true); - if ( exposure_auto ) - { - cam_->ExposureAuto.SetValue(ExposureAutoEnums::ExposureAuto_Once); - } - if ( gain_auto ) - { - cam_->GainAuto.SetValue(GainAutoEnums::GainAuto_Once); - } - } - else // target > 205 -> pre control to 205 - { - autoTargetBrightness().SetValue(autoTargetBrightness().GetMax(), true); - if ( exposure_auto ) - { - cam_->ExposureAuto.SetValue(ExposureAutoEnums::ExposureAuto_Once); - } - if ( gain_auto ) - { - cam_->GainAuto.SetValue(GainAutoEnums::GainAuto_Once); - } - } - is_binary_exposure_search_running_ = true; - } - } - } - catch (const GenICam::GenericException &e) - { - ROS_ERROR_STREAM("An generic exception while setting target brightness to " - << target_brightness << " (= " - << CameraTraitT::convertBrightness(std::min(255, target_brightness)) - << ") occurred: " << e.GetDescription()); - return false; - } - return true; -} - -template -bool PylonCameraImpl::setExtendedBrightness(const int& target_brightness, - const float& current_brightness) -{ - assert(target_brightness > 0 && target_brightness <= 255); - - typename CameraTraitT::AutoTargetBrightnessValueType brightness_to_set = - CameraTraitT::convertBrightness(target_brightness); - - if ( !binary_exp_search_ ) - { - if ( brightness_to_set < autoTargetBrightness().GetMin() ) // Range from [0 - 49] - { - binary_exp_search_ = new BinaryExposureSearch(target_brightness, - currentAutoExposureTimeLowerLimit(), - currentExposure(), - currentExposure()); - } - else // Range from [206-255] - { - binary_exp_search_ = new BinaryExposureSearch(target_brightness, - currentExposure(), - currentAutoExposureTimeUpperLimit(), - currentExposure()); - } - } - - if ( binary_exp_search_->isLimitReached() ) - { - disableAllRunningAutoBrightessFunctions(); - ROS_ERROR_STREAM("BinaryExposureSearach reached the exposure limits that " - << "the camera is able to set, but the target_brightness " - << "was not yet reached."); - return false; - } - - if ( !binary_exp_search_->update(current_brightness, currentExposure()) ) - { - disableAllRunningAutoBrightessFunctions(); - return false; - } - - float reached_exposure; - if ( !setExposure(binary_exp_search_->newExposure(), reached_exposure) ) - { - return false; - } - - // failure if we reached min or max exposure limit - // but we return in the next cycle because maybe the current setting leads - // to the target brightness - if ( reached_exposure == exposureTime().GetMin() || - reached_exposure == exposureTime().GetMax() ) - { - binary_exp_search_->limitReached(true); - } - return true; -} - -template -bool PylonCameraImpl::setShutterMode(const SHUTTER_MODE &shutter_mode) -{ - try - { - switch ( shutter_mode ) - { - case pylon_camera::SM_ROLLING: - cam_->ShutterMode.SetValue(ShutterModeEnums::ShutterMode_Rolling); - break; - case pylon_camera::SM_GLOBAL: - cam_->ShutterMode.SetValue(ShutterModeEnums::ShutterMode_Global); - break; - case pylon_camera::SM_GLOBAL_RESET_RELEASE: - cam_->ShutterMode.SetValue(ShutterModeEnums::ShutterMode_GlobalResetRelease); - break; - default: - // keep default setting - break; - } - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while setting shutter mode to " - << shutter_mode << " occurred: " << e.GetDescription()); - return false; - } - return true; -} - -template -float PylonCameraImpl::exposureStep() -{ - return static_cast(exposureTime().GetMin()); -} - -template -float PylonCameraImpl::maxPossibleFramerate() -{ - return static_cast(resultingFrameRate().GetValue()); -} - -template -std::vector PylonCameraImpl::detectAndCountNumUserOutputs() -{ - std::vector user_output_vec; - GenApi::INodeMap& node_map = cam_->GetNodeMap(); - GenApi::CEnumerationPtr output_selector_enumeration_ptr( - node_map.GetNode("UserOutputSelector")); - GenApi::NodeList_t feature_list; - output_selector_enumeration_ptr->GetEntries(feature_list); - for (GenApi::NodeList_t::iterator it = feature_list.begin(); - it != feature_list.end(); - ++it) - { - if ( GenApi::IsAvailable(*it) ) - { - GenApi::CEnumEntryPtr enum_entry(*it); - GenICam::gcstring symbolic_name = enum_entry->GetSymbolic().c_str(); - int num_value = enum_entry->GetNumericValue(); - if ( 0 != typeName().compare("GigE") ) - { - // TODO: @marcel: Contact Basler support why this is necessary - // for all USB-cameras - num_value += 1; - } - user_output_vec.push_back(num_value); - } - } - return user_output_vec; -} - -template -bool PylonCameraImpl::setUserOutput(const int& output_id, - const bool& value) -{ - ROS_DEBUG_STREAM("Setting user_output " << output_id << " to " << value); - try - { - cam_->UserOutputSelector.SetValue(static_cast( - user_output_selector_enums_.at(output_id))); - cam_->UserOutputValue.SetValue(value); - } - catch ( const std::exception& ex ) - { - ROS_ERROR_STREAM("Could not set user output " << output_id << ": " - << ex.what()); - return false; - } - if ( value != cam_->UserOutputValue.GetValue() ) - { - ROS_ERROR_STREAM("Value " << value << " could not be set to output " - << output_id); - return false; - } - return true; -} - -} // namespace pylon_camera - -#endif // PYLON_CAMERA_INTERNAL_BASE_HPP_ diff --git a/include/pylon_camera/internal/impl/pylon_camera_dart.hpp b/include/pylon_camera/internal/impl/pylon_camera_dart.hpp deleted file mode 100644 index f7cca438..00000000 --- a/include/pylon_camera/internal/impl/pylon_camera_dart.hpp +++ /dev/null @@ -1,133 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#ifndef PYLON_CAMERA_INTERNAL_DART_H_ -#define PYLON_CAMERA_INTERNAL_DART_H_ - -#include -#include - -#include - -namespace pylon_camera -{ - -class PylonDARTCamera : public PylonUSBCamera -{ -public: - explicit PylonDARTCamera(Pylon::IPylonDevice* device); - virtual ~PylonDARTCamera(); - - virtual bool applyCamSpecificStartupSettings(const PylonCameraParameter& params); - virtual bool setUserOutput(int output_id, bool value); - virtual std::string typeName() const; - -protected: - virtual bool setupSequencer(const std::vector& exposure_times, - std::vector& exposure_times_set); - virtual bool grab(Pylon::CGrabResultPtr& grab_result); -}; - -PylonDARTCamera::PylonDARTCamera(Pylon::IPylonDevice* device) : - PylonUSBCamera(device) -{} - -PylonDARTCamera::~PylonDARTCamera() -{} - -bool PylonDARTCamera::applyCamSpecificStartupSettings(const PylonCameraParameter& parameters) -{ - if ( !PylonUSBCamera::applyCamSpecificStartupSettings(parameters) ) - { - return false; - } - return true; -} - -bool PylonDARTCamera::setUserOutput(int output_id, bool value) -{ - ROS_ERROR("Dart camera has no digital output."); - return false; -} - -bool PylonDARTCamera::setupSequencer(const std::vector& exposure_times, - std::vector& exposure_times_set) -{ - ROS_ERROR("Sequencer Mode for Dart Cameras not yet implemented"); - return false; -} - -bool PylonDARTCamera::grab(Pylon::CGrabResultPtr& grab_result) -{ - try - { - // /!\ The dart camera device does not support - // 'waitForFrameTriggerReady' - cam_->ExecuteSoftwareTrigger(); - - cam_->RetrieveResult(grab_timeout_, grab_result, - Pylon::TimeoutHandling_ThrowException); - } - catch (const GenICam::GenericException &e) - { - if ( cam_->IsCameraDeviceRemoved() ) - { - ROS_ERROR("Camera was removed"); - } - else - { - ROS_ERROR_STREAM("An image grabbing exception in pylon camera occurred: " - << e.GetDescription()); - } - return false; - } - catch ( ... ) - { - ROS_ERROR("An unspecified image grabbing exception in pylon camera occurred"); - return false; - } - - if ( !grab_result->GrabSucceeded() ) - { - ROS_ERROR_STREAM("Error: " << grab_result->GetErrorCode() - << " " << grab_result->GetErrorDescription()); - return false; - } - - return true; -} - -std::string PylonDARTCamera::typeName() const -{ - return "DART"; -} - -} // namespace pylon_camera - -#endif // PYLON_CAMERA_INTERNAL_DART_H_ diff --git a/include/pylon_camera/internal/impl/pylon_camera_gige.hpp b/include/pylon_camera/internal/impl/pylon_camera_gige.hpp deleted file mode 100644 index a4600e86..00000000 --- a/include/pylon_camera/internal/impl/pylon_camera_gige.hpp +++ /dev/null @@ -1,462 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#ifndef PYLON_CAMERA_INTERNAL_GIGE_H_ -#define PYLON_CAMERA_INTERNAL_GIGE_H_ - -#include -#include - -#include - -#include - -namespace pylon_camera -{ - -struct GigECameraTrait -{ - typedef Pylon::CBaslerGigEInstantCamera CBaslerInstantCameraT; - typedef Basler_GigECameraParams::ExposureAutoEnums ExposureAutoEnums; - typedef Basler_GigECameraParams::GainAutoEnums GainAutoEnums; - typedef Basler_GigECameraParams::PixelFormatEnums PixelFormatEnums; - typedef Basler_GigECameraParams::PixelSizeEnums PixelSizeEnums; - typedef GenApi::IInteger AutoTargetBrightnessType; - typedef GenApi::IInteger GainType; - typedef int64_t AutoTargetBrightnessValueType; - typedef Basler_GigECameraParams::ShutterModeEnums ShutterModeEnums; - typedef Basler_GigECamera::UserOutputSelectorEnums UserOutputSelectorEnums; - typedef Basler_GigECamera::LineSelectorEnums LineSelectorEnums; - typedef Basler_GigECamera::LineModeEnums LineModeEnums; - typedef Basler_GigECamera::LineSourceEnums LineSourceEnums; - - static inline AutoTargetBrightnessValueType convertBrightness(const int& value) - { - return value; - } -}; - -typedef PylonCameraImpl PylonGigECamera; - - -template <> -bool PylonGigECamera::setAutoflash(const std::map flash_on_lines) -{ - // bool acc_auto_flash = false; - for (const std::pair p : flash_on_lines) - { - try - { - ROS_INFO("Executing SetAutoFlash: %i -> %i", p.first, p.second); - if (p.first == 2) - { - if (p.second) - { - // Set to flash - cam_->LineSelector.SetValue(Basler_GigECameraParams::LineSelector_Line2); - cam_->LineMode.SetValue(Basler_GigECameraParams::LineMode_Output); - cam_->LineSource.SetValue(Basler_GigECameraParams::LineSource_ExposureActive); - } - else - { - // Set to default - cam_->LineSelector.SetValue(Basler_GigECameraParams::LineSelector_Line2); - cam_->LineMode.SetValue(Basler_GigECameraParams::LineMode_Output); - cam_->LineSource.SetValue(Basler_GigECameraParams::LineSource_UserOutput1); - } - continue; - } - if (p.first == 3) - { - if (p.second) - { - // Set to flash - cam_->LineSelector.SetValue(Basler_GigECameraParams::LineSelector_Line3); - cam_->LineMode.SetValue(Basler_GigECameraParams::LineMode_Output); - cam_->LineSource.SetValue(Basler_GigECameraParams::LineSource_ExposureActive); - } - else - { - // Set to default - cam_->LineSelector.SetValue(Basler_GigECameraParams::LineSelector_Line3); - cam_->LineMode.SetValue(Basler_GigECameraParams::LineMode_Input); - } - continue; - } - ROS_WARN("Got request to set Flash for line %i, but only 2 and 3 are implemented!", p.first); - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("Error applying cam specific startup setting for GigE cameras: " - << e.GetDescription()); - } - } - return true; -} - -template <> -bool PylonGigECamera::applyCamSpecificStartupSettings(const PylonCameraParameter& parameters) -{ - try - { - // Remove all previous settings (sequencer etc.) - // Default Setting = Free-Running - cam_->UserSetSelector.SetValue(Basler_GigECameraParams::UserSetSelector_Default); - cam_->UserSetLoad.Execute(); - // UserSetSelector_Default overrides Software Trigger Mode !! - cam_->TriggerSource.SetValue(Basler_GigECameraParams::TriggerSource_Software); - cam_->TriggerMode.SetValue(Basler_GigECameraParams::TriggerMode_On); - - /* Thresholds for the AutoExposure Functions: - * - lower limit can be used to get rid of changing light conditions - * due to 50Hz lamps (-> 20ms cycle duration) - * - upper limit is to prevent motion blur - */ - double upper_lim = std::min(parameters.auto_exp_upper_lim_, - cam_->ExposureTimeAbs.GetMax()); - cam_->AutoExposureTimeAbsLowerLimit.SetValue(cam_->ExposureTimeAbs.GetMin()); - cam_->AutoExposureTimeAbsUpperLimit.SetValue(upper_lim); - - cam_->AutoGainRawLowerLimit.SetValue(cam_->GainRaw.GetMin()); - cam_->AutoGainRawUpperLimit.SetValue(cam_->GainRaw.GetMax()); - - // The gain auto function and the exposure auto function can be used at the - // same time. In this case, however, you must also set the - // Auto Function Profile feature. - // acA1920-40gm does not support Basler_GigECameraParams::GainSelector_AnalogAll - // has Basler_GigECameraParams::GainSelector_All instead - // cam_->GainSelector.SetValue(Basler_GigECameraParams::GainSelector_AnalogAll); - - if ( GenApi::IsAvailable(cam_->BinningHorizontal) && - GenApi::IsAvailable(cam_->BinningVertical) ) - { - ROS_INFO_STREAM("Cam has binning range: x(hz) = [" - << cam_->BinningHorizontal.GetMin() << " - " - << cam_->BinningHorizontal.GetMax() << "], y(vt) = [" - << cam_->BinningVertical.GetMin() << " - " - << cam_->BinningVertical.GetMax() << "]."); - } - else - { - ROS_INFO_STREAM("Cam does not support binning."); - } - - ROS_INFO_STREAM("Cam has exposure time range: [" - << cam_->ExposureTimeAbs.GetMin() - << " - " << cam_->ExposureTimeAbs.GetMax() - << "] measured in microseconds."); - ROS_INFO_STREAM("Cam has gain range: [" - << cam_->GainRaw.GetMin() << " - " - << cam_->GainRaw.GetMax() - << "] measured in device specific units."); - - // Check if gamma is available, print range - if ( !GenApi::IsAvailable(cam_->Gamma) ) - { - ROS_WARN("Cam gamma not available, will keep the default (auto)."); - } - else - { - ROS_INFO_STREAM("Cam has gamma range: [" - << cam_->Gamma.GetMin() << " - " - << cam_->Gamma.GetMax() << "]."); - } - - ROS_INFO_STREAM("Cam has pylon auto brightness range: [" - << cam_->AutoTargetValue.GetMin() << " - " - << cam_->AutoTargetValue.GetMax() - << "] which is the average pixel intensity."); - - // raise inter-package delay (GevSCPD) for solving error: - // 'the image buffer was incompletely grabbed' - // also in ubuntu settings -> network -> options -> MTU Size - // from 'automatic' to 3000 if card supports it - // Raspberry PI has MTU = 1500, max value for some cards: 9000 - cam_->GevSCPSPacketSize.SetValue(parameters.mtu_size_); - if (parameters.auto_flash_) - { - std::map flash_on_lines; - ROS_INFO("Flash 2: %i", parameters.auto_flash_line_2_); - ROS_INFO("Flash 3: %i", parameters.auto_flash_line_3_); - - flash_on_lines[2] = parameters.auto_flash_line_2_; - flash_on_lines[3] = parameters.auto_flash_line_3_; - setAutoflash(flash_on_lines); - } - - // http://www.baslerweb.com/media/documents/AW00064902000%20Control%20Packet%20Timing%20With%20Delays.pdf - // inter package delay in ticks (? -> mathi said in nanosec) -> prevent lost frames - // package size * n_cams + 5% overhead = inter package size - // int n_cams = 1; - // int inter_package_delay_in_ticks = n_cams * imageSize() * 1.05; - cam_->GevSCPD.SetValue(parameters.inter_pkg_delay_); - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("Error applying cam specific startup setting for GigE cameras: " - << e.GetDescription()); - return false; - } - return true; -} - - -template <> -bool PylonGigECamera::setupSequencer(const std::vector& exposure_times, - std::vector& exposure_times_set) -{ - try - { - if ( GenApi::IsWritable(cam_->SequenceEnable) ) - { - cam_->SequenceEnable.SetValue(false); - } - else - { - ROS_ERROR("Sequence mode not enabled."); - return false; - } - - cam_->SequenceAdvanceMode = Basler_GigECameraParams::SequenceAdvanceMode_Auto; - cam_->SequenceSetTotalNumber = exposure_times.size(); - - for ( std::size_t i = 0; i < exposure_times.size(); ++i ) - { - // Set parameters for each step - cam_->SequenceSetIndex = i; - float reached_exposure; - setExposure(exposure_times.at(i), reached_exposure); - exposure_times_set.push_back(reached_exposure / 1000000.); - cam_->SequenceSetStore.Execute(); - } - - // config finished - cam_->SequenceEnable.SetValue(true); - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR("%s", e.GetDescription()); - return false; - } - return true; -} - -template <> -GenApi::IFloat& PylonGigECamera::exposureTime() -{ - if ( GenApi::IsAvailable(cam_->ExposureTimeAbs) ) - { - return cam_->ExposureTimeAbs; - } - else - { - throw std::runtime_error("Error while accessing ExposureTimeAbs in PylonGigECamera"); - } -} - -template <> -GigECameraTrait::GainType& PylonGigECamera::gain() -{ - if ( GenApi::IsAvailable(cam_->GainRaw) ) - { - return cam_->GainRaw; - } - else - { - throw std::runtime_error("Error while accessing GainRaw in PylonGigECamera"); - } -} - -/** - * @override - * Overrides the base implementation as the Gamma object might not be available - * for some GigE color cameras when the 'AUTO GAMMA' is activated (see setGamma()). - * - * @returns -1 if Gamma is set to AUTO, returns gamma value if Gamma is set to USER. - */ -template <> -float PylonGigECamera::currentGamma() -{ - if ( !GenApi::IsAvailable(cam_->Gamma) ) - { - ROS_WARN_STREAM("Error while trying to access gamma: cam.Gamma NodeMap" - << " is not available!"); - return -1.; - } - else - { - return static_cast(gamma().GetValue()); - } -} - -template <> -bool PylonGigECamera::setGamma(const float& target_gamma, float& reached_gamma) -{ - // for GigE cameras you have to enable gamma first - if ( GenApi::IsAvailable(cam_->GammaEnable) ) - { - cam_->GammaEnable.SetValue(true); - } - - if ( !GenApi::IsAvailable(cam_->Gamma) ) - { - ROS_WARN_STREAM("Error while trying to set gamma: cam.Gamma NodeMap is" - << " not available!"); - return true; - } - - if ( GenApi::IsAvailable(cam_->GammaSelector) ) - { - // set gamma selector to USER, so that the gamma value has an influence - try - { - cam_->GammaSelector.SetValue(Basler_GigECameraParams::GammaSelector_User); - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while setting gamma selector to" - << " USER occurred: " << e.GetDescription()); - return false; - } - } - - try - { - float gamma_to_set = target_gamma; - if ( gamma().GetMin() > gamma_to_set ) - { - gamma_to_set = gamma().GetMin(); - ROS_WARN_STREAM("Desired gamma unreachable! Setting to lower limit: " - << gamma_to_set); - } - else if ( gamma().GetMax() < gamma_to_set ) - { - gamma_to_set = gamma().GetMax(); - ROS_WARN_STREAM("Desired gamma unreachable! Setting to upper limit: " - << gamma_to_set); - } - gamma().SetValue(gamma_to_set); - reached_gamma = currentGamma(); - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while setting target gamma to " - << target_gamma << " occurred: " << e.GetDescription()); - return false; - } - return true; -} - -template <> -GenApi::IFloat& PylonGigECamera::autoExposureTimeLowerLimit() -{ - if ( GenApi::IsAvailable(cam_->AutoExposureTimeAbsLowerLimit) ) - { - return cam_->AutoExposureTimeAbsLowerLimit; - } - else - { - throw std::runtime_error("Error while accessing AutoExposureTimeAbsLowerLimit in PylonGigECamera"); - } -} - -template <> -GenApi::IFloat& PylonGigECamera::autoExposureTimeUpperLimit() -{ - if ( GenApi::IsAvailable(cam_->AutoExposureTimeAbsUpperLimit) ) - { - return cam_->AutoExposureTimeAbsUpperLimit; - } - else - { - throw std::runtime_error("Error while accessing AutoExposureTimeAbsUpperLimit in PylonGigECamera"); - } -} - -template <> -GigECameraTrait::GainType& PylonGigECamera::autoGainLowerLimit() -{ - if ( GenApi::IsAvailable(cam_->AutoGainRawLowerLimit) ) - { - return cam_->AutoGainRawLowerLimit; - } - else - { - throw std::runtime_error("Error while accessing AutoGainRawLowerLimit in PylonGigECamera"); - } -} - -template <> -GigECameraTrait::GainType& PylonGigECamera::autoGainUpperLimit() -{ - if ( GenApi::IsAvailable(cam_->AutoGainRawUpperLimit) ) - { - return cam_->AutoGainRawUpperLimit; - } - else - { - throw std::runtime_error("Error while accessing AutoGainRawUpperLimit in PylonGigECamera"); - } -} - -template <> -GenApi::IFloat& PylonGigECamera::resultingFrameRate() -{ - if ( GenApi::IsAvailable(cam_->ResultingFrameRateAbs) ) - { - return cam_->ResultingFrameRateAbs; - } - else - { - throw std::runtime_error("Error while accessing ResultingFrameRateAbs in PylonGigECamera"); - } -} - -template <> -GigECameraTrait::AutoTargetBrightnessType& PylonGigECamera::autoTargetBrightness() -{ - if ( GenApi::IsAvailable(cam_->AutoTargetValue) ) - { - return cam_->AutoTargetValue; - } - else - { - throw std::runtime_error("Error while accessing AutoTargetValue in PylonGigECamera"); - } -} - -template <> -std::string PylonGigECamera::typeName() const -{ - return "GigE"; -} - -} // namespace pylon_camera - -#endif // PYLON_CAMERA_INTERNAL_GIGE_H_ diff --git a/include/pylon_camera/internal/impl/pylon_camera_usb.hpp b/include/pylon_camera/internal/impl/pylon_camera_usb.hpp deleted file mode 100644 index 7d309a7d..00000000 --- a/include/pylon_camera/internal/impl/pylon_camera_usb.hpp +++ /dev/null @@ -1,344 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#ifndef PYLON_CAMERA_INTERNAL_USB_H_ -#define PYLON_CAMERA_INTERNAL_USB_H_ - -#include -#include - -#include - -#include - -namespace pylon_camera -{ - -struct USBCameraTrait -{ - typedef Pylon::CBaslerUsbInstantCamera CBaslerInstantCameraT; - typedef Basler_UsbCameraParams::ExposureAutoEnums ExposureAutoEnums; - typedef Basler_UsbCameraParams::GainAutoEnums GainAutoEnums; - typedef Basler_UsbCameraParams::PixelFormatEnums PixelFormatEnums; - typedef Basler_UsbCameraParams::PixelSizeEnums PixelSizeEnums; - typedef GenApi::IFloat AutoTargetBrightnessType; - typedef GenApi::IFloat GainType; - typedef double AutoTargetBrightnessValueType; - typedef Basler_UsbCameraParams::ShutterModeEnums ShutterModeEnums; - typedef Basler_UsbCameraParams::UserOutputSelectorEnums UserOutputSelectorEnums; - - static inline AutoTargetBrightnessValueType convertBrightness(const int& value) - { - return value / 255.0; - } -}; - -typedef PylonCameraImpl PylonUSBCamera; - -template <> -bool PylonUSBCamera::applyCamSpecificStartupSettings(const PylonCameraParameter& parameters) -{ - try - { - // Remove all previous settings (sequencer etc.) - // Default Setting = Free-Running - cam_->UserSetSelector.SetValue(Basler_UsbCameraParams::UserSetSelector_Default); - cam_->UserSetLoad.Execute(); - // UserSetSelector_Default overrides Software Trigger Mode !! - cam_->TriggerSource.SetValue(Basler_UsbCameraParams::TriggerSource_Software); - cam_->TriggerMode.SetValue(Basler_UsbCameraParams::TriggerMode_On); - - /* Thresholds for the AutoExposure Functions: - * - lower limit can be used to get rid of changing light conditions - * due to 50Hz lamps (-> 20ms cycle duration) - * - upper limit is to prevent motion blur - */ - double upper_lim = std::min(parameters.auto_exp_upper_lim_, - cam_->ExposureTime.GetMax()); - cam_->AutoExposureTimeLowerLimit.SetValue(cam_->ExposureTime.GetMin()); - cam_->AutoExposureTimeUpperLimit.SetValue(upper_lim); - - cam_->AutoGainLowerLimit.SetValue(cam_->Gain.GetMin()); - cam_->AutoGainUpperLimit.SetValue(cam_->Gain.GetMax()); - - // The gain auto function and the exposure auto function can be used at the same time. In this case, - // however, you must also set the Auto Function Profile feature. - // cam_->AutoFunctionProfile.SetValue(Basler_UsbCameraParams::AutoFunctionProfile_MinimizeGain); - - if ( GenApi::IsAvailable(cam_->BinningHorizontal) && - GenApi::IsAvailable(cam_->BinningVertical) ) - { - ROS_INFO_STREAM("Cam has binning range: x(hz) = [" - << cam_->BinningHorizontal.GetMin() << " - " - << cam_->BinningHorizontal.GetMax() << "], y(vt) = [" - << cam_->BinningVertical.GetMin() << " - " - << cam_->BinningVertical.GetMax() << "]."); - } - else - { - ROS_INFO_STREAM("Cam does not support binning."); - } - - ROS_INFO_STREAM("Cam has exposure time range: [" << cam_->ExposureTime.GetMin() - << " - " << cam_->ExposureTime.GetMax() - << "] measured in microseconds."); - ROS_INFO_STREAM("Cam has gain range: [" << cam_->Gain.GetMin() - << " - " << cam_->Gain.GetMax() - << "] measured in dB."); - ROS_INFO_STREAM("Cam has gammma range: [" - << cam_->Gamma.GetMin() << " - " - << cam_->Gamma.GetMax() << "]."); - ROS_INFO_STREAM("Cam has pylon auto brightness range: [" - << cam_->AutoTargetBrightness.GetMin() * 255 << " - " - << cam_->AutoTargetBrightness.GetMax() * 255 - << "] which is the average pixel intensity."); - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("Error applying cam specific startup setting for USB cameras: " - << e.GetDescription()); - return false; - } - return true; -} - -template <> -bool PylonUSBCamera::setupSequencer(const std::vector& exposure_times, - std::vector& exposure_times_set) -{ - try - { - // Runtime Sequencer: cam_->IsGrabbing() ? cam_->StopGrabbing(); //10ms - if ( GenApi::IsWritable(cam_->SequencerMode) ) - { - cam_->SequencerMode.SetValue(Basler_UsbCameraParams::SequencerMode_Off); - } - else - { - ROS_ERROR("Sequencer Mode not writable"); - } - - cam_->SequencerConfigurationMode.SetValue(Basler_UsbCameraParams::SequencerConfigurationMode_On); - - // **** valid for all sets: reset on software signal 1 **** - int64_t initial_set = cam_->SequencerSetSelector.GetMin(); - - cam_->SequencerSetSelector.SetValue(initial_set); - cam_->SequencerPathSelector.SetValue(0); - cam_->SequencerSetNext.SetValue(initial_set); - cam_->SequencerTriggerSource.SetValue(Basler_UsbCameraParams::SequencerTriggerSource_SoftwareSignal1); - // advance on Frame Start - cam_->SequencerPathSelector.SetValue(1); - cam_->SequencerTriggerSource.SetValue(Basler_UsbCameraParams::SequencerTriggerSource_FrameStart); - // ******************************************************** - - for ( std::size_t i = 0; i < exposure_times.size(); ++i ) - { - if ( i > 0 ) - { - cam_->SequencerSetSelector.SetValue(i); - } - - if ( i == exposure_times.size() - 1 ) // last frame - { - cam_->SequencerSetNext.SetValue(0); - } - else - { - cam_->SequencerSetNext.SetValue(i + 1); - } - float reached_exposure; - setExposure(exposure_times.at(i), reached_exposure); - exposure_times_set.push_back(reached_exposure / 1000000.); - cam_->SequencerSetSave.Execute(); - } - - // config finished - cam_->SequencerConfigurationMode.SetValue(Basler_UsbCameraParams::SequencerConfigurationMode_Off); - cam_->SequencerMode.SetValue(Basler_UsbCameraParams::SequencerMode_On); - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("ERROR while initializing pylon sequencer: " - << e.GetDescription()); - return false; - } - return true; -} - -template <> -GenApi::IFloat& PylonUSBCamera::exposureTime() -{ - if ( GenApi::IsAvailable(cam_->ExposureTime) ) - { - return cam_->ExposureTime; - } - else - { - throw std::runtime_error("Error while accessing ExposureTime in PylonUSBCamera"); - } -} - -template <> -USBCameraTrait::GainType& PylonUSBCamera::gain() -{ - if ( GenApi::IsAvailable(cam_->Gain) ) - { - return cam_->Gain; - } - else - { - throw std::runtime_error("Error while accessing Gain in PylonUSBCamera"); - } -} - -template <> -bool PylonUSBCamera::setGamma(const float& target_gamma, float& reached_gamma) -{ - if ( !GenApi::IsAvailable(cam_->Gamma) ) - { - ROS_ERROR_STREAM("Error while trying to set gamma: cam.Gamma NodeMap is" - << " not available!"); - return false; - } - - try - { - float gamma_to_set = target_gamma; - if ( gamma().GetMin() > gamma_to_set ) - { - gamma_to_set = gamma().GetMin(); - ROS_WARN_STREAM("Desired gamma unreachable! Setting to lower limit: " - << gamma_to_set); - } - else if ( gamma().GetMax() < gamma_to_set ) - { - gamma_to_set = gamma().GetMax(); - ROS_WARN_STREAM("Desired gamma unreachable! Setting to upper limit: " - << gamma_to_set); - } - gamma().SetValue(gamma_to_set); - reached_gamma = currentGamma(); - } - catch ( const GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while setting target gamma to " - << target_gamma << " occurred: " << e.GetDescription()); - return false; - } - return true; -} - -template <> -GenApi::IFloat& PylonUSBCamera::autoExposureTimeLowerLimit() -{ - if ( GenApi::IsAvailable(cam_->AutoExposureTimeLowerLimit) ) - { - return cam_->AutoExposureTimeLowerLimit; - } - else - { - throw std::runtime_error("Error while accessing AutoExposureTimeLowerLimit in PylonUSBCamera"); - } -} - -template <> -GenApi::IFloat& PylonUSBCamera::autoExposureTimeUpperLimit() -{ - if ( GenApi::IsAvailable(cam_->AutoExposureTimeUpperLimit) ) - { - return cam_->AutoExposureTimeUpperLimit; - } - else - { - throw std::runtime_error("Error while accessing AutoExposureTimeUpperLimit in PylonUSBCamera"); - } -} - -template <> -USBCameraTrait::GainType& PylonUSBCamera::autoGainLowerLimit() -{ - if ( GenApi::IsAvailable(cam_->AutoGainLowerLimit) ) - { - return cam_->AutoGainLowerLimit; - } - else - { - throw std::runtime_error("Error while accessing AutoGainLowerLimit in PylonUSBCamera"); - } -} - -template <> -USBCameraTrait::GainType& PylonUSBCamera::autoGainUpperLimit() -{ - if ( GenApi::IsAvailable(cam_->AutoGainUpperLimit) ) - { - return cam_->AutoGainUpperLimit; - } - else - { - throw std::runtime_error("Error while accessing AutoGainUpperLimit in PylonUSBCamera"); - } -} - -template <> -GenApi::IFloat& PylonUSBCamera::resultingFrameRate() -{ - if ( GenApi::IsAvailable(cam_->ResultingFrameRate) ) - { - return cam_->ResultingFrameRate; - } - else - { - throw std::runtime_error("Error while accessing ResultingFrameRate in PylonUSBCamera"); - } -} - -template <> -USBCameraTrait::AutoTargetBrightnessType& PylonUSBCamera::autoTargetBrightness() -{ - if ( GenApi::IsAvailable(cam_->AutoTargetBrightness) ) - { - return cam_->AutoTargetBrightness; - } - else - { - throw std::runtime_error("Error while accessing AutoTargetBrightness in PylonUSBCamera"); - } -} - -template <> -std::string PylonUSBCamera::typeName() const -{ - return "USB"; -} - -} // namespace pylon_camera - -#endif // PYLON_CAMERA_INTERNAL_USB_H_ diff --git a/include/pylon_camera/internal/pylon_camera.h b/include/pylon_camera/internal/pylon_camera.h deleted file mode 100644 index 91c99e67..00000000 --- a/include/pylon_camera/internal/pylon_camera.h +++ /dev/null @@ -1,189 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#ifndef PYLON_CAMERA_INTERNAL_PYLON_CAMERA_H -#define PYLON_CAMERA_INTERNAL_PYLON_CAMERA_H - -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#pragma GCC diagnostic ignored "-Wliteral-suffix" - -#include -#include -#include -#include -#include - -#include -#include - -namespace pylon_camera -{ - -template -class PylonCameraImpl : public PylonCamera -{ -public: - explicit PylonCameraImpl(Pylon::IPylonDevice* device); - - virtual ~PylonCameraImpl(); - - virtual bool registerCameraConfiguration(); - - virtual bool openCamera(); - - virtual bool isCamRemoved(); - - virtual bool setupSequencer(const std::vector& exposure_times); - - virtual bool applyCamSpecificStartupSettings(const PylonCameraParameter& parameters); - - virtual bool startGrabbing(const PylonCameraParameter& parameters); - - virtual bool grab(std::vector& image); - - virtual bool grab(uint8_t* image); - - virtual bool setShutterMode(const pylon_camera::SHUTTER_MODE& mode); - - virtual bool setROI(const sensor_msgs::RegionOfInterest target_roi, - sensor_msgs::RegionOfInterest& reached_roi); - - virtual bool setBinningX(const size_t& target_binning_x, - size_t& reached_binning_x); - - virtual bool setBinningY(const size_t& target_binning_y, - size_t& reached_binning_y); - - virtual bool setImageEncoding(const std::string& target_ros_encoding); - - virtual bool setExposure(const float& target_exposure, float& reached_exposure); - - virtual bool setAutoflash(const std::map flash_on_lines); - - virtual bool setGain(const float& target_gain, float& reached_gain); - - virtual bool setGamma(const float& target_gamma, float& reached_gamma); - - virtual bool setBrightness(const int& target_brightness, - const float& current_brightness, - const bool& exposure_auto, - const bool& gain_auto); - - virtual std::vector detectAndCountNumUserOutputs(); - - virtual bool setUserOutput(const int& output_id, const bool& value); - - virtual size_t currentOffsetX(); - - virtual size_t currentOffsetY(); - - virtual sensor_msgs::RegionOfInterest currentROI(); - - virtual size_t currentBinningX(); - - virtual size_t currentBinningY(); - - virtual std::vector detectAvailableImageEncodings(); - - virtual std::string currentROSEncoding() const; - - virtual int imagePixelDepth() const; - - virtual float currentExposure(); - - virtual float currentAutoExposureTimeLowerLimit(); - - virtual float currentAutoExposureTimeUpperLimit(); - - virtual float currentGain(); - - virtual float currentAutoGainLowerLimit(); - - virtual float currentAutoGainUpperLimit(); - - virtual float currentGamma(); - - virtual float maxPossibleFramerate(); - - virtual bool isPylonAutoBrightnessFunctionRunning(); - - virtual bool isBrightnessSearchRunning(); - - virtual void disableAllRunningAutoBrightessFunctions(); - - virtual void enableContinuousAutoExposure(); - - virtual void enableContinuousAutoGain(); - - virtual std::string typeName() const; - - virtual float exposureStep(); - -protected: - typedef typename CameraTraitT::CBaslerInstantCameraT CBaslerInstantCameraT; - typedef typename CameraTraitT::ExposureAutoEnums ExposureAutoEnums; - typedef typename CameraTraitT::GainAutoEnums GainAutoEnums; - typedef typename CameraTraitT::PixelFormatEnums PixelFormatEnums; - typedef typename CameraTraitT::PixelSizeEnums PixelSizeEnums; - typedef typename CameraTraitT::AutoTargetBrightnessType AutoTargetBrightnessType; - typedef typename CameraTraitT::GainType GainType; - typedef typename CameraTraitT::ShutterModeEnums ShutterModeEnums; - typedef typename CameraTraitT::UserOutputSelectorEnums UserOutputSelectorEnums; - - CBaslerInstantCameraT* cam_; - - // Each camera has it's own getter for GenApi accessors that are named - // differently for USB and GigE - GenApi::IFloat& exposureTime(); - GainType& gain(); - GenApi::IFloat& gamma(); - GenApi::IFloat& autoExposureTimeLowerLimit(); - GenApi::IFloat& autoExposureTimeUpperLimit(); - GainType& autoGainLowerLimit(); - GainType& autoGainUpperLimit(); - GenApi::IFloat& resultingFrameRate(); - AutoTargetBrightnessType& autoTargetBrightness(); - - virtual bool setExtendedBrightness(const int& target_brightness, - const float& current_brightness); - - virtual bool grab(Pylon::CGrabResultPtr& grab_result); - - virtual bool setupSequencer(const std::vector& exposure_times, - std::vector& exposure_times_set); -}; - -} // namespace pylon_camera - -#include -#include -#include -#include - -#endif // PYLON_CAMERA_INTERNAL_PYLON_CAMERA_H diff --git a/include/pylon_camera/pylon_camera.h b/include/pylon_camera/pylon_camera.h deleted file mode 100644 index 4073898f..00000000 --- a/include/pylon_camera/pylon_camera.h +++ /dev/null @@ -1,524 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#ifndef PYLON_CAMERA_PYLON_CAMERA_H -#define PYLON_CAMERA_PYLON_CAMERA_H - -#include -#include -#include - -#include -#include -#include - -namespace pylon_camera -{ - -// Number of channels for image encoding -#define CHANNEL_MONO8 1 -#define CHANNEL_RGB8 3 - -/** - * The PylonCamera base class. Create a new instance using the static create() functions. - */ -class PylonCamera -{ -public: - /** - * Create a new PylonCamera instance. It will return the first camera that could be found. - * @return new PylonCamera instance or NULL if no camera was found. - */ - static PylonCamera* create(); - - /** - * Create a new PylonCamera instance based on the DeviceUserID of the camera. - * @param device_user_id Pylon DeviceUserID. If the string is empty, the - * first camera that could be found is returned. - * @return new PylonCamera instance or NULL if the camera was not found. - */ - static PylonCamera* create(const std::string& device_user_id); - - /** - * Configures the camera according to the software trigger mode. - * @return true if all the configuration could be set up. - */ - virtual bool registerCameraConfiguration() = 0; - - /** - * Opens the desired camera, the communication starts from now on. - * @return true if the camera could be opened. - */ - virtual bool openCamera() = 0; - - /** - * Returns the connection state of the camera device. - * @return true if the camera device removal from the PC has been detected. - */ - virtual bool isCamRemoved() = 0; - - /** - * Configure the sequencer exposure times. - * @param exposure_times the list of exposure times. - * @return true if all parameters could be sent to the camera. - */ - virtual bool setupSequencer(const std::vector& exposure_times) = 0; - - /** - * Configures the camera according to the provided ros parameters. - * This will use the device specific parameters as e.g. the mtu size for - * GigE-Cameras - * @param parameters The PylonCameraParameter set to use - * @return true if all parameters could be sent to the camera. - */ - virtual bool applyCamSpecificStartupSettings(const PylonCameraParameter& parameters) = 0; - - /** - * Initializes the internal parameters of the PylonCamera instance. - * @param parameters The PylonCameraParameter set to use - * @return true if all parameters could be sent to the camera. - */ - virtual bool startGrabbing(const PylonCameraParameter& parameters) = 0; - - /** - * Grab a camera frame and copy the result into image - * @param image reference to the output image. - * @return true if the image was grabbed successfully. - */ - virtual bool grab(std::vector& image) = 0; - - /** - * Grab a camera frame and copy the result into image - * @param image pointer to the image buffer. - * Caution: Make sure the buffer is initialized correctly! - * @return true if the image was grabbed successfully. - */ - virtual bool grab(uint8_t* image) = 0; - - /** - * @brief sets shutter mode for the camera (rolling or global_reset) - * @param mode - * @return - */ - virtual bool setShutterMode(const pylon_camera::SHUTTER_MODE& mode) = 0; - - /** - * Update area of interest in the camera image - * @param target_roi the target roi - * @param reached_roi the roi that could be set - * @return true if the targeted roi could be reached - */ - virtual bool setROI(const sensor_msgs::RegionOfInterest target_roi, - sensor_msgs::RegionOfInterest& reached_roi) = 0; - - /** - * Sets the target horizontal binning_x factor - * @param target_binning_x the target horizontal binning_x factor. - * @param reached_binning_x the reached horizontal binning_x factor. - * @return false if a communication error occurred or true otherwise. - */ - virtual bool setBinningX(const size_t& target_binning_x, - size_t& reached_binning_x) = 0; - - /** - * Sets the target vertical binning_y factor - * @param target_binning_y the target vertical binning_y factor. - * @param reached_binning_y the reached vertical binning_y factor. - * @return false if a communication error occurred or true otherwise. - */ - virtual bool setBinningY(const size_t& target_binning_y, - size_t& reached_binning_y) = 0; - - /** - * Detects the supported image pixel encodings of the camera an stores - * them in a vector. - * @return a list of strings describing the supported encodings in GenAPI - * language. - */ - virtual std::vector detectAvailableImageEncodings() = 0; - - /** - * Sets the desired image pixel encoding (channel meaning, ordering, size) - * taken from the list of strings in include/sensor_msgs/image_encodings.h - * The supported encodings are 'mono8', 'bgr8', 'rgb8', 'bayer_bggr8', - * 'bayer_gbrg8', 'bayer_rggb8' and 'yuv422' - * @param target_ros_endcoding: string describing the encoding. - * @return false if a communication error occurred or true otherwise. - */ - virtual bool setImageEncoding(const std::string& target_ros_encoding) = 0; - - /** - * Sets the exposure time in microseconds - * @param target_exposure the desired exposure time to set in microseconds. - * @param reached_exposure time in microseconds - * @return false if a communication error occurred or true otherwise. - */ - virtual bool setExposure(const float& target_exposure, - float& reached_exposure) = 0; - - /** - * Sets autoflash active for the specified lines - * @param flash_on_lines map from line e.g. 1 or 2 to a boolean to - activate or deactivate the autoflash for this line . - * @return false if a communication error occurred or true otherwise. - */ - virtual bool setAutoflash(const std::map flash_on_lines) = 0; - /** - * Sets the gain in percent independent of the camera type - * @param target_gain the target gain in percent. - * @param reached_gain the reached gain in percent. - * @return false if a communication error occurred or true otherwise. - */ - virtual bool setGain(const float& target_gain, float& reached_gain) = 0; - - /** - * Sets the target gamma value - * @param target_gamma the target gamma value. - * @param reached_gamma the reached gamma value. - * @return false if a communication error occurred or true otherwise. - */ - virtual bool setGamma(const float& target_gamma, float& reached_gamma) = 0; - - /** - * Sets the target brightness - * Setting the exposure time to -1 enables the AutoExposureContinuous mode. - * Setting the exposure time to 0 disables the AutoExposure function. - * If the target exposure time is not in the range of Pylon's auto target brightness range - * the extended brightness search is started. - * @param target_brightness is the desired brightness. Range is [1...255]. - * @param current_brightness is the current brightness with the given settings. - * @param exposure_auto flag which indicates if the target_brightness - * should be reached adapting the exposure time - * @param gain_auto flag which indicates if the target_brightness should be - * reached adapting the gain. - * @return true if the brightness could be reached or false otherwise. - */ - virtual bool setBrightness(const int& target_brightness, - const float& current_brightness, - const bool& exposure_auto, - const bool& gain_auto) = 0; - - /** - * @brief Detects and counts the number of user-settable-outputs the cam - * provides. This might be zero for some cameras. The size affects - * the number of 'set' ros-services the camera_node will provide. - * A vector which length equals the number of user-settable outputs - * will be generated. Hence e.g. output '1' can be accessed via - * user_output_selector_enums_.at(1). - * @return the UserOutputSelector enum list - */ - virtual std::vector detectAndCountNumUserOutputs() = 0; - - /** - * @brief setUserOutput sets the digital output - * @param output_id - * @param value goal value for output - * @return true if value was set - */ - virtual bool setUserOutput(const int& output_id, const bool& value) = 0; - - /** - * Returns the current x offset setting. - * @return the horizontal x offset setting. - */ - virtual size_t currentOffsetX() = 0; - - /** - * Returns the current y offset setting. - * @return the horizontal y offset setting. - */ - virtual size_t currentOffsetY() = 0; - - /** - * Returns the current roi setting. - * @return the roi setting. - */ - virtual sensor_msgs::RegionOfInterest currentROI() = 0; - - /** - * Returns the current horizontal binning_x setting. - * @return the horizontal binning_x setting. - */ - virtual size_t currentBinningX() = 0; - - /** - * Returns the current vertical binning_y setting. - * @return the vertical binning_y setting. - */ - virtual size_t currentBinningY() = 0; - - /** - * Get the camera image encoding according to sensor_msgs::image_encodings - * The supported encodings are 'mono8', 'bgr8', 'rgb8', 'bayer_bggr8', - * 'bayer_gbrg8', 'bayer_rggb8' and 'yuv422' - * @return the current ros image pixel encoding. - */ - virtual std::string currentROSEncoding() const = 0; - - /** - * Get the number of bytes per pixel - * @return number of bytes per pixel - */ - virtual int imagePixelDepth() const = 0; - - /** - * Returns the current exposure time in microseconds. - * @return the exposure time in microseconds. - */ - virtual float currentExposure() = 0; - - /** - * Returns the current auto exposure time lower limit - * @return the current auto exposure time lower limit - */ - virtual float currentAutoExposureTimeLowerLimit() = 0; - - /** - * Returns the current auto exposure time upper limit - * @return the current auto exposure time upper limit - */ - virtual float currentAutoExposureTimeUpperLimit() = 0; - - /** - * Returns the current gain in percent. - * @return the gain time percent. - */ - virtual float currentGain() = 0; - - /** - * Returns the current auto gain lower limit - * @return the current auto gain lower limit - */ - virtual float currentAutoGainLowerLimit() = 0; - - /** - * Returns the current auto gain upper limit - * @return the current auto gain upper limit - */ - virtual float currentAutoGainUpperLimit() = 0; - /** - * Returns the current gamma value. - * @return the gamma value. - */ - virtual float currentGamma() = 0; - - /** - * Checks if the camera currently tries to regulate towards a target brightness. - * This can either be done by pylon for the range [50 - 205] or the own extendended binary search one - * for the ranges [1 - 49] and [206 - 254]. - * @return true if the brightness-search is running - */ - virtual bool isBrightnessSearchRunning() = 0; - - /** - * Checks if the auto brightness function from the Pylon API is enabled. - * @return true if AutoExposure is set to AutoExposureContinuous or AutoExposureOnce. - */ - virtual bool isPylonAutoBrightnessFunctionRunning() = 0; - - /** - * Getter for is_binary_exposure_search_running_ - * @return true if the extended exposure search is running - */ - const bool& isBinaryExposureSearchRunning() const; - - /** - * Disables all currently running brightness search methods in case that - * the desired brightness is reached or a timeout occurred - */ - virtual void disableAllRunningAutoBrightessFunctions() = 0; - - /** - * Enables the continuous auto exposure mode - */ - virtual void enableContinuousAutoExposure() = 0; - - /** - * Enables the continuous auto gain mode - */ - virtual void enableContinuousAutoGain() = 0; - - /** - * Get the camera type. Currently supported cameras are USB, DART and GigE - * @return camera type as string - */ - virtual std::string typeName() const = 0; - - /** - * Minimum possible increment between two possible exposure values - * @return the minimum possible increment between two possible exposure values - */ - virtual float exposureStep() = 0; - - /** - * Getter for the device user id of the used camera - * @return the device_user_id - */ - const std::string& deviceUserID() const; - - /** - * Getter for the image height - * @return number of rows in the image - */ - const size_t& imageRows() const; - - /** - * Getter for the image width - * @return number of columns in the image - */ - const size_t& imageCols() const; - - /** - * Getter for the is_ready_ flag. This is set in case that the - * grab-result-pointer of the first acquisition contains valid data. - * Hence this is the current state of the interface - * @return true if the interface is ready - */ - const bool& isReady() const; - - /** - * Returns the number of digital user outputs, which can be set by the - * camera. Might be zero for some cameras. The size affects the number of - * 'set' ros-services the camera node will provide - * @return number of digital user outputs - */ - std::size_t numUserOutputs() const; - - /** - * Returns the image size in bytes - * @return the image size in bytes - */ - const size_t& imageSize() const; - - /** - * Get the maximum achievable frame rate - * @return float - */ - virtual float maxPossibleFramerate() = 0; - - /** - * Checks if the camera has the auto exposure feature. - * @return true if the camera supports auto exposure. - */ - const bool& hasAutoExposure() const; - - /** - * Max allowed delta between target and reached brightness - * @return the allowed tolerance. - */ - const float& maxBrightnessTolerance() const; - - /** - * Getter for the sequencer exposure times. - * @return the list of exposure times - */ - const std::vector& sequencerExposureTimes() const; - - virtual ~PylonCamera(); -protected: - /** - * Protected default constructor. - */ - PylonCamera(); - - /** - * Enables the extended brightness search. - * @param brightness target brightness - * @return true after reaching the target brightness. - */ - virtual bool setExtendedBrightness(const int& target_brightness, - const float& current_brightness) = 0; - - /** - * Parameters for the extended brightness search - */ - BinaryExposureSearch* binary_exp_search_; - - /** - * The DeviceUserID of the found camera - */ - std::string device_user_id_; - - /** - * Number of image rows. - */ - size_t img_rows_; - - /** - * Number of image columns. - */ - size_t img_cols_; - - /** - * The size of the image in number of bytes. - */ - size_t img_size_byte_; - - /** - * The max time a single grab is allowed to take. This value should always - * be greater then the max possible exposure time of the camera - */ - float grab_timeout_; - - /** - * Flag which is set in case that the grab-result-pointer of the first - * acquisition contains valid data - */ - bool is_ready_; - - /** - * True if the extended binary exposure search is running. - */ - bool is_binary_exposure_search_running_; - - /** - * Max allowed delta between target and reached brightness - */ - const float max_brightness_tolerance_; - - /** - * Exposure times to use when in sequencer mode. - */ - std::vector seq_exp_times_; - - /** - * Vector containing all available user outputs. - */ - std::vector user_output_selector_enums_; - - /** - * Vector that contains the available image_encodings the camera supports. - * The strings describe the GenAPI encoding. - */ - std::vector available_image_encodings_; -}; - -} // namespace pylon_camera - -#endif // PYLON_CAMERA_PYLON_CAMERA_H diff --git a/include/pylon_camera/pylon_camera_node.h b/include/pylon_camera/pylon_camera_node.h deleted file mode 100644 index 76bba365..00000000 --- a/include/pylon_camera/pylon_camera_node.h +++ /dev/null @@ -1,411 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#ifndef PYLON_CAMERA_PYLON_CAMERA_NODE_H -#define PYLON_CAMERA_PYLON_CAMERA_NODE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace pylon_camera -{ - -typedef actionlib::SimpleActionServer GrabImagesAS; - -/** - * The ROS-node of the pylon_camera interface - */ -class PylonCameraNode -{ -public: - PylonCameraNode(); - virtual ~PylonCameraNode(); - - /** - * initialize the camera and the ros node. - * calls ros::shutdown if an error occurs. - */ - void init(); - - /** - * spin the node - */ - virtual void spin(); - - /** - * Getter for the frame rate set by the launch script or from the ros parameter - * server - * @return the desired frame rate. - */ - const double& frameRate() const; - - /** - * Getter for the tf frame. - * @return the camera frame. - */ - const std::string& cameraFrame() const; - -protected: - /** - * Creates the camera instance and starts the services and action servers. - * @return false if an error occurred - */ - bool initAndRegister(); - - /** - * Start the camera and initialize the messages - * @return - */ - bool startGrabbing(); - - /** - * Initializing of img_rect_pub_, grab_img_rect_as_ and the pinhole_model_, - * in case that a valid camera info has been set - * @return - */ - void setupRectification(); - - /** - * Returns the total number of subscribers on any advertised image topic. - */ - uint32_t getNumSubscribers() const; - - /** - * Returns the number of subscribers for the raw image topic - */ - uint32_t getNumSubscribersRaw() const; - - /** - * Returns the number of subscribers for the rect image topic - */ - uint32_t getNumSubscribersRect() const; - - /** - * Grabs an image and stores the image in img_raw_msg_ - * @return false if an error occurred. - */ - virtual bool grabImage(); - - /** - * Fills the ros CameraInfo-Object with the image dimensions - */ - virtual void setupInitialCameraInfo(sensor_msgs::CameraInfo& cam_info_msg); - - /** - * Update area of interest in the camera image - * @param target_roi the target roi - * @param reached_roi the roi that could be set - * @return true if the targeted roi could be reached - */ - bool setROI(const sensor_msgs::RegionOfInterest target_roi, - sensor_msgs::RegionOfInterest& reached_roi); - - /** - * Update the horizontal binning_x factor to get downsampled images - * @param target_binning_x the target horizontal binning_x factor - * @param reached_binning_x the horizontal binning_x factor that could be - * reached - * @return true if the targeted binning could be reached - */ - bool setBinningX(const size_t& target_binning_x, - size_t& reached_binning_x); - - /** - * Update the vertical binning_y factor to get downsampled images - * @param target_binning_y the target vertical binning_y factor - * @param reached_binning_y the vertical binning_y factor that could be - * reached - * @return true if the targeted binning could be reached - */ - bool setBinningY(const size_t& target_binning_y, - size_t& reached_binning_y); - - /** - * Service callback for updating the cameras binning setting - * @param req request - * @param res response - * @return true on success - */ - bool setBinningCallback(camera_control_msgs::SetBinning::Request &req, - camera_control_msgs::SetBinning::Response &res); - - /** - * Service callback for updating the cameras roi setting - * @param req request - * @param res response - * @return true on success - */ - bool setROICallback(camera_control_msgs::SetROI::Request &req, - camera_control_msgs::SetROI::Response &res); - - /** - * Update the exposure value on the camera - * @param target_exposure the targeted exposure - * @param reached_exposure the exposure that could be reached - * @return true if the targeted exposure could be reached - */ - bool setExposure(const float& target_exposure, float& reached_exposure); - - /** - * Service callback for setting the exposure - * @param req request - * @param res response - * @return true on success - */ - bool setExposureCallback(camera_control_msgs::SetExposure::Request &req, - camera_control_msgs::SetExposure::Response &res); - - /** - * Sets the target brightness which is the intensity-mean over all pixels. - * If the target exposure time is not in the range of Pylon's auto target - * brightness range the extended brightness search is started. - * The Auto function of the Pylon-API supports values from [50 - 205]. - * Using a binary search, this range will be extended up to [1 - 255]. - * @param target_brightness is the desired brightness. Range is [1...255]. - * @param current_brightness is the current brightness with the given settings. - * @param exposure_auto flag which indicates if the target_brightness - * should be reached adapting the exposure time - * @param gain_auto flag which indicates if the target_brightness should be - * reached adapting the gain. - * @return true if the brightness could be reached or false otherwise. - */ - bool setBrightness(const int& target_brightness, - int& reached_brightness, - const bool& exposure_auto, - const bool& gain_auto); - - /** - * Service callback for setting the brightness - * @param req request - * @param res response - * @return true on success - */ - bool setBrightnessCallback(camera_control_msgs::SetBrightness::Request &req, - camera_control_msgs::SetBrightness::Response &res); - - /** - * Update the gain from the camera to a target gain in percent - * @param target_gain the targeted gain in percent - * @param reached_gain the gain that could be reached - * @return true if the targeted gain could be reached - */ - bool setGain(const float& target_gain, float& reached_gain); - - /** - * Service callback for setting the desired gain in percent - * @param req request - * @param res response - * @return true on success - */ - bool setGainCallback(camera_control_msgs::SetGain::Request &req, - camera_control_msgs::SetGain::Response &res); - - /** - * Update the gamma from the camera to a target gamma correction value - * @param target_gamma the targeted gamma - * @param reached_gamma the gamma that could be reached - * @return true if the targeted gamma could be reached - */ - bool setGamma(const float& target_gamma, float& reached_gamma); - - /** - * Service callback for setting the desired gamma correction value - * @param req request - * @param res response - * @return true on success - */ - bool setGammaCallback(camera_control_msgs::SetGamma::Request &req, - camera_control_msgs::SetGamma::Response &res); - - /** - * Callback that puts the camera to sleep - * @param req request - * @param res response - * @return true on success - */ - bool setSleepingCallback(camera_control_msgs::SetSleeping::Request &req, - camera_control_msgs::SetSleeping::Response &res); - - /** - * Returns true if the camera was put into sleep mode - * @return true if in sleep mode - */ - bool isSleeping(); - - /** - * Generates the subset of points on which the brightness search will be - * executed in order to speed it up. The subset are the indices of the - * one-dimensional image_raw data vector. The base generation is done in a - * recursive manner, by calling genSamplingIndicesRec - * @return indices describing the subset of points - */ - void setupSamplingIndices(std::vector& indices, - std::size_t rows, - std::size_t cols, - int downsampling_factor); - - /** - * This function will recursively be called from above setupSamplingIndices() - * to generate the indices of pixels given the actual ROI. - * @return indices describing the subset of points - */ - void genSamplingIndicesRec(std::vector& indices, - const std::size_t& min_window_height, - const cv::Point2i& start, - const cv::Point2i& end); - - /** - * Calculates the mean brightness of the image based on the subset indices - * @return the mean brightness of the image - */ - float calcCurrentBrightness(); - - /** - * Callback for the grab images action - * @param goal the goal - */ - void grabImagesRawActionExecuteCB( - const camera_control_msgs::GrabImagesGoal::ConstPtr& goal); - - /** - * Callback for the grab rectified images action - * @param goal the goal - */ - void grabImagesRectActionExecuteCB( - const camera_control_msgs::GrabImagesGoal::ConstPtr& goal); - /** - * This function can also be called from the derived PylonCameraOpenCV-Class - */ - camera_control_msgs::GrabImagesResult grabImagesRaw( - const camera_control_msgs::GrabImagesGoal::ConstPtr& goal, - GrabImagesAS* action_server); - - void initCalibrationMatrices(sensor_msgs::CameraInfo& info, - const cv::Mat& D, - const cv::Mat& K); - - /** - * Callback that sets the digital user output - * @param output_id the ID of the user output to set - * @param req request - * @param res response - * @return true on success - */ - bool setUserOutputCB(int output_id, - camera_control_msgs::SetBool::Request &req, - camera_control_msgs::SetBool::Response &res); - - /** - * Callback that activates the digital user output to - be used as autoflash - * @param output_id the ID of the user output to set - * @param req request - * @param res response - * @return true on success - */ - bool setAutoflash(const int output_id, - camera_control_msgs::SetBool::Request &req, - camera_control_msgs::SetBool::Response &res); - - - /** - * Waits till the pylon_camera_ isReady() observing a given timeout - * @return true when the camera's state toggles to 'isReady()' - */ - bool waitForCamera(const ros::Duration& timeout) const; - - ros::NodeHandle nh_; - PylonCameraParameter pylon_camera_parameter_set_; - ros::ServiceServer set_binning_srv_; - ros::ServiceServer set_roi_srv_; - ros::ServiceServer set_exposure_srv_; - ros::ServiceServer set_gain_srv_; - ros::ServiceServer set_gamma_srv_; - ros::ServiceServer set_brightness_srv_; - ros::ServiceServer set_sleeping_srv_; - std::vector set_user_output_srvs_; - - PylonCamera* pylon_camera_; - - image_transport::ImageTransport* it_; - image_transport::CameraPublisher img_raw_pub_; - - ros::Publisher* img_rect_pub_; - image_geometry::PinholeCameraModel* pinhole_model_; - - GrabImagesAS grab_imgs_raw_as_; - GrabImagesAS* grab_imgs_rect_as_; - - sensor_msgs::Image img_raw_msg_; - cv_bridge::CvImage* cv_bridge_img_rect_; - - camera_info_manager::CameraInfoManager* camera_info_manager_; - - std::vector sampling_indices_; - std::array brightness_exp_lut_; - - bool is_sleeping_; - boost::recursive_mutex grab_mutex_; - - /// diagnostics: - diagnostic_updater::Updater diagnostics_updater_; - void diagnostics_timer_callback_(const ros::TimerEvent&); - ros::Timer diagnostics_trigger_; - void create_diagnostics(diagnostic_updater::DiagnosticStatusWrapper &stat); - void create_camera_info_diagnostics(diagnostic_updater::DiagnosticStatusWrapper &stat); -}; - -} // namespace pylon_camera - -#endif // PYLON_CAMERA_PYLON_CAMERA_NODE_H diff --git a/include/pylon_camera/pylon_camera_parameter.h b/include/pylon_camera/pylon_camera_parameter.h deleted file mode 100644 index eb50cf8e..00000000 --- a/include/pylon_camera/pylon_camera_parameter.h +++ /dev/null @@ -1,333 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#ifndef PYLON_CAMERA_PYLON_CAMERA_PARAMETER_H -#define PYLON_CAMERA_PYLON_CAMERA_PARAMETER_H - -#include -#include -#include - -namespace pylon_camera -{ - -enum SHUTTER_MODE -{ - SM_ROLLING = 0, - SM_GLOBAL = 1, - SM_GLOBAL_RESET_RELEASE = 2, - SM_DEFAULT = -1, -}; - -/** - * Parameter class for the PylonCamera - */ -class PylonCameraParameter -{ -public: - PylonCameraParameter(); - - virtual ~PylonCameraParameter(); - - /** - * Read the parameters from the parameter server. - * If invalid parameters can be detected, the interface will reset them - * to the default values. - * @param nh the ros::NodeHandle to use - */ - void readFromRosParameterServer(const ros::NodeHandle& nh); - - /** - * Getter for the device_user_id_ set from ros-parameter server - */ - const std::string& deviceUserID() const; - - /** - * Setter for the device_user_id_ to the class and as well - * the ros-parameter server - */ - void adaptDeviceUserId(const ros::NodeHandle& nh, const std::string& device_user_id); - - /** - * Getter for the string describing the shutter mode - */ - std::string shutterModeString() const; - - /** - * Getter for the camera_frame_ set from ros-parameter server - */ - const std::string& cameraFrame() const; - - /** - * Getter for the frame_rate_ read from ros-parameter server - */ - const double& frameRate() const; - - /** - * Getter for the image_encoding_ read from ros-parameter server - */ - const std::string& imageEncoding() const; - - /** - * Setter for the frame_rate_ initially set from ros-parameter server - * The frame rate needs to be updated with the value the camera supports - */ - void setFrameRate(const ros::NodeHandle& nh, const double& frame_rate); - - /** - * Getter for the camera_info_url set from ros-parameter server - */ - const std::string& cameraInfoURL() const; - - /** - * Setter for the camera_info_url_ if a new CameraInfo-Msgs Object is - * provided via the SetCameraInfo-service from the CameraInfoManager - */ - void setCameraInfoURL(const ros::NodeHandle& nh, - const std::string& camera_info_url); - -public: - /** Binning factor to get downsampled images. It refers here to any camera - * setting which combines rectangular neighborhoods of pixels into larger - * "super-pixels." It reduces the resolution of the output image to - * (width / binning_x) x (height / binning_y). - * The default values binning_x = binning_y = 0 are considered the same - * as binning_x = binning_y = 1 (no subsampling). - */ - size_t binning_x_; - size_t binning_y_; - - /** - * Flags which indicate if the binning factors are provided and hence - * should be set during startup - */ - bool binning_x_given_; - bool binning_y_given_; - - /** - * Factor that describes the image downsampling to speed up the exposure - * search to find the desired brightness. - * The smallest window height is img_rows/downsampling_factor - */ - int downsampling_factor_exp_search_; - - // ####################################################################### - // ###################### Image Intensity Settings ###################### - // ####################################################################### - // The following settings do *NOT* have to be set. Each camera has default - // values which provide an automatic image adjustment - // If one would like to adjust image brightness, it is not - // ####################################################################### - - /** - * The exposure time in microseconds to be set after opening the camera. - */ - double exposure_; - - /** - * Flag which indicates if the exposure time is provided and hence should - * be set during startup - */ - bool exposure_given_; - - /** - * The target gain in percent of the maximal value the camera supports - * For USB-Cameras, the gain is in dB, for GigE-Cameras it is given in so - * called 'device specific units'. - */ - double gain_; - - /** - * Flag which indicates if the gain value is provided and hence should be - * set during startup - */ - bool gain_given_; - - /** - * Gamma correction of pixel intensity. - * Adjusts the brightness of the pixel values output by the camera's sensor - * to account for a non-linearity in the human perception of brightness or - * of the display system (such as CRT). - */ - double gamma_; - - /** - * Flag which indicates if the gamma correction value is provided and - * hence should be set during startup - */ - bool gamma_given_; - - /** - * The average intensity value of the images. It depends on the exposure - * time as well as the gain setting. If 'exposure' is provided, the - * interface will try to reach the desired brightness by only varying the - * gain. (What may often fail, because the range of possible exposure - * values is many times higher than the gain range). - * If 'gain' is provided, the interface will try to reach the desired - * brightness by only varying the exposure time. If gain AND exposure are - * given, it is not possible to reach the brightness, because both are - * assumed to be fix. - */ - int brightness_; - - /** - * Flag which indicates if the average brightness is provided and hence - * should be set during startup - */ - bool brightness_given_; - - /** - * Only relevant, if 'brightness' is set as ros-parameter: - * The brightness_continuous flag controls the auto brightness function. - * If it is set to false, the brightness will only be reached once. - * Hence changing light conditions lead to changing brightness values. - * If it is set to true, the given brightness will be reached continuously, - * trying to adapt to changing light conditions. This is only possible for - * values in the possible auto range of the pylon API which is - * e.g. [50 - 205] for acA2500-14um and acA1920-40gm - */ - bool brightness_continuous_; - /** - * Only relevant, if 'brightness' is given as ros-parameter: - * If the camera should try to reach and / or keep the brightness, hence - * adapting to changing light conditions, at least one of the following - * flags must be set. If both are set, the interface will use the profile - * that tries to keep the gain at minimum to reduce white noise. - * The exposure_auto flag indicates, that the desired brightness will - * be reached by adapting the exposure time. - * The gain_auto flag indicates, that the desired brightness will be - * reached by adapting the gain. - */ - bool exposure_auto_; - bool gain_auto_; - // ####################################################################### - - /** - * The timeout while searching the exposure which is connected to the - * desired brightness. For slow system this has to be increased. - */ - double exposure_search_timeout_; - - /** - * The exposure search can be limited with an upper bound. This is to - * prevent very high exposure times and resulting timeouts. - * A typical value for this upper bound is ~2000000us. - */ - double auto_exp_upper_lim_; - - /** - * The MTU size. Only used for GigE cameras. - * To prevent lost frames the camera has to be configured - * with the MTU size the network card supports. A value greater 3000 - * should be good (1500 for RaspberryPI) - */ - int mtu_size_; - - /** - * The inter-package delay in ticks. Only used for GigE cameras. - * To prevent lost frames it should be greater 0. - * For most of GigE-Cameras, a value of 1000 is reasonable. - * For GigE-Cameras used on a RaspberryPI this value should be set to 11772 - */ - int inter_pkg_delay_; - - /** - Shutter mode - */ - SHUTTER_MODE shutter_mode_; - - /** - * Flag that indicates if the camera has been calibrated and the intrinsic - * calibration matrices are available - */ - bool has_intrinsic_calib_; - - /** - * Flag that indicates if the camera has a flash connected which should be on on exposure - * Only supported for GigE cameras. Default: false - */ - bool auto_flash_; - /** - * Flag that indicates if the camera, when using auto_flash == true, a flash connected on line 2 which should be on on exposure - * Only supported for GigE cameras. Default: true - */ - bool auto_flash_line_2_; - /** - * Flag that indicates if the camera has, when using auto_flash == true, a flash connected on line 3 which should be on on exposure - * Only supported for GigE cameras. Default: true - */ - bool auto_flash_line_3_; - -protected: - /** - * Validates the parameter set found on the ros parameter server. - * If invalid parameters can be detected, the interface will reset them - * to the default values. - * @param nh the ros::NodeHandle to use - */ - void validateParameterSet(const ros::NodeHandle& nh); - - /** - * The tf frame under which the images were published - */ - std::string camera_frame_; - - /** - * The DeviceUserID of the camera. If empty, the first camera found in the - * device list will be used - */ - std::string device_user_id_; - - /** - * The desired publisher frame rate if listening to the topics. - * This parameter can only be set once at startup - * Calling the GrabImages-Action can result in a higher framerate - */ - double frame_rate_; - - /** - * The CameraInfo URL (Uniform Resource Locator) where the optional - * intrinsic camera calibration parameters are stored. This URL string will - * be parsed from the CameraInfoManager: - * http://wiki.ros.org/camera_info_manager - */ - std::string camera_info_url_; - - /** - * The encoding of the pixels -- channel meaning, ordering, size taken - * from the list of strings in include/sensor_msgs/image_encodings.h - * The supported encodings are 'mono8', 'bgr8', 'rgb8', 'bayer_bggr8', - * 'bayer_gbrg8', 'bayer_rggb8' and 'yuv422' - */ - std::string image_encoding_; -}; - -} // namespace pylon_camera - -#endif // PYLON_CAMERA_PYLON_CAMERA_PARAMETER_H diff --git a/package.xml b/package.xml deleted file mode 100644 index d31a1460..00000000 --- a/package.xml +++ /dev/null @@ -1,51 +0,0 @@ - - pylon_camera - - 0.15.0 - - - Proprietary Package for Basler Cameras using the the Pylon API. - - Supports DART, USB3 and GigE cameras. - - Setting Gain, Gamma, Exposure, Binning and Brightness using Services. - - - Marcel Debout - Markus Grimm - Nikolas Engelhard - Markus Grimm - Nikolas Engelhard - - BSD - - http://www.ros.org/wiki/pylon_camera - - catkin - - actionlib - camera_control_msgs - camera_info_manager - cv_bridge - diagnostic_updater - image_geometry - image_transport - pylon - roscpp - sensor_msgs - roslaunch - - roslint - - actionlib - camera_control_msgs - camera_info_manager - diagnostic_updater - cv_bridge - image_geometry - image_transport - pylon - roscpp - roslaunch - sensor_msgs - - - diff --git a/ros_and_workspace_setup.sh b/ros_and_workspace_setup.sh new file mode 100644 index 00000000..e0dab214 --- /dev/null +++ b/ros_and_workspace_setup.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +# +# MAIN VARS TO CHANGE +# +ARENA_INSTALLATION_ROOT="$HOME/ArenaSDK_Linux_x64" +ARENA_ROS_WORDSPACE_TO_SETUP="$HOME/arena_camera_ros/catkin_ws" #change to workspace location +INSTALL_ROS=0 + + + +############################################################ +# Note: +# ArenaSDK does not need to be installed as long as +# $ARENA_INSTALLATION_ROOT point to ArenaSDK binaries +# +############################################################ + +set -x #echo on + +# only run with sudo +if [ `id -u` -ne 0 ]; then + echo "Please run as root" + exit +fi + +# info +CURR_OS="$(lsb_release -sc)" + +if [ $CURR_OS = "xenial" ]; then + ROS_DIS="kinetic" +else + echo "$CURR_OS is not saupported yet" + exit(-1) +fi + +############################################################ +# ROS section +############################################################ + +if [ $INSTALL_ROS -eq 1 ]; then + + # Set up your system to acquire software from packages.ros.org + sudo echo "deb http://packages.ros.org/ros/ubuntu $CURR_OS main" > /etc/apt/sources.list.d/ros-latest.list + # Use apt-key to install the Open Robotics key to your list of trusted keys. + sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 + # Install ROS Desktop. + sudo apt-get update + sudo apt-get install ros-$ROS_DIS-desktop-full + # Setup system dependencies for ROS. + sudo rosdep init + sudo rosdep fix-permissions + rosdep update + + # Setups ROS environment variables + echo "# load ROS env vars" >> ~/.bashrc + echo "source /opt/ros/$ROS_DIS/setup.bash" >> ~/.bashrc + + echo "# load ROS env vars" >> ~/.zshrc + echo "source /opt/ros/$ROS_DIS/setup.zsh" >> ~/.zshrc + + # would not have an effect if script is not run in intractive mode + source ~/.bashrc + + # + # Install ROS package workspace dependencies. This will allow + # you to create and manage your own ROS workspaces, including + # the ROS workspace usedby arena_camera. + # + sudo apt-get install python-rosinstall \ + python-rosinstall-generator \ + python-wstool \ + build-essential + +fi +############################################################ +# Arena SDK section +############################################################ + +# dont not need to be installed as long as +# $ARENA_INSTALLATION_ROOT points to ArenaSDK binaries + + +# Set up your ARENA_ROOT environment variable. This +# environment variable should be the path where you have +# installed Arena SDK. +echo "export ARENA_ROOT=$ARENA_INSTALLATION_ROOT">> ~/.bashrc +echo "export ARENA_ROOT=$ARENA_INSTALLATION_ROOT">> ~/.zshrc +# would not have an effect if script is not run in intractive mode +source ~/.bashrc + +############################################################ +# Workspace section +############################################################ + +# Copy the included image_encoding.h to your ROS include folder after +# baking the old one up (if existed). +# A custom image_encoding.h is included to enable streaming +# support for LUCID’s Helios camera. +sudo cp -f \ + /opt/ros/$ROS_DIS/include/sensor_msgs/image_encodings.h \ + /opt/ros/$ROS_DIS/include/sensor_msgs/image_encodings.h.bak +sudo cp -f \ + $ARENA_ROS_WORDSPACE_TO_SETUP/inc/image_encodings.h \ + /opt/ros/$ROS_DIS/include/sensor_msgs/image_encodings.h \ No newline at end of file diff --git a/rosdep/pylon_sdk.rdmanifest b/rosdep/pylon_sdk.rdmanifest deleted file mode 100644 index a3196d49..00000000 --- a/rosdep/pylon_sdk.rdmanifest +++ /dev/null @@ -1,67 +0,0 @@ -uri: 'https://raw.githubusercontent.com/magazino/pylon_camera/indigo-devel/rosdep/empty.tar' -md5sum: df41600634ca08cb5082877eb64220c9 -install-script: | - #!/bin/bash - if ( [ "${PYLON_ROOT}" != "" ] && [ -d "${PYLON_ROOT}" ] ); then - MAJOR=`${PYLON_ROOT}/bin/pylon-config --version-major` - if [ "$MAJOR" = "5" ]; then - exit 0 - else - echo "Found an existing Pylon installation in PYLON_ROOT but the version is too low" - exit 1 - fi - fi - - arch="$(uname -m)" - if [ "$arch" == "armv7l" ]; then - pkgarch="armhf" - elif [ "$arch" == "aarch64" ]; then - pkgarch="arm64" - elif [ "$arch" == "arm64" ]; then - pkgarch="arm64" - elif [ "$arch" == "x86_64" ]; then - pkgarch="amd64" - else - pkgarch="i386" - fi - - version="5.0.11.10914" - pkg=pylon_${version}-deb0_${pkgarch}.deb - url="https://www.baslerweb.com/media/downloads/software/pylon_software/${pkg}" - - wget --no-check-certificate -O $pkg $url - sudo dpkg -i $pkg - sudo udevadm control --reload-rules || true - -check-presence-script: | - #!/bin/bash - if [ "${PYLON_ROOT}" = "" ]; then - find /opt -type f -name pylon-config - FOUND=$? - if [ "$FOUND" -eq 0 ]; then - for pylon_install in `find /opt -type f -name pylon-config`; do - MAJOR=`${pylon_install} --version-major` - if [ "$MAJOR" = "5" ]; then - echo "Found a pylon Installation with version 5 or greater" - exit 0 - fi - done - fi - echo "Could not find any pylon Installation with version 5 or greater" - exit 1 - else - if [ -d "${PYLON_ROOT}" ]; then - MAJOR=`${PYLON_ROOT}/bin/pylon-config --version-major` - if [ "$MAJOR" = "5" ]; then - exit 0 - else - echo "Found an existing Pylon installation in PYLON_ROOT but the version is too low" - exit 2 - fi - exit 0 - else - echo "PYLON_ROOT is set but folder does not exist." - exit 1 - fi - fi - diff --git a/rosdep/pylon_sdk.yaml b/rosdep/pylon_sdk.yaml deleted file mode 100644 index 9339b3f6..00000000 --- a/rosdep/pylon_sdk.yaml +++ /dev/null @@ -1,4 +0,0 @@ -pylon: - ubuntu: - source: - uri: "https://raw.githubusercontent.com/magazino/pylon_camera/indigo-devel/rosdep/pylon_sdk.rdmanifest" diff --git a/src/pylon_camera/binary_exposure_search.cpp b/src/pylon_camera/binary_exposure_search.cpp deleted file mode 100644 index 0ba10dd0..00000000 --- a/src/pylon_camera/binary_exposure_search.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#include - -namespace pylon_camera -{ - -BinaryExposureSearch::BinaryExposureSearch(const float& target_brightness, - const float& left_lim, - const float& right_lim, - const float& current_exp) - : last_exposure_(current_exp) - , last_unchanged_exposure_counter_(0) - , left_limit_(left_lim) - , right_limit_(right_lim) - , new_exposure_((left_lim + right_lim) / 2.0) - , target_brightness_(target_brightness) - , limit_reached_(false) - , is_initial_setting_(true) -{} - -BinaryExposureSearch::~BinaryExposureSearch() -{} - -bool BinaryExposureSearch::update(const float& current_brightness, - const float& current_exposure) -{ - if ( is_initial_setting_ ) - { - // no need to update the limits, the first time this function will - // be called because limits were correctly set in the constructor - is_initial_setting_ = false; - return true; - } - - if ( current_brightness > target_brightness_ ) - { - right_limit_ = current_exposure; - } - else - { - left_limit_ = current_exposure; - } - - new_exposure_ = (left_limit_ + right_limit_) / 2.0; - - if ( new_exposure_ == current_exposure ) - { - ++last_unchanged_exposure_counter_; - } - else - { - last_exposure_ = current_exposure; - } - - if ( last_unchanged_exposure_counter_ > 2 ) - { - ROS_ERROR_STREAM("BinaryExposureSearch failed, trying three times " - << "to set the same new exposure value"); - return false; - } - else - { - return true; - } -} - -const float& BinaryExposureSearch::newExposure() const -{ - return new_exposure_; -} - -void BinaryExposureSearch::limitReached(bool reached) -{ - limit_reached_ = reached; -} - -bool BinaryExposureSearch::isLimitReached() const -{ - return limit_reached_; -} - -} // namespace pylon_camera diff --git a/src/pylon_camera/encoding_conversions.cpp b/src/pylon_camera/encoding_conversions.cpp deleted file mode 100644 index 3d8b0423..00000000 --- a/src/pylon_camera/encoding_conversions.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#include -#include -#include - -namespace pylon_camera -{ - -namespace encoding_conversions -{ - -bool ros2GenAPI(const std::string& ros_enc, std::string& gen_api_enc) -{ - /* - * http://docs.ros.org/kinetic/api/sensor_msgs/html/image__encodings_8h_source.html - */ - if ( ros_enc == sensor_msgs::image_encodings::MONO8 ) - { - gen_api_enc = "Mono8"; - } - else if ( ros_enc == sensor_msgs::image_encodings::BGR8 ) - { - gen_api_enc = "BGR8"; - } - else if ( ros_enc == sensor_msgs::image_encodings::RGB8 ) - { - gen_api_enc = "RGB8"; - } - else if ( ros_enc == sensor_msgs::image_encodings::BAYER_BGGR8 ) - { - gen_api_enc = "BayerBG8"; - } - else if ( ros_enc == sensor_msgs::image_encodings::BAYER_GBRG8 ) - { - gen_api_enc = "BayerGB8"; - } - else if ( ros_enc == sensor_msgs::image_encodings::BAYER_RGGB8 ) - { - gen_api_enc = "BayerRG8"; - } - /* - else if ( ros_enc == sensor_msgs::image_encodings::YUV422 ) - { - // This is the UYVY version of YUV422 codec http://www.fourcc.org/yuv.php#UYVY - // with an 8-bit depth - gen_api_enc = "YCbCr422_8"; - } - */ - else - { - /* No gen-api pendant existant for following ROS-encodings: - * - sensor_msgs::image_encodings::MONO16 - * - sensor_msgs::image_encodings::BGRA8 - * - sensor_msgs::image_encodings::BGR16 - * - sensor_msgs::image_encodings::BGRA16 - * - sensor_msgs::image_encodings::RGBA8 - * - sensor_msgs::image_encodings::RGB16 - * - sensor_msgs::image_encodings::RGBA16 - * - sensor_msgs::image_encodings::BAYER_BGGR16 - * - sensor_msgs::image_encodings::BAYER_GBRG16 - * - sensor_msgs::image_encodings::BAYER_GRBG16 - * - sensor_msgs::image_encodings::BAYER_GRBG8 - * - sensor_msgs::image_encodings::YUV422 - */ - return false; - } - return true; -} - -bool genAPI2Ros(const std::string& gen_api_enc, std::string& ros_enc) -{ - if ( gen_api_enc == "Mono8" ) - { - ros_enc = sensor_msgs::image_encodings::MONO8; - } - else if ( gen_api_enc == "BGR8" ) - { - ros_enc = sensor_msgs::image_encodings::BGR8; - } - else if ( gen_api_enc == "RGB8" ) - { - ros_enc = sensor_msgs::image_encodings::RGB8; - } - else if ( gen_api_enc == "BayerBG8" ) - { - ros_enc = sensor_msgs::image_encodings::BAYER_BGGR8; - } - else if ( gen_api_enc == "BayerGB8" ) - { - ros_enc = sensor_msgs::image_encodings::BAYER_GBRG8; - } - else if ( gen_api_enc == "BayerRG8" ) - { - ros_enc = sensor_msgs::image_encodings::BAYER_RGGB8; - } - /* - else if ( gen_api_enc == "YCbCr422_8" ) - { - ros_enc = sensor_msgs::image_encodings::YUV422; - } - */ - else - { - /* Unsupported are: - * - Mono10 - * - Mono10p - * - Mono12 - * - Mono12p - * - BayerGR10 - * - BayerGR10p - * - BayerRG10 - * - BayerRG10p - * - BayerGB10 - * - BayerGB10p - * - BayerBG10 - * - BayerBG10p - * - BayerGR12 - * - BayerGR12p - * - BayerRG12 - * - BayerRG12p - * - BayerGB12 - * - BayerGB12p - * - BayerBG12 - * - BayerBG12p - * - YCbCr422_8 - */ - return false; - } - return true; -} -} // namespace encoding_conversions -} // namespace pylon_camera diff --git a/src/pylon_camera/pylon_camera.cpp b/src/pylon_camera/pylon_camera.cpp deleted file mode 100644 index 31b7a645..00000000 --- a/src/pylon_camera/pylon_camera.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#include -#include -#include - -namespace pylon_camera -{ - -enum PYLON_CAM_TYPE -{ - GIGE = 1, - USB = 2, - DART = 3, - UNKNOWN = -1, -}; - -PylonCamera::PylonCamera() - : device_user_id_("") - , img_rows_(0) - , img_cols_(0) - , img_size_byte_(0) - , grab_timeout_(-1.0) - , is_ready_(false) - , is_binary_exposure_search_running_(false) - , max_brightness_tolerance_(2.5) - , binary_exp_search_(nullptr) -{} - -PYLON_CAM_TYPE detectPylonCamType(const Pylon::CDeviceInfo& device_info) -{ - Pylon::String_t device_class; - if ( device_info.IsDeviceClassAvailable() ) - { - device_class = device_info.GetDeviceClass(); - if ( device_class == "BaslerGigE" ) - { - return GIGE; - } - else if ( device_class == "BaslerUsb" ) - { - if ( device_info.IsModelNameAvailable() ) - { - std::string model_name(device_info.GetModelName()); - if ( model_name.compare(0, 3, "acA") == 0 ) - { - return USB; - } - else if ( model_name.compare(0, 3, "daA") == 0 ) - { - return DART; - } - else - { - ROS_ERROR_STREAM("Found 'BaslerUsb'-Camera, but it is neither " - << "a Dart, nor a USB-Camera. Up to now, other cameras are " - << "not supported by this pkg!"); - return UNKNOWN; - } - } - else - { - ROS_ERROR_STREAM("Error while detecting the pylon camera type from " - << "its ModelName: Camera has no ModelName available!"); - return UNKNOWN; - } - } - else - { - ROS_ERROR_STREAM("Detected Camera Type is neither 'BaslerUsb', nor " - << "'BaslerGigE'. Up to now, other cameras not supported by " - << "this pkg!"); - return UNKNOWN; - } - } - else - { - ROS_ERROR_STREAM("Error while detecting the pylon camera type from " - << "its DeviceClass: Camera has no DeviceClass available!"); - return UNKNOWN; - } -} - -PylonCamera* createFromDevice(PYLON_CAM_TYPE cam_type, Pylon::IPylonDevice* device) -{ - switch ( cam_type ) - { - case GIGE: - return new PylonGigECamera(device); - case USB: - return new PylonUSBCamera(device); - case DART: - return new PylonDARTCamera(device); - case UNKNOWN: - default: - return nullptr; - } -} - -PylonCamera* PylonCamera::create(const std::string& device_user_id_to_open) -{ - try - { - // Before using any pylon methods, the pylon runtime must be initialized. - Pylon::PylonInitialize(); - - Pylon::CTlFactory& tl_factory = Pylon::CTlFactory::GetInstance(); - Pylon::DeviceInfoList_t device_list; - - // EnumerateDevices() returns the number of devices found - if ( 0 == tl_factory.EnumerateDevices(device_list) ) - { - Pylon::PylonTerminate(); - ROS_ERROR_ONCE("No camera present"); - return nullptr; - } - else - { - Pylon::DeviceInfoList_t::const_iterator it; - if ( device_user_id_to_open.empty() ) - { - for (it = device_list.begin(); it != device_list.end(); ++it) - { - ROS_INFO_STREAM("Found camera with DeviceUserID " - << it->GetUserDefinedName() << ": " - << it->GetModelName()); - PYLON_CAM_TYPE cam_type = detectPylonCamType(*it); - if (cam_type != UNKNOWN) - { - PylonCamera* new_cam_ptr = createFromDevice(cam_type, - tl_factory.CreateDevice(*it)); - new_cam_ptr->device_user_id_ = it->GetUserDefinedName(); - return new_cam_ptr; - } - } - Pylon::PylonTerminate(); - ROS_ERROR_ONCE("No compatible camera present"); - return nullptr; - } - bool found_desired_device = false; - for ( it = device_list.begin(); it != device_list.end(); ++it ) - { - std::string device_user_id_found(it->GetUserDefinedName()); - if ( (0 == device_user_id_to_open.compare(device_user_id_found)) || - (device_user_id_to_open.length() < device_user_id_found.length() && - (0 == device_user_id_found.compare(device_user_id_found.length() - - device_user_id_to_open.length(), - device_user_id_to_open.length(), - device_user_id_to_open) ) - ) - ) - { - found_desired_device = true; - break; - } - } - if ( found_desired_device ) - { - ROS_INFO_STREAM("Found the desired camera with DeviceUserID " - << device_user_id_to_open << ": " - << it->GetModelName()); - PYLON_CAM_TYPE cam_type = detectPylonCamType(*it); - return createFromDevice(cam_type, - tl_factory.CreateDevice(*it)); - } - else - { - ROS_ERROR_STREAM("Couldn't find the camera that matches the " - << "given DeviceUserID: " << device_user_id_to_open << "! " - << "Either the ID is wrong or the cam is not yet connected"); - return nullptr; - } - } - } - catch ( GenICam::GenericException &e ) - { - ROS_ERROR_STREAM("An exception while opening the desired camera with " - << "DeviceUserID: " << device_user_id_to_open << " occurred: \r\n" - << e.GetDescription()); - return nullptr; - } -} - -const std::string& PylonCamera::deviceUserID() const -{ - return device_user_id_; -} - -const size_t& PylonCamera::imageRows() const -{ - return img_rows_; -} - -const size_t& PylonCamera::imageCols() const -{ - return img_cols_; -} - -const size_t& PylonCamera::imageSize() const -{ - return img_size_byte_; -} - -const float& PylonCamera::maxBrightnessTolerance() const -{ - return max_brightness_tolerance_; -} - -const bool& PylonCamera::isReady() const -{ - return is_ready_; -} - -std::size_t PylonCamera::numUserOutputs() const -{ - return user_output_selector_enums_.size(); -} - -const std::vector& PylonCamera::sequencerExposureTimes() const -{ - return seq_exp_times_; -} - -const bool& PylonCamera::isBinaryExposureSearchRunning() const -{ - return is_binary_exposure_search_running_; -} - -PylonCamera::~PylonCamera() -{ - // Releases all Pylon resources. - Pylon::PylonTerminate(); - if ( binary_exp_search_ ) - { - delete binary_exp_search_; - binary_exp_search_ = nullptr; - } -} - -} // namespace pylon_camera diff --git a/src/pylon_camera/pylon_camera_node.cpp b/src/pylon_camera/pylon_camera_node.cpp deleted file mode 100644 index e694c179..00000000 --- a/src/pylon_camera/pylon_camera_node.cpp +++ /dev/null @@ -1,1649 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#include -#include -#include -#include -#include -#include "boost/multi_array.hpp" - -using diagnostic_msgs::DiagnosticStatus; - -namespace pylon_camera -{ - -using sensor_msgs::CameraInfo; -using sensor_msgs::CameraInfoPtr; - -PylonCameraNode::PylonCameraNode() - : nh_("~"), - pylon_camera_parameter_set_(), - set_binning_srv_(nh_.advertiseService("set_binning", - &PylonCameraNode::setBinningCallback, - this)), - set_roi_srv_(nh_.advertiseService("set_roi", - &PylonCameraNode::setROICallback, - this)), - set_exposure_srv_(nh_.advertiseService("set_exposure", - &PylonCameraNode::setExposureCallback, - this)), - set_gain_srv_(nh_.advertiseService("set_gain", - &PylonCameraNode::setGainCallback, - this)), - set_gamma_srv_(nh_.advertiseService("set_gamma", - &PylonCameraNode::setGammaCallback, - this)), - set_brightness_srv_(nh_.advertiseService("set_brightness", - &PylonCameraNode::setBrightnessCallback, - this)), - set_sleeping_srv_(nh_.advertiseService("set_sleeping", - &PylonCameraNode::setSleepingCallback, - this)), - set_user_output_srvs_(), - pylon_camera_(nullptr), - it_(new image_transport::ImageTransport(nh_)), - img_raw_pub_(it_->advertiseCamera("image_raw", 1)), - img_rect_pub_(nullptr), - grab_imgs_raw_as_( - nh_, - "grab_images_raw", - boost::bind(&PylonCameraNode::grabImagesRawActionExecuteCB, - this, - _1), - false), - grab_imgs_rect_as_(nullptr), - pinhole_model_(nullptr), - cv_bridge_img_rect_(nullptr), - camera_info_manager_(new camera_info_manager::CameraInfoManager(nh_)), - sampling_indices_(), - brightness_exp_lut_(), - is_sleeping_(false) -{ - diagnostics_updater_.setHardwareID("none"); - diagnostics_updater_.add("camera_availability", this, &PylonCameraNode::create_diagnostics); - diagnostics_updater_.add("intrinsic_calibration", this, &PylonCameraNode::create_camera_info_diagnostics); - diagnostics_trigger_ = nh_.createTimer(ros::Duration(2), &PylonCameraNode::diagnostics_timer_callback_, this); - - init(); -} - -void PylonCameraNode::create_diagnostics(diagnostic_updater::DiagnosticStatusWrapper &stat) -{ - if (pylon_camera_parameter_set_.deviceUserID().empty()) - { - return; - } - - if (pylon_camera_) - { - diagnostics_updater_.setHardwareID( pylon_camera_->deviceUserID()); - stat.summary(diagnostic_msgs::DiagnosticStatus::OK, "Device is connected"); - } - else - { - diagnostics_updater_.setHardwareID(pylon_camera_parameter_set_.deviceUserID()); - stat.summary(diagnostic_msgs::DiagnosticStatus::ERROR, "No camera connected"); - } -} - -void PylonCameraNode::create_camera_info_diagnostics(diagnostic_updater::DiagnosticStatusWrapper &stat) -{ - if (camera_info_manager_->isCalibrated()) - { - stat.summaryf(DiagnosticStatus::OK, "Intrinsic calibration found"); - }else{ - stat.summaryf(DiagnosticStatus::ERROR, "No intrinsic calibration found"); - } -} - -void PylonCameraNode::diagnostics_timer_callback_(const ros::TimerEvent&) -{ - diagnostics_updater_.update(); -} - -void PylonCameraNode::init() -{ - // reading all necessary parameter to open the desired camera from the - // ros-parameter-server. In case that invalid parameter values can be - // detected, the interface will reset them to the default values. - // These parameters furthermore contain the intrinsic calibration matrices, - // in case they are provided - pylon_camera_parameter_set_.readFromRosParameterServer(nh_); - - // creating the target PylonCamera-Object with the specified - // device_user_id, registering the Software-Trigger-Mode, starting the - // communication with the device and enabling the desired startup-settings - if ( !initAndRegister() ) - { - ros::shutdown(); - return; - } - - // starting the grabbing procedure with the desired image-settings - if ( !startGrabbing() ) - { - ros::shutdown(); - return; - } -} - -bool PylonCameraNode::initAndRegister() -{ - pylon_camera_ = PylonCamera::create( - pylon_camera_parameter_set_.deviceUserID()); - - if ( pylon_camera_ == nullptr ) - { - // wait and retry until a camera is present - ros::Time end = ros::Time::now() + ros::Duration(15.0); - ros::Rate r(0.5); - while ( ros::ok() && pylon_camera_ == nullptr ) - { - pylon_camera_ = PylonCamera::create( - pylon_camera_parameter_set_.deviceUserID()); - if ( ros::Time::now() > end ) - { - ROS_WARN_STREAM("No camera present. Keep waiting ..."); - end = ros::Time::now() + ros::Duration(15.0); - } - r.sleep(); - ros::spinOnce(); - } - } - - if (pylon_camera_ != nullptr && pylon_camera_parameter_set_.deviceUserID().empty()) - { - pylon_camera_parameter_set_.adaptDeviceUserId(nh_, pylon_camera_->deviceUserID()); - } - - if ( !ros::ok() ) - { - return false; - } - - if ( !pylon_camera_->registerCameraConfiguration() ) - { - ROS_ERROR_STREAM("Error while registering the camera configuration to " - << "software-trigger mode!"); - return false; - } - - if ( !pylon_camera_->openCamera() ) - { - ROS_ERROR("Error while trying to open the desired camera!"); - return false; - } - - if ( !pylon_camera_->applyCamSpecificStartupSettings(pylon_camera_parameter_set_) ) - { - ROS_ERROR_STREAM("Error while applying the cam specific startup settings " - << "(e.g. mtu size for GigE, ...) to the camera!"); - return false; - } - - return true; -} - -bool PylonCameraNode::startGrabbing() -{ - if ( !pylon_camera_->startGrabbing(pylon_camera_parameter_set_) ) - { - ROS_ERROR("Error while start grabbing"); - return false; - } - - size_t num_user_outputs = pylon_camera_->numUserOutputs(); - set_user_output_srvs_.resize(2*num_user_outputs); - for ( int i = 0; i < num_user_outputs; ++i ) - { - std::string srv_name = "set_user_output_" + std::to_string(i); - std::string srv_name_af = "activate_autoflash_output_" + std::to_string(i); - set_user_output_srvs_.at(i) = - nh_.advertiseService< camera_control_msgs::SetBool::Request, - camera_control_msgs::SetBool::Response >( - srv_name, - boost::bind(&PylonCameraNode::setUserOutputCB, - this, i ,_1 ,_2)); - set_user_output_srvs_.at(num_user_outputs+i) = - nh_.advertiseService< camera_control_msgs::SetBool::Request, - camera_control_msgs::SetBool::Response >( - srv_name_af, - boost::bind(&PylonCameraNode::setAutoflash, - this, i+2, _1, _2)); // ! using lines 2 and 3 - } - - img_raw_msg_.header.frame_id = pylon_camera_parameter_set_.cameraFrame(); - // Encoding of pixels -- channel meaning, ordering, size - // taken from the list of strings in include/sensor_msgs/image_encodings.h - img_raw_msg_.encoding = pylon_camera_->currentROSEncoding(); - img_raw_msg_.height = pylon_camera_->imageRows(); - img_raw_msg_.width = pylon_camera_->imageCols(); - // step = full row length in bytes, img_size = (step * rows), imagePixelDepth - // already contains the number of channels - img_raw_msg_.step = img_raw_msg_.width * pylon_camera_->imagePixelDepth(); - - if ( !camera_info_manager_->setCameraName(pylon_camera_->deviceUserID()) ) - { - // valid name contains only alphanumeric signs and '_' - ROS_WARN_STREAM("[" << pylon_camera_->deviceUserID() - << "] name not valid for camera_info_manager"); - } - - setupSamplingIndices(sampling_indices_, - pylon_camera_->imageRows(), - pylon_camera_->imageCols(), - pylon_camera_parameter_set_.downsampling_factor_exp_search_); - - grab_imgs_raw_as_.start(); - - // Initial setting of the CameraInfo-msg, assuming no calibration given - CameraInfo initial_cam_info; - setupInitialCameraInfo(initial_cam_info); - camera_info_manager_->setCameraInfo(initial_cam_info); - - if ( pylon_camera_parameter_set_.cameraInfoURL().empty() || - !camera_info_manager_->validateURL(pylon_camera_parameter_set_.cameraInfoURL()) ) - { - ROS_INFO_STREAM("CameraInfoURL needed for rectification! ROS-Param: " - << "'" << nh_.getNamespace() << "/camera_info_url' = '" - << pylon_camera_parameter_set_.cameraInfoURL() << "' is invalid!"); - ROS_DEBUG_STREAM("CameraInfoURL should have following style: " - << "'file:///full/path/to/local/file.yaml' or " - << "'file://${ROS_HOME}/camera_info/${NAME}.yaml'"); - ROS_WARN("Will only provide distorted /image_raw images!"); - } - else - { - // override initial camera info if the url is valid - if ( camera_info_manager_->loadCameraInfo( - pylon_camera_parameter_set_.cameraInfoURL()) ) - { - setupRectification(); - // set the correct tf frame_id - CameraInfoPtr cam_info(new CameraInfo( - camera_info_manager_->getCameraInfo())); - cam_info->header.frame_id = img_raw_msg_.header.frame_id; - camera_info_manager_->setCameraInfo(*cam_info); - } - else - { - ROS_WARN("Will only provide distorted /image_raw images!"); - } - } - - if ( pylon_camera_parameter_set_.binning_x_given_ ) - { - size_t reached_binning_x; - setBinningX(pylon_camera_parameter_set_.binning_x_, reached_binning_x); - ROS_INFO_STREAM("Setting horizontal binning_x to " - << pylon_camera_parameter_set_.binning_x_); - ROS_WARN_STREAM("The image width of the camera_info-msg will " - << "be adapted, so that the binning_x value in this msg remains 1"); - } - - if ( pylon_camera_parameter_set_.binning_y_given_ ) - { - size_t reached_binning_y; - setBinningY(pylon_camera_parameter_set_.binning_y_, reached_binning_y); - ROS_INFO_STREAM("Setting vertical binning_y to " - << pylon_camera_parameter_set_.binning_y_); - ROS_WARN_STREAM("The image height of the camera_info-msg will " - << "be adapted, so that the binning_y value in this msg remains 1"); - } - - if ( pylon_camera_parameter_set_.exposure_given_ ) - { - float reached_exposure; - setExposure(pylon_camera_parameter_set_.exposure_, reached_exposure); - ROS_INFO_STREAM("Setting exposure to " - << pylon_camera_parameter_set_.exposure_ << ", reached: " - << reached_exposure); - } - - if ( pylon_camera_parameter_set_.gain_given_ ) - { - float reached_gain; - setGain(pylon_camera_parameter_set_.gain_, reached_gain); - ROS_INFO_STREAM("Setting gain to: " - << pylon_camera_parameter_set_.gain_ << ", reached: " - << reached_gain); - } - - if ( pylon_camera_parameter_set_.gamma_given_ ) - { - float reached_gamma; - setGamma(pylon_camera_parameter_set_.gamma_, reached_gamma); - ROS_INFO_STREAM("Setting gamma to " << pylon_camera_parameter_set_.gamma_ - << ", reached: " << reached_gamma); - } - - if ( pylon_camera_parameter_set_.brightness_given_ ) - { - int reached_brightness; - setBrightness(pylon_camera_parameter_set_.brightness_, - reached_brightness, - pylon_camera_parameter_set_.exposure_auto_, - pylon_camera_parameter_set_.gain_auto_); - ROS_INFO_STREAM("Setting brightness to: " - << pylon_camera_parameter_set_.brightness_ << ", reached: " - << reached_brightness); - if ( pylon_camera_parameter_set_.brightness_continuous_ ) - { - if ( pylon_camera_parameter_set_.exposure_auto_ ) - { - pylon_camera_->enableContinuousAutoExposure(); - } - if ( pylon_camera_parameter_set_.gain_auto_ ) - { - pylon_camera_->enableContinuousAutoGain(); - } - } - else - { - pylon_camera_->disableAllRunningAutoBrightessFunctions(); - } - } - - ROS_INFO_STREAM("Startup settings: " - << "encoding = '" << pylon_camera_->currentROSEncoding() << "', " - << "binning = [" << pylon_camera_->currentBinningX() << ", " - << pylon_camera_->currentBinningY() << "], " - << "exposure = " << pylon_camera_->currentExposure() << ", " - << "gain = " << pylon_camera_->currentGain() << ", " - << "gamma = " << pylon_camera_->currentGamma() << ", " - << "shutter mode = " - << pylon_camera_parameter_set_.shutterModeString()); - - // Framerate Settings - if ( pylon_camera_->maxPossibleFramerate() < pylon_camera_parameter_set_.frameRate() ) - { - ROS_INFO("Desired framerate %.2f is higher than max possible. Will limit framerate to: %.2f Hz", - pylon_camera_parameter_set_.frameRate(), - pylon_camera_->maxPossibleFramerate()); - pylon_camera_parameter_set_.setFrameRate( - nh_, - pylon_camera_->maxPossibleFramerate()); - } - else if ( pylon_camera_parameter_set_.frameRate() == -1 ) - { - pylon_camera_parameter_set_.setFrameRate(nh_, - pylon_camera_->maxPossibleFramerate()); - ROS_INFO("Max possible framerate is %.2f Hz", - pylon_camera_->maxPossibleFramerate()); - } - return true; -} - -void PylonCameraNode::setupRectification() -{ - if ( !img_rect_pub_ ) - { - img_rect_pub_ = new ros::Publisher( - nh_.advertise("image_rect", 1)); - } - - if ( !grab_imgs_rect_as_ ) - { - grab_imgs_rect_as_ = - new GrabImagesAS(nh_, - "grab_images_rect", - boost::bind( - &PylonCameraNode::grabImagesRectActionExecuteCB, - this, - _1), - false); - grab_imgs_rect_as_->start(); - } - - if ( !pinhole_model_ ) - { - pinhole_model_ = new image_geometry::PinholeCameraModel(); - } - - pinhole_model_->fromCameraInfo(camera_info_manager_->getCameraInfo()); - if ( !cv_bridge_img_rect_ ) - { - cv_bridge_img_rect_ = new cv_bridge::CvImage(); - } - cv_bridge_img_rect_->header = img_raw_msg_.header; - cv_bridge_img_rect_->encoding = img_raw_msg_.encoding; -} - - -struct CameraPublisherImpl -{ - image_transport::Publisher image_pub_; - ros::Publisher info_pub_; - bool unadvertised_; - //double constructed_; -}; - -class CameraPublisherLocal -{ -public: - struct Impl; - typedef boost::shared_ptr ImplPtr; - typedef boost::weak_ptr ImplWPtr; - - CameraPublisherImpl* impl_; -}; - -uint32_t PylonCameraNode::getNumSubscribersRaw() const -{ - return ((CameraPublisherLocal*)(&img_raw_pub_))->impl_->image_pub_.getNumSubscribers(); -} - -void PylonCameraNode::spin() -{ - if ( camera_info_manager_->isCalibrated() ) - { - ROS_INFO_ONCE("Camera is calibrated"); - } - else - { - ROS_INFO_ONCE("Camera not calibrated"); - } - - if ( pylon_camera_->isCamRemoved() ) - { - ROS_ERROR("Pylon camera has been removed, trying to reset"); - delete pylon_camera_; - pylon_camera_ = nullptr; - for ( ros::ServiceServer& user_output_srv : set_user_output_srvs_ ) - { - user_output_srv.shutdown(); - } - set_user_output_srvs_.clear(); - ros::Duration(0.5).sleep(); // sleep for half a second - init(); - return; - } - // images were published if subscribers are available or if someone calls - // the GrabImages Action - if ( !isSleeping() && (img_raw_pub_.getNumSubscribers() || getNumSubscribersRect() ) ) - { - if ( getNumSubscribersRaw() || getNumSubscribersRect()) - { - if (!grabImage() ) - { - return; - } - } - - if ( img_raw_pub_.getNumSubscribers() > 0 ) - { - // get actual cam_info-object in every frame, because it might have - // changed due to a 'set_camera_info'-service call - sensor_msgs::CameraInfoPtr cam_info( - new sensor_msgs::CameraInfo( - camera_info_manager_->getCameraInfo())); - cam_info->header.stamp = img_raw_msg_.header.stamp; - - // Publish via image_transport - img_raw_pub_.publish(img_raw_msg_, *cam_info); - } - - if ( getNumSubscribersRect() > 0 && camera_info_manager_->isCalibrated() ) - { - cv_bridge_img_rect_->header.stamp = img_raw_msg_.header.stamp; - assert(pinhole_model_->initialized()); - cv_bridge::CvImagePtr cv_img_raw = cv_bridge::toCvCopy( - img_raw_msg_, - img_raw_msg_.encoding); - pinhole_model_->fromCameraInfo(camera_info_manager_->getCameraInfo()); - pinhole_model_->rectifyImage(cv_img_raw->image, cv_bridge_img_rect_->image); - img_rect_pub_->publish(*cv_bridge_img_rect_); - } - } -} - -bool PylonCameraNode::grabImage() -{ - boost::lock_guard lock(grab_mutex_); - if ( !pylon_camera_->grab(img_raw_msg_.data) ) - { - ROS_WARN("Pylon camera returned invalid image! Skipping"); - return false; - } - img_raw_msg_.header.stamp = ros::Time::now(); - return true; -} - -void PylonCameraNode::grabImagesRawActionExecuteCB( - const camera_control_msgs::GrabImagesGoal::ConstPtr& goal) -{ - camera_control_msgs::GrabImagesResult result; - result = grabImagesRaw(goal, &grab_imgs_raw_as_); - grab_imgs_raw_as_.setSucceeded(result); -} - -void PylonCameraNode::grabImagesRectActionExecuteCB( - const camera_control_msgs::GrabImagesGoal::ConstPtr& goal) -{ - camera_control_msgs::GrabImagesResult result; - if ( !camera_info_manager_->isCalibrated() ) - { - result.success = false; - grab_imgs_rect_as_->setSucceeded(result); - return; - } - else - { - result = grabImagesRaw(goal, std::ref(grab_imgs_rect_as_)); - if ( !result.success ) - { - grab_imgs_rect_as_->setSucceeded(result); - return; - } - - for ( std::size_t i = 0; i < result.images.size(); ++i) - { - cv_bridge::CvImagePtr cv_img_raw = cv_bridge::toCvCopy( - result.images[i], - result.images[i].encoding); - pinhole_model_->fromCameraInfo(camera_info_manager_->getCameraInfo()); - cv_bridge::CvImage cv_bridge_img_rect; - cv_bridge_img_rect.header = result.images[i].header; - cv_bridge_img_rect.encoding = result.images[i].encoding; - pinhole_model_->rectifyImage(cv_img_raw->image, cv_bridge_img_rect.image); - cv_bridge_img_rect.toImageMsg(result.images[i]); - } - grab_imgs_rect_as_->setSucceeded(result); - } -} - -camera_control_msgs::GrabImagesResult PylonCameraNode::grabImagesRaw( - const camera_control_msgs::GrabImagesGoal::ConstPtr& goal, - GrabImagesAS* action_server) -{ - camera_control_msgs::GrabImagesResult result; - camera_control_msgs::GrabImagesFeedback feedback; - -#if DEBUG - std::cout << *goal << std::endl; -#endif - - if ( goal->exposure_given && goal->exposure_times.empty() ) - { - ROS_ERROR_STREAM("GrabImagesRaw action server received request and " - << "'exposure_given' is true, but the 'exposure_times' vector is " - << "empty! Not enough information to execute acquisition!"); - result.success = false; - return result; - } - - if ( goal->gain_given && goal->gain_values.empty() ) - { - ROS_ERROR_STREAM("GrabImagesRaw action server received request and " - << "'gain_given' is true, but the 'gain_values' vector is " - << "empty! Not enough information to execute acquisition!"); - result.success = false; - return result; - } - - if ( goal->brightness_given && goal->brightness_values.empty() ) - { - ROS_ERROR_STREAM("GrabImagesRaw action server received request and " - << "'brightness_given' is true, but the 'brightness_values' vector" - << " is empty! Not enough information to execute acquisition!"); - result.success = false; - return result; - } - - if ( goal->gamma_given && goal->gamma_values.empty() ) - { - ROS_ERROR_STREAM("GrabImagesRaw action server received request and " - << "'gamma_given' is true, but the 'gamma_values' vector is " - << "empty! Not enough information to execute acquisition!"); - result.success = false; - return result; - } - - std::vector candidates; - candidates.resize(4); // gain, exposure, gamma, brightness - candidates.at(0) = goal->gain_given ? goal->gain_values.size() : 0; - candidates.at(1) = goal->exposure_given ? goal->exposure_times.size() : 0; - candidates.at(2) = goal->brightness_given ? goal->brightness_values.size() : 0; - candidates.at(3) = goal->gamma_given ? goal->gamma_values.size() : 0; - - size_t n_images = *std::max_element(candidates.begin(), candidates.end()); - - if ( goal->exposure_given && goal->exposure_times.size() != n_images ) - { - ROS_ERROR_STREAM("Size of requested exposure times does not match to " - << "the size of the requested vaules of brightness, gain or " - << "gamma! Can't grab!"); - result.success = false; - return result; - } - - if ( goal->gain_given && goal->gain_values.size() != n_images ) - { - ROS_ERROR_STREAM("Size of requested gain values does not match to " - << "the size of the requested exposure times or the vaules of " - << "brightness or gamma! Can't grab!"); - result.success = false; - return result; - } - - if ( goal->gamma_given && goal->gamma_values.size() != n_images ) - { - ROS_ERROR_STREAM("Size of requested gamma values does not match to " - << "the size of the requested exposure times or the vaules of " - << "brightness or gain! Can't grab!"); - result.success = false; - return result; - } - - if ( goal->brightness_given && goal->brightness_values.size() != n_images ) - { - ROS_ERROR_STREAM("Size of requested brightness values does not match to " - << "the size of the requested exposure times or the vaules of gain or " - << "gamma! Can't grab!"); - result.success = false; - return result; - } - - if ( goal->brightness_given && !( goal->exposure_auto || goal->gain_auto ) ) - { - ROS_ERROR_STREAM("Error while executing the GrabImagesRawAction: A " - << "target brightness is provided but Exposure time AND gain are " - << "declared as fix, so its impossible to reach the brightness"); - result.success = false; - return result; - } - - result.images.resize(n_images); - result.reached_exposure_times.resize(n_images); - result.reached_gain_values.resize(n_images); - result.reached_gamma_values.resize(n_images); - result.reached_brightness_values.resize(n_images); - - result.success = true; - - boost::lock_guard lock(grab_mutex_); - - float previous_exp, previous_gain, previous_gamma; - if ( goal->exposure_given ) - { - previous_exp = pylon_camera_->currentExposure(); - } - if ( goal->gain_given ) - { - previous_gain = pylon_camera_->currentGain(); - } - if ( goal->gamma_given ) - { - previous_gamma = pylon_camera_->currentGamma(); - } - if ( goal->brightness_given ) - { - previous_gain = pylon_camera_->currentGain(); - previous_exp = pylon_camera_->currentExposure(); - } - - for ( std::size_t i = 0; i < n_images; ++i ) - { - if ( goal->exposure_given ) - { - result.success = setExposure(goal->exposure_times[i], - result.reached_exposure_times[i]); - } - if ( goal->gain_given ) - { - result.success = setGain(goal->gain_values[i], - result.reached_gain_values[i]); - } - if ( goal->gamma_given ) - { - result.success = setGamma(goal->gamma_values[i], - result.reached_gamma_values[i]); - } - if ( goal->brightness_given ) - { - int reached_brightness; - result.success = setBrightness(goal->brightness_values[i], - reached_brightness, - goal->exposure_auto, - goal->gain_auto); - result.reached_brightness_values[i] = static_cast( - reached_brightness); - result.reached_exposure_times[i] = pylon_camera_->currentExposure(); - result.reached_gain_values[i] = pylon_camera_->currentGain(); - } - if ( !result.success ) - { - ROS_ERROR_STREAM("Error while setting one of the desired image " - << "properties in the GrabImagesRawActionCB. Aborting!"); - break; - } - - sensor_msgs::Image& img = result.images[i]; - img.encoding = pylon_camera_->currentROSEncoding(); - img.height = pylon_camera_->imageRows(); - img.width = pylon_camera_->imageCols(); - // step = full row length in bytes, img_size = (step * rows), imagePixelDepth - // already contains the number of channels - img.step = img.width * pylon_camera_->imagePixelDepth(); - - if ( !pylon_camera_->grab(img.data) ) - { - result.success = false; - break; - } - - img.header.stamp = ros::Time::now(); - img.header.frame_id = cameraFrame(); - feedback.curr_nr_images_taken = i+1; - - if ( action_server != nullptr ) - { - action_server->publishFeedback(feedback); - } - } - if ( camera_info_manager_ ) - { - sensor_msgs::CameraInfoPtr cam_info( - new sensor_msgs::CameraInfo( - camera_info_manager_->getCameraInfo())); - result.cam_info = *cam_info; - } - - // restore previous settings: - float reached_val; - if ( goal->exposure_given ) - { - setExposure(previous_exp, reached_val); - } - if ( goal->gain_given ) - { - setGain(previous_gain, reached_val); - } - if ( goal->gamma_given ) - { - setGamma(previous_gamma, reached_val); - } - if ( goal->brightness_given ) - { - setGain(previous_gain, reached_val); - setExposure(previous_exp, reached_val); - } - return result; -} - -bool PylonCameraNode::setUserOutputCB(const int output_id, - camera_control_msgs::SetBool::Request &req, - camera_control_msgs::SetBool::Response &res) -{ - res.success = pylon_camera_->setUserOutput(output_id, req.data); - return true; -} - -bool PylonCameraNode::setAutoflash(const int output_id, - camera_control_msgs::SetBool::Request &req, - camera_control_msgs::SetBool::Response &res) -{ - ROS_INFO("AUtoFlashCB: %i -> %i", output_id, req.data); - std::map auto_flashs; - auto_flashs[output_id] = req.data; - pylon_camera_->setAutoflash(auto_flashs); - res.success = true; - return true; -} - -const double& PylonCameraNode::frameRate() const -{ - return pylon_camera_parameter_set_.frameRate(); -} - -const std::string& PylonCameraNode::cameraFrame() const -{ - return pylon_camera_parameter_set_.cameraFrame(); -} - -uint32_t PylonCameraNode::getNumSubscribersRect() const -{ - return camera_info_manager_->isCalibrated() ? img_rect_pub_->getNumSubscribers() : 0; -} - -uint32_t PylonCameraNode::getNumSubscribers() const -{ - return img_raw_pub_.getNumSubscribers() + img_rect_pub_->getNumSubscribers(); -} - -void PylonCameraNode::setupInitialCameraInfo(sensor_msgs::CameraInfo& cam_info_msg) -{ - std_msgs::Header header; - header.frame_id = pylon_camera_parameter_set_.cameraFrame(); - header.stamp = ros::Time::now(); - - // http://www.ros.org/reps/rep-0104.html - // If the camera is uncalibrated, the matrices D, K, R, P should be left - // zeroed out. In particular, clients may assume that K[0] == 0.0 - // indicates an uncalibrated camera. - cam_info_msg.header = header; - - // The image dimensions with which the camera was calibrated. Normally - // this will be the full camera resolution in pixels. They remain fix, even - // if binning is applied - cam_info_msg.height = pylon_camera_->imageRows(); - cam_info_msg.width = pylon_camera_->imageCols(); - - // The distortion model used. Supported models are listed in - // sensor_msgs/distortion_models.h. For most cameras, "plumb_bob" - a - // simple model of radial and tangential distortion - is sufficient. - // Empty D and distortion_model indicate that the CameraInfo cannot be used - // to rectify points or images, either because the camera is not calibrated - // or because the rectified image was produced using an unsupported - // distortion model, e.g. the proprietary one used by Bumblebee cameras - // [http://www.ros.org/reps/rep-0104.html]. - cam_info_msg.distortion_model = ""; - - // The distortion parameters, size depending on the distortion model. - // For "plumb_bob", the 5 parameters are: (k1, k2, t1, t2, k3) -> float64[] D. - cam_info_msg.D = std::vector(5, 0.); - - // Intrinsic camera matrix for the raw (distorted) images. - // [fx 0 cx] - // K = [ 0 fy cy] --> 3x3 row-major matrix - // [ 0 0 1] - // Projects 3D points in the camera coordinate frame to 2D pixel coordinates - // using the focal lengths (fx, fy) and principal point (cx, cy). - cam_info_msg.K.assign(0.0); - - // Rectification matrix (stereo cameras only) - // A rotation matrix aligning the camera coordinate system to the ideal - // stereo image plane so that epipolar lines in both stereo images are parallel. - cam_info_msg.R.assign(0.0); - - // Projection/camera matrix - // [fx' 0 cx' Tx] - // P = [ 0 fy' cy' Ty] --> # 3x4 row-major matrix - // [ 0 0 1 0] - // By convention, this matrix specifies the intrinsic (camera) matrix of the - // processed (rectified) image. That is, the left 3x3 portion is the normal - // camera intrinsic matrix for the rectified image. It projects 3D points - // in the camera coordinate frame to 2D pixel coordinates using the focal - // lengths (fx', fy') and principal point (cx', cy') - these may differ from - // the values in K. For monocular cameras, Tx = Ty = 0. Normally, monocular - // cameras will also have R = the identity and P[1:3,1:3] = K. - // For a stereo pair, the fourth column [Tx Ty 0]' is related to the - // position of the optical center of the second camera in the first - // camera's frame. We assume Tz = 0 so both cameras are in the same - // stereo image plane. The first camera always has Tx = Ty = 0. - // For the right (second) camera of a horizontal stereo pair, - // Ty = 0 and Tx = -fx' * B, where B is the baseline between the cameras. - // Given a 3D point [X Y Z]', the projection (x, y) of the point onto the - // rectified image is given by: - // [u v w]' = P * [X Y Z 1]' - // x = u / w - // y = v / w - // This holds for both images of a stereo pair. - cam_info_msg.P.assign(0.0); - - // Binning refers here to any camera setting which combines rectangular - // neighborhoods of pixels into larger "super-pixels." It reduces the - // resolution of the output image to (width / binning_x) x (height / binning_y). - // The default values binning_x = binning_y = 0 is considered the same as - // binning_x = binning_y = 1 (no subsampling). - cam_info_msg.binning_x = pylon_camera_->currentBinningX(); - cam_info_msg.binning_y = pylon_camera_->currentBinningY(); - - // Region of interest (subwindow of full camera resolution), given in full - // resolution (unbinned) image coordinates. A particular ROI always denotes - // the same window of pixels on the camera sensor, regardless of binning - // settings. The default setting of roi (all values 0) is considered the same - // as full resolution (roi.width = width, roi.height = height). - cam_info_msg.roi.x_offset = cam_info_msg.roi.y_offset = 0; - cam_info_msg.roi.height = cam_info_msg.roi.width = 0; -} - -/** - * Waits till the pylon_camera_ isReady() observing a given timeout - * @return true when the camera's state toggles to 'isReady()' - */ -bool PylonCameraNode::waitForCamera(const ros::Duration& timeout) const -{ - bool result = false; - ros::Time start_time = ros::Time::now(); - - while ( ros::ok() ) - { - if ( pylon_camera_->isReady() ) - { - result = true; - break; - } - else - { - if ( timeout >= ros::Duration(0) ) - { - if ( ros::Time::now() - start_time >= timeout ) - { - ROS_ERROR_STREAM("Setting brightness failed, because the " - << "interface is not ready. This happens although " - << "waiting for " << timeout.sec << " seconds!"); - return false; - } - } - ros::Duration(0.02).sleep(); - } - } - return result; -} - - -bool PylonCameraNode::setROI(const sensor_msgs::RegionOfInterest target_roi, - sensor_msgs::RegionOfInterest& reached_roi) -{ - boost::lock_guard lock(grab_mutex_); - if ( !pylon_camera_->setROI(target_roi, reached_roi) ) - { - // retry till timeout - ros::Rate r(10.0); - ros::Time timeout(ros::Time::now() + ros::Duration(2.0)); - while ( ros::ok() ) - { - if ( pylon_camera_->setROI(target_roi, reached_roi) ) - { - break; - } - if ( ros::Time::now() > timeout ) - { - ROS_ERROR_STREAM("Error in setROI(): Unable to set target " - << "roi before timeout"); - CameraInfoPtr cam_info(new CameraInfo(camera_info_manager_->getCameraInfo())); - cam_info->roi = pylon_camera_->currentROI(); - camera_info_manager_->setCameraInfo(*cam_info); - img_raw_msg_.width = pylon_camera_->imageCols(); - img_raw_msg_.height = pylon_camera_->imageRows(); - // step = full row length in bytes, img_size = (step * rows), imagePixelDepth - // already contains the number of channels - img_raw_msg_.step = img_raw_msg_.width * pylon_camera_->imagePixelDepth(); - return false; - } - r.sleep(); - } - } - CameraInfoPtr cam_info(new CameraInfo(camera_info_manager_->getCameraInfo())); - cam_info->roi = pylon_camera_->currentROI(); - camera_info_manager_->setCameraInfo(*cam_info); - img_raw_msg_.height = pylon_camera_->imageRows(); - img_raw_msg_.width = pylon_camera_->imageCols(); - // step = full row length in bytes, img_size = (step * rows), imagePixelDepth - // already contains the number of channels - img_raw_msg_.step = img_raw_msg_.width * pylon_camera_->imagePixelDepth(); - return true; -} - - -bool PylonCameraNode::setBinningX(const size_t& target_binning_x, - size_t& reached_binning_x) -{ - boost::lock_guard lock(grab_mutex_); - if ( !pylon_camera_->setBinningX(target_binning_x, reached_binning_x) ) - { - // retry till timeout - ros::Rate r(10.0); - ros::Time timeout(ros::Time::now() + ros::Duration(2.0)); - while ( ros::ok() ) - { - if ( pylon_camera_->setBinningX(target_binning_x, reached_binning_x) ) - { - break; - } - if ( ros::Time::now() > timeout ) - { - ROS_ERROR_STREAM("Error in setBinningX(): Unable to set target " - << "binning_x factor before timeout"); - CameraInfoPtr cam_info(new CameraInfo(camera_info_manager_->getCameraInfo())); - cam_info->binning_x = pylon_camera_->currentBinningX(); - camera_info_manager_->setCameraInfo(*cam_info); - img_raw_msg_.width = pylon_camera_->imageCols(); - // step = full row length in bytes, img_size = (step * rows), imagePixelDepth - // already contains the number of channels - img_raw_msg_.step = img_raw_msg_.width * pylon_camera_->imagePixelDepth(); - return false; - } - r.sleep(); - } - } - CameraInfoPtr cam_info(new CameraInfo(camera_info_manager_->getCameraInfo())); - cam_info->binning_x = pylon_camera_->currentBinningX(); - camera_info_manager_->setCameraInfo(*cam_info); - img_raw_msg_.width = pylon_camera_->imageCols(); - // step = full row length in bytes, img_size = (step * rows), imagePixelDepth - // already contains the number of channels - img_raw_msg_.step = img_raw_msg_.width * pylon_camera_->imagePixelDepth(); - setupSamplingIndices(sampling_indices_, - pylon_camera_->imageRows(), - pylon_camera_->imageCols(), - pylon_camera_parameter_set_.downsampling_factor_exp_search_); - return true; -} - -bool PylonCameraNode::setBinningY(const size_t& target_binning_y, - size_t& reached_binning_y) -{ - boost::lock_guard lock(grab_mutex_); - if ( !pylon_camera_->setBinningY(target_binning_y, reached_binning_y) ) - { - // retry till timeout - ros::Rate r(10.0); - ros::Time timeout(ros::Time::now() + ros::Duration(2.0)); - while ( ros::ok() ) - { - if ( pylon_camera_->setBinningY(target_binning_y, reached_binning_y) ) - { - break; - } - if ( ros::Time::now() > timeout ) - { - ROS_ERROR_STREAM("Error in setBinningY(): Unable to set target " - << "binning_y factor before timeout"); - CameraInfoPtr cam_info(new CameraInfo(camera_info_manager_->getCameraInfo())); - cam_info->binning_y = pylon_camera_->currentBinningY(); - camera_info_manager_->setCameraInfo(*cam_info); - img_raw_msg_.height = pylon_camera_->imageRows(); - // step = full row length in bytes, img_size = (step * rows), imagePixelDepth - // already contains the number of channels - img_raw_msg_.step = img_raw_msg_.width * pylon_camera_->imagePixelDepth(); - return false; - } - r.sleep(); - } - } - CameraInfoPtr cam_info(new CameraInfo(camera_info_manager_->getCameraInfo())); - cam_info->binning_y = pylon_camera_->currentBinningY(); - camera_info_manager_->setCameraInfo(*cam_info); - img_raw_msg_.height = pylon_camera_->imageRows(); - // step = full row length in bytes, img_size = (step * rows), imagePixelDepth - // already contains the number of channels - img_raw_msg_.step = img_raw_msg_.width * pylon_camera_->imagePixelDepth(); - setupSamplingIndices(sampling_indices_, - pylon_camera_->imageRows(), - pylon_camera_->imageCols(), - pylon_camera_parameter_set_.downsampling_factor_exp_search_); - return true; -} - -bool PylonCameraNode::setBinningCallback(camera_control_msgs::SetBinning::Request &req, - camera_control_msgs::SetBinning::Response &res) -{ - size_t reached_binning_x, reached_binning_y; - bool success_x = setBinningX(req.target_binning_x, - reached_binning_x); - bool success_y = setBinningY(req.target_binning_y, - reached_binning_y); - res.reached_binning_x = static_cast(reached_binning_x); - res.reached_binning_y = static_cast(reached_binning_y); - res.success = success_x && success_y; - return true; -} - -bool PylonCameraNode::setROICallback(camera_control_msgs::SetROI::Request &req, - camera_control_msgs::SetROI::Response &res) -{ - res.success = setROI(req.target_roi, res.reached_roi); - return true; -} - - -bool PylonCameraNode::setExposure(const float& target_exposure, - float& reached_exposure) -{ - boost::lock_guard lock(grab_mutex_); - if ( !pylon_camera_->isReady() ) - { - ROS_WARN("Error in setExposure(): pylon_camera_ is not ready!"); - return false; - } - - if ( pylon_camera_->setExposure(target_exposure, reached_exposure) ) - { - // success if the delta is smaller then the exposure step - return true; - } - else // retry till timeout - { - // wait for max 5s till the cam has updated the exposure - ros::Rate r(10.0); - ros::Time timeout(ros::Time::now() + ros::Duration(5.0)); - while ( ros::ok() ) - { - if ( pylon_camera_->setExposure(target_exposure, reached_exposure) ) - { - // success if the delta is smaller then the exposure step - return true; - } - - if ( ros::Time::now() > timeout ) - { - break; - } - r.sleep(); - } - ROS_ERROR_STREAM("Error in setExposure(): Unable to set target" - << " exposure before timeout"); - return false; - } -} - -bool PylonCameraNode::setExposureCallback(camera_control_msgs::SetExposure::Request &req, - camera_control_msgs::SetExposure::Response &res) -{ - res.success = setExposure(req.target_exposure, res.reached_exposure); - return true; -} - -bool PylonCameraNode::setGain(const float& target_gain, float& reached_gain) -{ - boost::lock_guard lock(grab_mutex_); - if ( !pylon_camera_->isReady() ) - { - ROS_WARN("Error in setGain(): pylon_camera_ is not ready!"); - return false; - } - - if ( pylon_camera_->setGain(target_gain, reached_gain) ) - { - return true; - } - else // retry till timeout - { - // wait for max 5s till the cam has updated the exposure - ros::Rate r(10.0); - ros::Time timeout(ros::Time::now() + ros::Duration(5.0)); - while ( ros::ok() ) - { - if ( pylon_camera_->setGain(target_gain, reached_gain) ) - { - return true; - } - - if ( ros::Time::now() > timeout ) - { - break; - } - r.sleep(); - } - ROS_ERROR_STREAM("Error in setGain(): Unable to set target " - << "gain before timeout"); - return false; - } -} - -bool PylonCameraNode::setGainCallback(camera_control_msgs::SetGain::Request &req, - camera_control_msgs::SetGain::Response &res) -{ - res.success = setGain(req.target_gain, res.reached_gain); - return true; -} - -bool PylonCameraNode::setGamma(const float& target_gamma, float& reached_gamma) -{ - boost::lock_guard lock(grab_mutex_); - if ( !pylon_camera_->isReady() ) - { - ROS_WARN("Error in setGamma(): pylon_camera_ is not ready!"); - return false; - } - - if ( pylon_camera_->setGamma(target_gamma, reached_gamma) ) - { - return true; - } - else // retry till timeout - { - // wait for max 5s till the cam has updated the gamma value - ros::Rate r(10.0); - ros::Time timeout(ros::Time::now() + ros::Duration(5.0)); - while ( ros::ok() ) - { - if ( pylon_camera_->setGamma(target_gamma, reached_gamma) ) - { - return true; - } - - if ( ros::Time::now() > timeout ) - { - break; - } - r.sleep(); - } - ROS_ERROR_STREAM("Error in setGamma(): Unable to set target " - << "gamma before timeout"); - return false; - } -} - -bool PylonCameraNode::setGammaCallback(camera_control_msgs::SetGamma::Request &req, - camera_control_msgs::SetGamma::Response &res) -{ - res.success = setGamma(req.target_gamma, res.reached_gamma); - return true; -} - -bool PylonCameraNode::setBrightness(const int& target_brightness, - int& reached_brightness, - const bool& exposure_auto, - const bool& gain_auto) -{ - boost::lock_guard lock(grab_mutex_); - ros::Time begin = ros::Time::now(); // time measurement for the exposure search - - // brightness service can only work, if an image has already been grabbed, - // because it calculates the mean on the current image. The interface is - // ready if the grab-result-pointer of the first acquisition contains - // valid data - if ( !waitForCamera(ros::Duration(3.0)) ) - { - ROS_ERROR("Setting brightness failed: interface not ready, although waiting for 3 sec!"); - return false; - } - - int target_brightness_co = std::min(255, target_brightness); - // smart brightness search initially sets the last rememberd exposure time - if ( brightness_exp_lut_.at(target_brightness_co) != 0.0 ) - { - float reached_exp; - if ( !setExposure(brightness_exp_lut_.at(target_brightness_co), - reached_exp) ) - { - ROS_WARN_STREAM("Tried to speed-up exposure search with initial" - << " guess, but setting the exposure failed!"); - } - else - { - ROS_DEBUG_STREAM("Speed-up exposure search with initial exposure" - << " guess of " << reached_exp); - } - } - - // get actual image -> fills img_raw_msg_.data vector - if ( !grabImage() ) - { - ROS_ERROR("Failed to grab image, can't calculate current brightness!"); - return false; - } - - // calculates current brightness by generating the mean over all pixels - // stored in img_raw_msg_.data vector - float current_brightness = calcCurrentBrightness(); - - ROS_DEBUG_STREAM("New brightness request for target brightness " - << target_brightness_co << ", current brightness = " - << current_brightness); - - if ( std::fabs(current_brightness - static_cast(target_brightness_co)) <= 1.0 ) - { - reached_brightness = static_cast(current_brightness); - ros::Time end = ros::Time::now(); - ROS_DEBUG_STREAM("Brightness reached without exposure search, duration: " - << (end-begin).toSec()); - return true; // target brightness already reached - } - - // initially cancel all running exposure search by deactivating - // ExposureAuto & AutoGain - pylon_camera_->disableAllRunningAutoBrightessFunctions(); - - if ( target_brightness_co <= 50 ) - { - // own binary-exp search: we need to have the upper bound -> PylonAuto - // exposure to a initial start value of 50 provides it - if ( brightness_exp_lut_.at(50) != 0.0 ) - { - float reached_exp; - if ( !setExposure(brightness_exp_lut_.at(50), reached_exp) ) - { - ROS_WARN_STREAM("Tried to speed-up exposure search with initial" - << " guess, but setting the exposure failed!"); - } - else - { - ROS_DEBUG_STREAM("Speed-up exposure search with initial exposure" - << " guess of " << reached_exp); - } - } - } - - if ( !exposure_auto && !gain_auto ) - { - ROS_WARN_STREAM("Neither Auto Exposure Time ('exposure_auto') nor Auto " - << "Gain ('gain_auto') are enabled! Hence gain and exposure time " - << "are assumed to be fix and the target brightness (" - << target_brightness_co << ") can not be reached!"); - return false; - } - - bool is_brightness_reached = false; - size_t fail_safe_ctr = 0; - size_t fail_safe_ctr_limit = 10; - if ( pylon_camera_->typeName() == "DART" ) - { - // DART Cameras may need up to 50 images till the desired brightness - // value can be reached. USB & GigE Cameras can achieve that much faster - fail_safe_ctr_limit = 50; - } - float last_brightness = std::numeric_limits::max(); - - // timeout for the exposure search -> need more time for great exposure values - ros::Time start_time = ros::Time::now(); - ros::Time timeout = start_time; - if ( target_brightness_co < 205) - { - timeout += ros::Duration(pylon_camera_parameter_set_.exposure_search_timeout_); - } - else - { - timeout += ros::Duration(10.0 + pylon_camera_parameter_set_.exposure_search_timeout_); - } - - while ( ros::ok() ) - { - // calling setBrightness in every cycle would not be necessary for the pylon auto - // brightness search. But for the case that the target brightness is out of the - // pylon range which is from [50 - 205] a binary exposure search will be executed - // where we have to update the search parameter in every cycle - if ( !pylon_camera_->setBrightness(target_brightness_co, - current_brightness, - exposure_auto, - gain_auto) ) - { - pylon_camera_->disableAllRunningAutoBrightessFunctions(); - break; - } - - if ( !grabImage() ) - { - return false; - } - - if ( pylon_camera_->isPylonAutoBrightnessFunctionRunning() ) - { - // do nothing if the pylon auto function is running, we need to - // wait till it's finished - /* - ROS_DEBUG_STREAM("PylonAutoBrightnessFunction still running! " - << " Current brightness: " << calcCurrentBrightness() - << ", current exposure: " << pylon_camera_->currentExposure()); - */ - continue; - } - - current_brightness = calcCurrentBrightness(); - is_brightness_reached = fabs(current_brightness - static_cast(target_brightness_co)) - < pylon_camera_->maxBrightnessTolerance(); - - if ( is_brightness_reached ) - { - pylon_camera_->disableAllRunningAutoBrightessFunctions(); - break; - } - - if ( std::fabs(last_brightness - current_brightness) <= 1.0 ) - { - fail_safe_ctr++; - } - else - { - fail_safe_ctr = 0; - } - - last_brightness = current_brightness; - - if ( ( fail_safe_ctr > fail_safe_ctr_limit ) && !is_brightness_reached ) - { - ROS_WARN_STREAM("Seems like the desired brightness (" << target_brightness_co - << ") is not reachable! Stuck at brightness " << current_brightness - << " and exposure " << pylon_camera_->currentExposure() << "us"); - pylon_camera_->disableAllRunningAutoBrightessFunctions(); - reached_brightness = static_cast(current_brightness); - return false; - } - - if ( ros::Time::now() > timeout ) - { - // cancel all running brightness search by deactivating ExposureAuto - pylon_camera_->disableAllRunningAutoBrightessFunctions(); - ROS_WARN_STREAM("Did not reach the target brightness before " - << "timeout of " << (timeout - start_time).sec - << " sec! Stuck at brightness " << current_brightness - << " and exposure " << pylon_camera_->currentExposure() << "us"); - reached_brightness = static_cast(current_brightness); - return false; - } - } - - ROS_DEBUG_STREAM("Finally reached brightness: " << current_brightness); - reached_brightness = static_cast(current_brightness); - - // store reached brightness - exposure tuple for next times search - if ( is_brightness_reached ) - { - if ( brightness_exp_lut_.at(reached_brightness) == 0.0 ) - { - brightness_exp_lut_.at(reached_brightness) = pylon_camera_->currentExposure(); - } - else - { - brightness_exp_lut_.at(reached_brightness) += pylon_camera_->currentExposure(); - brightness_exp_lut_.at(reached_brightness) *= 0.5; - } - if ( brightness_exp_lut_.at(target_brightness_co) == 0.0 ) - { - brightness_exp_lut_.at(target_brightness_co) = pylon_camera_->currentExposure(); - } - else - { - brightness_exp_lut_.at(target_brightness_co) += pylon_camera_->currentExposure(); - brightness_exp_lut_.at(target_brightness_co) *= 0.5; - } - } - ros::Time end = ros::Time::now(); - ROS_DEBUG_STREAM("Brightness search duration: " << (end-begin).toSec()); - return is_brightness_reached; -} - -bool PylonCameraNode::setBrightnessCallback(camera_control_msgs::SetBrightness::Request &req, - camera_control_msgs::SetBrightness::Response &res) -{ - res.success = setBrightness(req.target_brightness, - res.reached_brightness, - req.exposure_auto, - req.gain_auto); - if ( req.brightness_continuous ) - { - if ( req.exposure_auto ) - { - pylon_camera_->enableContinuousAutoExposure(); - } - if ( req.gain_auto ) - { - pylon_camera_->enableContinuousAutoGain(); - } - } - res.reached_exposure_time = pylon_camera_->currentExposure(); - res.reached_gain_value = pylon_camera_->currentGain(); - return true; -} - -void PylonCameraNode::setupSamplingIndices(std::vector& indices, - std::size_t rows, - std::size_t cols, - int downsampling_factor) -{ - indices.clear(); - std::size_t min_window_height = static_cast(rows) / - static_cast(downsampling_factor); - cv::Point2i start_pt(0, 0); - cv::Point2i end_pt(cols, rows); - // add the iamge center point only once - sampling_indices_.push_back(0.5 * rows * cols); - genSamplingIndicesRec(indices, - min_window_height, - start_pt, - end_pt); - std::sort(indices.begin(), indices.end()); - return; -} - -void PylonCameraNode::genSamplingIndicesRec(std::vector& indices, - const std::size_t& min_window_height, - const cv::Point2i& s, // start - const cv::Point2i& e) // end -{ - if ( static_cast(std::abs(e.y - s.y)) <= min_window_height ) - { - return; // abort criteria -> shrinked window has the min_col_size - } - /* - * sampled img: point: idx: - * s 0 0 0 0 0 0 a) [(e.x-s.x)*0.5, (e.y-s.y)*0.5] a.x*a.y*0.5 - * 0 0 0 d 0 0 0 b) [a.x, 1.5*a.y] b.y*img_rows+b.x - * 0 0 0 0 0 0 0 c) [0.5*a.x, a.y] c.y*img_rows+c.x - * 0 c 0 a 0 f 0 d) [a.x, 0.5*a.y] d.y*img_rows+d.x - * 0 0 0 0 0 0 0 f) [1.5*a.x, a.y] f.y*img_rows+f.x - * 0 0 0 b 0 0 0 - * 0 0 0 0 0 0 e - */ - cv::Point2i a, b, c, d, f, delta; - a = s + 0.5 * (e - s); // center point - delta = 0.5 * (e - s); - b = s + cv::Point2i(delta.x, 1.5 * delta.y); - c = s + cv::Point2i(0.5 * delta.x, delta.y); - d = s + cv::Point2i(delta.x, 0.5 * delta.y); - f = s + cv::Point2i(1.5 * delta.x, delta.y); - indices.push_back(b.y * pylon_camera_->imageCols() + b.x); - indices.push_back(c.y * pylon_camera_->imageCols() + c.x); - indices.push_back(d.y * pylon_camera_->imageCols() + d.x); - indices.push_back(f.y * pylon_camera_->imageCols() + f.x); - genSamplingIndicesRec(indices, min_window_height, s, a); - genSamplingIndicesRec(indices, min_window_height, a, e); - genSamplingIndicesRec(indices, min_window_height, cv::Point2i(s.x, a.y), cv::Point2i(a.x, e.y)); - genSamplingIndicesRec(indices, min_window_height, cv::Point2i(a.x, s.y), cv::Point2i(e.x, a.y)); - return; -} - -float PylonCameraNode::calcCurrentBrightness() -{ - boost::lock_guard lock(grab_mutex_); - if ( img_raw_msg_.data.empty() ) - { - return 0.0; - } - float sum = 0.0; - if ( sensor_msgs::image_encodings::isMono(img_raw_msg_.encoding) ) - { - // The mean brightness is calculated using a subset of all pixels - for ( const std::size_t& idx : sampling_indices_ ) - { - sum += img_raw_msg_.data.at(idx); - } - if ( sum > 0.0 ) - { - sum /= static_cast(sampling_indices_.size()); - } - } - else - { - // The mean brightness is calculated using all pixels and all channels - sum = std::accumulate(img_raw_msg_.data.begin(), img_raw_msg_.data.end(), 0); - if ( sum > 0.0 ) - { - sum /= static_cast(img_raw_msg_.data.size()); - } - } - return sum; -} - -bool PylonCameraNode::setSleepingCallback(camera_control_msgs::SetSleeping::Request &req, - camera_control_msgs::SetSleeping::Response &res) -{ - is_sleeping_ = req.set_sleeping; - - if ( is_sleeping_ ) - { - ROS_INFO("Seting Pylon Camera Node to sleep..."); - } - else - { - ROS_INFO("Pylon Camera Node continues grabbing"); - } - - res.success = true; - return true; -} - -bool PylonCameraNode::isSleeping() -{ - return is_sleeping_; -} - -PylonCameraNode::~PylonCameraNode() -{ - if ( pylon_camera_ ) - { - delete pylon_camera_; - pylon_camera_ = nullptr; - } - if ( it_ ) - { - delete it_; - it_ = nullptr; - } - if ( grab_imgs_rect_as_ ) - { - grab_imgs_rect_as_->shutdown(); - delete grab_imgs_rect_as_; - grab_imgs_rect_as_ = nullptr; - } - - if ( img_rect_pub_ ) - { - delete img_rect_pub_; - img_rect_pub_ = nullptr; - } - - if ( cv_bridge_img_rect_ ) - { - delete cv_bridge_img_rect_; - cv_bridge_img_rect_ = nullptr; - } - - if ( pinhole_model_ ) - { - delete pinhole_model_; - pinhole_model_ = nullptr; - } -} - -} // namespace pylon_camera diff --git a/src/pylon_camera/pylon_camera_parameter.cpp b/src/pylon_camera/pylon_camera_parameter.cpp deleted file mode 100644 index 75a372d8..00000000 --- a/src/pylon_camera/pylon_camera_parameter.cpp +++ /dev/null @@ -1,368 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#include -#include - -namespace pylon_camera -{ - -PylonCameraParameter::PylonCameraParameter() : - camera_frame_("pylon_camera"), - device_user_id_(""), - frame_rate_(5.0), - camera_info_url_(""), - image_encoding_(""), - binning_x_(1), - binning_y_(1), - binning_x_given_(false), - binning_y_given_(false), - downsampling_factor_exp_search_(1), - // ########################## - // image intensity settings - // ########################## - exposure_(10000.0), - exposure_given_(false), - gain_(0.5), - gain_given_(false), - gamma_(1.0), - gamma_given_(false), - brightness_(100), - brightness_given_(false), - brightness_continuous_(false), - exposure_auto_(true), - gain_auto_(true), - // ######################### - exposure_search_timeout_(5.), - auto_exp_upper_lim_(0.0), - mtu_size_(3000), - inter_pkg_delay_(1000), - shutter_mode_(SM_DEFAULT), - auto_flash_(false) -{} - -PylonCameraParameter::~PylonCameraParameter() -{} - -void PylonCameraParameter::readFromRosParameterServer(const ros::NodeHandle& nh) -{ - nh.param("camera_frame", camera_frame_, "pylon_camera"); - - nh.param("device_user_id", device_user_id_, ""); - - if ( nh.hasParam("frame_rate") ) - { - nh.getParam("frame_rate", frame_rate_); - } - - nh.param("camera_info_url", camera_info_url_, ""); - if ( nh.hasParam("camera_info_url") ) - { - nh.getParam("camera_info_url", camera_info_url_); - } - - binning_x_given_ = nh.hasParam("binning_x"); - if ( binning_x_given_ ) - { - int binning_x; - nh.getParam("binning_x", binning_x); - std::cout << "binning x is given and has value " << binning_x << std::endl; - if ( binning_x > 32 || binning_x < 0 ) - { - ROS_WARN_STREAM("Desired horizontal binning_x factor not in valid " - << "range! Binning x = " << binning_x << ". Will reset it to " - << "default value (1)"); - binning_x_given_ = false; - } - else - { - binning_x_ = static_cast(binning_x); - } - } - binning_y_given_ = nh.hasParam("binning_y"); - if ( binning_y_given_ ) - { - int binning_y; - nh.getParam("binning_y", binning_y); - std::cout << "binning y is given and has value " << binning_y << std::endl; - if ( binning_y > 32 || binning_y < 0 ) - { - ROS_WARN_STREAM("Desired vertical binning_y factor not in valid " - << "range! Binning y = " << binning_y << ". Will reset it to " - << "default value (1)"); - binning_y_given_ = false; - } - else - { - binning_y_ = static_cast(binning_y); - } - } - nh.param("downsampling_factor_exposure_search", - downsampling_factor_exp_search_, - 20); - - if ( nh.hasParam("image_encoding") ) - { - std::string encoding; - nh.getParam("image_encoding", encoding); - if ( !encoding.empty() && - !sensor_msgs::image_encodings::isMono(encoding) && - !sensor_msgs::image_encodings::isColor(encoding) && - !sensor_msgs::image_encodings::isBayer(encoding) && - encoding != sensor_msgs::image_encodings::YUV422 ) - { - ROS_WARN_STREAM("Desired image encoding parameter: '" << encoding - << "' is not part of the 'sensor_msgs/image_encodings.h' list!" - << " Will not set encoding"); - encoding = std::string(""); - } - image_encoding_ = encoding; - } - - // ########################## - // image intensity settings - // ########################## - - // > 0: Exposure time in microseconds - exposure_given_ = nh.hasParam("exposure"); - if ( exposure_given_ ) - { - nh.getParam("exposure", exposure_); - std::cout << "exposure is given and has value " << exposure_ << std::endl; - } - - gain_given_ = nh.hasParam("gain"); - if ( gain_given_ ) - { - nh.getParam("gain", gain_); - std::cout << "gain is given and has value " << gain_ << std::endl; - } - - gamma_given_ = nh.hasParam("gamma"); - if ( gamma_given_ ) - { - nh.getParam("gamma", gamma_); - std::cout << "gamma is given and has value " << gamma_ << std::endl; - } - - brightness_given_ = nh.hasParam("brightness"); - if ( brightness_given_ ) - { - nh.getParam("brightness", brightness_); - std::cout << "brightness is given and has value " << brightness_ - << std::endl; - if ( gain_given_ && exposure_given_ ) - { - ROS_WARN_STREAM("Gain ('gain') and Exposure Time ('exposure') " - << "are given as startup ros-parameter and hence assumed to be " - << "fix! The desired brightness (" << brightness_ << ") can't " - << "be reached! Will ignore the brightness by only " - << "setting gain and exposure . . ."); - brightness_given_ = false; - } - else - { - if ( nh.hasParam("brightness_continuous") ) - { - nh.getParam("brightness_continuous", brightness_continuous_); - std::cout << "brightness is continuous" << std::endl; - } - if ( nh.hasParam("exposure_auto") ) - { - nh.getParam("exposure_auto", exposure_auto_); - std::cout << "exposure is set to auto" << std::endl; - } - if ( nh.hasParam("gain_auto") ) - { - nh.getParam("gain_auto", gain_auto_); - std::cout << "gain is set to auto" << std::endl; - } - } - } - // ########################## - - nh.param("exposure_search_timeout", exposure_search_timeout_, 5.); - nh.param("auto_exposure_upper_limit", auto_exp_upper_lim_, 10000000.); - - if ( nh.hasParam("gige/mtu_size") ) - { - nh.getParam("gige/mtu_size", mtu_size_); - } - - if ( nh.hasParam("gige/inter_pkg_delay") ) - { - nh.getParam("gige/inter_pkg_delay", inter_pkg_delay_); - } - - std::string shutter_param_string; - nh.param("shutter_mode", shutter_param_string, ""); - if ( shutter_param_string == "rolling" ) - { - shutter_mode_ = SM_ROLLING; - } - else if ( shutter_param_string == "global" ) - { - shutter_mode_ = SM_GLOBAL; - } - else if ( shutter_param_string == "global_reset" ) - { - shutter_mode_ = SM_GLOBAL_RESET_RELEASE; - } - else - { - shutter_mode_ = SM_DEFAULT; - } - - nh.param("auto_flash", auto_flash_, false); - nh.param("auto_flash_line_2", auto_flash_line_2_, true); - nh.param("auto_flash_line_3", auto_flash_line_3_, true); - - ROS_WARN("Autoflash: %i, line2: %i , line3: %i ", auto_flash_, auto_flash_line_2_, auto_flash_line_3_); - validateParameterSet(nh); - return; -} - -void PylonCameraParameter::adaptDeviceUserId(const ros::NodeHandle& nh, const std::string& device_user_id) -{ - device_user_id_ = device_user_id; - nh.setParam("device_user_id", device_user_id_); -} - -void PylonCameraParameter::validateParameterSet(const ros::NodeHandle& nh) -{ - if ( !device_user_id_.empty() ) - { - ROS_INFO_STREAM("Trying to open the following camera: " - << device_user_id_.c_str()); - } - else - { - ROS_INFO_STREAM("No Device User ID set -> Will open the camera device " - << "found first"); - } - - if ( frame_rate_ < 0 && frame_rate_ != -1 ) - { - ROS_WARN_STREAM("Unexpected frame rate (" << frame_rate_ << "). Will " - << "reset it to default value which is 5 Hz"); - frame_rate_ = 5.0; - nh.setParam("frame_rate", frame_rate_); - } - - if ( exposure_given_ && ( exposure_ <= 0.0 || exposure_ > 1e7 ) ) - { - ROS_WARN_STREAM("Desired exposure measured in microseconds not in " - << "valid range! Exposure time = " << exposure_ << ". Will " - << "reset it to default value!"); - exposure_given_ = false; - } - - if ( gain_given_ && ( gain_ < 0.0 || gain_ > 1.0 ) ) - { - ROS_WARN_STREAM("Desired gain (in percent) not in allowed range! " - << "Gain = " << gain_ << ". Will reset it to default value!"); - gain_given_ = false; - } - - if ( brightness_given_ && ( brightness_ < 0.0 || brightness_ > 255 ) ) - { - ROS_WARN_STREAM("Desired brightness not in allowed range [0 - 255]! " - << "Brightness = " << brightness_ << ". Will reset it to " - << "default value!"); - brightness_given_ = false; - } - - if ( exposure_search_timeout_ < 5.) - { - ROS_WARN_STREAM("Low timeout for exposure search detected! Exposure " - << "search may fail."); - } - return; -} - -const std::string& PylonCameraParameter::deviceUserID() const -{ - return device_user_id_; -} - -std::string PylonCameraParameter::shutterModeString() const -{ - if ( shutter_mode_ == SM_ROLLING ) - { - return "rolling"; - } - else if ( shutter_mode_ == SM_GLOBAL ) - { - return "global"; - } - else if ( shutter_mode_ == SM_GLOBAL_RESET_RELEASE ) - { - return "global_reset"; - } - else - { - return "default_shutter_mode"; - } -} - -const std::string& PylonCameraParameter::imageEncoding() const -{ - return image_encoding_; -} - -const std::string& PylonCameraParameter::cameraFrame() const -{ - return camera_frame_; -} - -const double& PylonCameraParameter::frameRate() const -{ - return frame_rate_; -} - -void PylonCameraParameter::setFrameRate(const ros::NodeHandle& nh, - const double& frame_rate) -{ - frame_rate_ = frame_rate; - nh.setParam("frame_rate", frame_rate_); -} - -const std::string& PylonCameraParameter::cameraInfoURL() const -{ - return camera_info_url_; -} - -void PylonCameraParameter::setCameraInfoURL(const ros::NodeHandle& nh, - const std::string& camera_info_url) -{ - camera_info_url_ = camera_info_url; - nh.setParam("camera_info_url", camera_info_url_); -} - -} // namespace pylon_camera diff --git a/src/pylon_camera/write_device_user_id_to_camera.cpp b/src/pylon_camera/write_device_user_id_to_camera.cpp deleted file mode 100644 index d5e5a036..00000000 --- a/src/pylon_camera/write_device_user_id_to_camera.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/****************************************************************************** - * Software License Agreement (BSD License) - * - * Copyright (C) 2016, Magazino GmbH. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the names of Magazino GmbH nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -/* - This program will open a Basler Pylon Camera and write a desired camera id. -*/ - -#include -#include -#include -#include - -int main(int argc, char* argv[]) -{ - if ( argc < 2 ) - { - std::cerr << "ERROR: No device_user_id set!" << std::endl; - std::cout << "USAGE: write_device_user_id_to_camera DEVICE_USER_ID" << std::endl; - return 1; - } - - // TODO: regular expression, instead of only catching 2 '_' - std::string desired_device_user_id(reinterpret_cast(argv[1])); - if ( desired_device_user_id.empty() ) - { - std::cout << "ERROR:" << std::endl; - std::cout << "Your desired device_user_id is empty!" << std::endl; - return 2; - } - - // Before using any pylon methods, the pylon runtime must be initialized. - Pylon::PylonInitialize(); - - try - { - Pylon::CDeviceInfo di; - - // TODO: Multiple cameras connected? -> Don't use first device found - // TODO: Write IP to Camera? - - // Create an instant camera object with the camera device found first. - Pylon::CInstantCamera camera(Pylon::CTlFactory::GetInstance().CreateFirstDevice(di)); - - camera.Open(); - - while (!camera.IsOpen()) - { - usleep(1000); - } - - GenApi::INodeMap& node_map = camera.GetNodeMap(); - GenApi::CStringPtr current_device_user_id(node_map.GetNode("DeviceUserID")); - current_device_user_id->SetValue(Pylon::String_t(desired_device_user_id.c_str())); - - std::cout << "Successfully wrote " << current_device_user_id->GetValue() << " to the camera " - << camera.GetDeviceInfo().GetModelName() << std::endl; - camera.Close(); - } - catch (GenICam::GenericException &e) - { - // Error handling. - std::cerr << "An exception occurred." - << std::endl - << e.GetDescription() - << std::endl; - return 3; - } - - // Releases all pylon resources. - Pylon::PylonTerminate(); - - return 0; -} diff --git a/wiki_imgs/logos.png b/wiki_imgs/logos.png deleted file mode 100644 index 793ca6bd5c3e7dcd3e6c57c25247bb04d3f9dcb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20948 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 F{{f%1vFrc< diff --git a/wiki_imgs/logos_small.png b/wiki_imgs/logos_small.png deleted file mode 100644 index 9d84606e507188d7e7b757880d06310e4a6801be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21573 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 yAJG3_>hk|cqJKBI{`>!bDOG