[
  {
    "path": "CMakeLists.txt",
    "content": "# SPDX-License-Identifier: BSD-2-Clause\ncmake_minimum_required(VERSION 2.8.3)\nproject(radar_graph_slam)\n\n# Can we use C++17 in indigo?\nadd_definitions(-msse -msse2 -msse3 -msse4 -msse4.1 -msse4.2)\n# add_definitions(\"-Wall -g\")\nset(CMAKE_CXX_FLAGS \"-msse -msse2 -msse3 -msse4 -msse4.1 -msse4.2\")\nset(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} \"${CMAKE_CURRENT_LIST_DIR}/cmake\")\n\n# pcl 1.7 causes a segfault when it is built with debug mode\nset(CMAKE_BUILD_TYPE \"RELEASE\")\n\nfind_package(catkin REQUIRED COMPONENTS\n  roscpp\n  rospy\n  pcl_ros\n  geodesy\n  nmea_msgs\n  sensor_msgs\n  geometry_msgs\n  message_generation\n  interactive_markers\n  ndt_omp\n  fast_gicp\n  image_transport\n  cv_bridge\n  # rosopencv\n)\ncatkin_python_setup()\n\nfind_package(OpenCV REQUIRED)\ninclude_directories(${OpenCV_INCLUDE_DIRS})\nmessage(STATUS \"version: ${OpenCV_VERSION}\")\n\nfind_package(PCL REQUIRED)\ninclude_directories(${PCL_INCLUDE_DIRS})\nlink_directories(${PCL_LIBRARY_DIRS})\nadd_definitions(${PCL_DEFINITIONS})\n\nmessage(STATUS \"PCL_INCLUDE_DIRS:\" ${PCL_INCLUDE_DIRS})\nmessage(STATUS \"PCL_LIBRARY_DIRS:\" ${PCL_LIBRARY_DIRS})\nmessage(STATUS \"PCL_DEFINITIONS:\" ${PCL_DEFINITIONS})\n\nfind_package(G2O REQUIRED)\ninclude_directories(SYSTEM ${G2O_INCLUDE_DIR} ${G2O_INCLUDE_DIRS})\nlink_directories(${G2O_LIBRARY_DIRS})\n# link_libraries(${G2O_LIBRARIES})\n\nfind_package(OpenMP)\nif (OPENMP_FOUND)\n    set (CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}\")\n    set (CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}\")\nendif()\n\nfind_package(GTSAM REQUIRED QUIET)\nlink_directories(${GTSAM_LIBRARY_DIRS})\n\nfind_library(VGICP_CUDA_FOUND NAMES fast_vgicp_cuda)\nmessage(STATUS \"VGICP_CUDA_FOUND:\" ${VGICP_CUDA_FOUND})\nif(VGICP_CUDA_FOUND)\n  add_definitions(-DUSE_VGICP_CUDA)\nendif()\n\n########################\n## message generation ##\n########################\nadd_message_files(FILES\n  FloorCoeffs.msg\n  ScanMatchingStatus.msg\n)\n\nadd_service_files(FILES\n  SaveMap.srv\n  DumpGraph.srv\n)\n\ngenerate_messages(DEPENDENCIES std_msgs geometry_msgs)\n\n###################################\n## catkin specific configuration ##\n###################################\ncatkin_package(\n  INCLUDE_DIRS include\n  LIBRARIES radar_graph_slam_nodelet\n#  CATKIN_DEPENDS pcl_ros roscpp sensor_msgs\n  DEPENDS system_lib GTSAM OpenCV\n)\n\n###########\n## Build ##\n###########\ninclude_directories(include)\ninclude_directories(\n  ${PCL_INCLUDE_DIRS}\n  ${catkin_INCLUDE_DIRS}\n  ${GTSAM_INCLUDE_DIR}\n  \"~/sensor_ws/devel/include\"\n)\n\n# nodelets\nadd_library(preprocessing_nodelet\n  apps/preprocessing_nodelet.cpp\n  src/radar_ego_velocity_estimator.cpp\n  #src/lib/radar_body_velocity_estimator.cpp\n  #src/lib/radar_body_velocity_estimator_ros.cpp\n)\ntarget_link_libraries(preprocessing_nodelet\n  ${catkin_LIBRARIES}\n  ${PCL_LIBRARIES}\n  ${OpenCV_LIBRARIES}\n  ${Boost_LIBRARIES}\n)\nadd_dependencies(preprocessing_nodelet ${PROJECT_NAME}_gencpp)\n\n\nadd_library(scan_matching_odometry_nodelet\n  apps/scan_matching_odometry_nodelet.cpp\n  src/radar_graph_slam/registrations.cpp\n)\ntarget_link_libraries(scan_matching_odometry_nodelet\n  ${catkin_LIBRARIES}\n  ${PCL_LIBRARIES}\n)\nadd_dependencies(scan_matching_odometry_nodelet ${PROJECT_NAME}_gencpp)\n\n\nadd_library(radar_graph_slam_nodelet\n  apps/radar_graph_slam_nodelet.cpp\n  src/radar_graph_slam/graph_slam.cpp\n  src/radar_graph_slam/loop_detector.cpp\n  src/radar_graph_slam/Scancontext.cpp\n  src/radar_graph_slam/keyframe.cpp\n  src/radar_graph_slam/map_cloud_generator.cpp\n  src/radar_graph_slam/registrations.cpp\n  src/radar_graph_slam/information_matrix_calculator.cpp\n  src/g2o/robust_kernel_io.cpp\n)\ntarget_link_libraries(radar_graph_slam_nodelet\n  ${catkin_LIBRARIES}\n  ${OpenCV_LIBS}\n  ${PCL_LIBRARIES}\n  ${G2O_TYPES_DATA}\n  ${G2O_CORE_LIBRARY}\n  ${G2O_STUFF_LIBRARY}\n  ${G2O_SOLVER_PCG}\n  ${G2O_SOLVER_CSPARSE}   # be aware of that CSPARSE is released under LGPL\n  ${G2O_SOLVER_CHOLMOD}   # be aware of that cholmod is released under GPL\n  ${G2O_TYPES_SLAM3D}\n  ${G2O_TYPES_SLAM3D_ADDONS}\n)\nadd_dependencies(radar_graph_slam_nodelet ${PROJECT_NAME}_gencpp)\n\n\n\n# Adjust Groundtruth\nadd_executable(gt_adjust src/gt_adjust.cpp )\ntarget_link_libraries(gt_adjust\n  radar_graph_slam_nodelet\n  ${catkin_LIBRARIES}\n  ${Boost_LIBRARIES}\n  ${G2O_TYPES_DATA}\n  ${G2O_CORE_LIBRARY}\n  ${G2O_STUFF_LIBRARY}\n  ${G2O_SOLVER_PCG}\n  ${G2O_SOLVER_CSPARSE}   # be aware of that CSPARSE is released under LGPL\n  ${G2O_SOLVER_CHOLMOD}   # be aware of that cholmod is released under GPL\n  ${G2O_TYPES_SLAM3D}\n  ${G2O_TYPES_SLAM3D_ADDONS}\n)\n# Calculate UTM->world Transform\nadd_executable(gps_traj_align src/gps_traj_align.cpp )\ntarget_link_libraries(gps_traj_align\n  radar_graph_slam_nodelet\n  ${catkin_LIBRARIES}\n  ${Boost_LIBRARIES}\n  ${G2O_TYPES_DATA}\n  ${G2O_CORE_LIBRARY}\n  ${G2O_STUFF_LIBRARY}\n  ${G2O_SOLVER_PCG}\n  ${G2O_SOLVER_CSPARSE}   # be aware of that CSPARSE is released under LGPL\n  ${G2O_SOLVER_CHOLMOD}   # be aware of that cholmod is released under GPL\n  ${G2O_TYPES_SLAM3D}\n  ${G2O_TYPES_SLAM3D_ADDONS}\n)\n\ncatkin_install_python(\n  PROGRAMS\n    src/${PROJECT_NAME}/bag_player.py\n    src/${PROJECT_NAME}/ford2bag.py\n    src/${PROJECT_NAME}/map2odom_publisher.py\n  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}\n)\n\ninstall(FILES nodelet_plugins.xml\n  DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}\n)\n\ninstall(TARGETS\n  preprocessing_nodelet\n  scan_matching_odometry_nodelet \n  radar_graph_slam_nodelet\n  LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION})\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# 4DRadarSLAM\n## A 4D Imaging Radar SLAM System for Large-scale Environments based on Pose Graph Optimization\n\n[Paper (ResearchGate)](https://www.researchgate.net/publication/371309896_4DRadarSLAM_A_4D_Imaging_Radar_SLAM_System_for_Large-scale_Environments_based_on_Pose_Graph_Optimization), [IEEEXplore](https://ieeexplore.ieee.org/document/10160670), [Video](https://www.youtube.com/watch?v=Qlvs7ywA5TI), [Dataset (NTU4DRadLM)](https://github.com/junzhang2016/NTU4DRadLM)\n\n***4DRadarSLAM*** is an open source ROS package for real-time 6DOF SLAM using a 4D Radar. It is based on 3D Graph SLAM with Adaptive Probability Distribution GICP scan matching-based odometry estimation and Intensity Scan Context loop detection. It also supports several graph constraints, such as GPS. We have tested this package with ***Oculli Eagle*** in outdoor structured (buildings), unstructured (trees and grasses) and semi-structured environments.\n\n4DRadarSLAM can operate in adverse wheather. We did a experiment in which sensors are covered by dense ***Smoke***. The Lidar SLAM (R2LIVE) failed, but our 4DRadarSLAM is not affected by it, thanks to the penetration of millimeter waves to small objects such as smoke and rain.\n\n<p align='center'>\n    <img src=\"./doc/mapping_smoke.gif\" alt=\"drawing\" width=\"800\"/>\n</p>\n\n\n## 1. Dependency\n### 1.1 **Ubuntu** and **ROS**\nUbuntu 64-bit 18.04 or 20.04.\nROS Melodic or Noetic. [ROS Installation](http://wiki.ros.org/ROS/Installation):\n\n### 1.2 ***4DRadarSLAM*** requires the following libraries:\n- Eigen3\n- OpenMP\n- PCL\n- g2o\n### 1.3 The following ROS packages are required:\n- geodesy\n- nmea_msgs\n- pcl_ros\n- Our modified [fast_apdgicp](https://github.com/zhuge2333/fast_apdgicp), in which Adaptive Probability Distribution GICP algorithum module is added. The original is [fast_gicp](https://github.com/SMRT-AIST/fast_gicp)\n- [barometer_bmp388](https://github.com/zhuge2333/barometer_bmp388.git)\n```\n    sudo apt-get install ros-XXX-geodesy ros-XXX-pcl-ros ros-XXX-nmea-msgs ros-XXX-libg2o\n```\n**NOTICE:** remember to replace \"XXX\" on above command as your ROS distributions, for example, if your use ROS-noetic, the command should be:\n```\n    sudo apt-get install ros-noetic-geodesy ros-noetic-pcl-ros ros-noetic-nmea-msgs ros-noetic-libg2o\n```\n\n## 2. System architecture\n***4DRadarSLAM*** consists of three nodelets.\n\n- *preprocessing_nodelet*\n- *scan_matching_odometry_nodelet*\n- *radar_graph_slam_nodelet*\n\nThe input point cloud is first downsampled by ***preprocessing_nodelet***; the radar pointcloud is transformed to Livox LiDAR frame; estimate its ego velocity and remove dynamic objects, and then passed to the next nodelets. While scan_matching_odometry_nodelet estimates the sensor pose by iteratively applying a scan matching between consecutive frames (i.e., odometry estimation). The estimated odometry are sent to ***radar_graph_slam***. To compensate the accumulated error of the scan matching, it performs loop detection and optimizes a pose graph which takes various constraints into account.\n\n<div align=\"center\">\n    <img src=\"doc/fig_flowchart_system.png\" width = 100% >\n</div>\n\n## 3. Parameter tuning guide\nThe mapping quality largely depends on the parameter setting. In particular, scan matching parameters have a big impact on the result. Tune the parameters accoding to the following instructions:\n\n### 3.1 Point cloud registration\n- ***registration_method***\n\nThis parameter allows to change the registration method to be used for odometry estimation and loop detection. Our code gives five options: ICP, NDT_OMP, FAST_GICP, FAST_APDGICP, FAST_VGICP. \n\nFAST_APDGICP is the implementation of our proposed Adaptive Probability Distribution GICP, it utilizes OpenMP for acceleration. Note that FAST_APDGICP requires extra parameters.\nPoint uncertainty parameters:\n- ***dist_var***\n- ***azimuth_var***\n- ***elevation_var***\n\n*dist_var* means the uncertainty of a point’s range measurement at 100m range, *azimuth_var* and *elevation_var* denote the azimuth and elevation angle accuracy (degree)\n\n### 3.2 Loop detection \n- ***accum_distance_thresh***: Minimum distance beteen two edges of the loop\n- ***min_loop_interval_dist***: Minimum distance between a new loop edge and the last one\n- ***max_baro_difference***: Maximum altitude difference beteen two edges' odometry\n- ***max_yaw_difference***: Maximum yaw difference beteen two edges' odometry\n- ***odom_check_trans_thresh***: Translation threshold of Odometry Check\n- ***odom_check_rot_thresh***: Rotation threshold of Odometry Check\n- ***sc_dist_thresh***: Matching score threshold of Scan Context\n\n### 3.3 Other parameters\n  All the configurable parameters are available in the launch file. Many are similar to the project ***hdl_graph_slam***.\n\n## 4. Run the package\nDownload [our recorded rosbag](https://drive.google.com/drive/folders/14jVa_dzmckVMDdfELmY32fJlKrZG1Afv?usp=sharing)  (**More datasets**: [NTU4DRadLM](https://github.com/junzhang2016/NTU4DRadLM)) and, then\n```\nroslaunch radar_graph_slam radar_graph_slam.launch\n```\nYou'll see a point cloud like:\n<div align=\"center\">\n    <img src=\"doc/fig_carpark_map.png\" width = 80% >\n</div>\nYou can choose the dataset to play at end of the launch file.\nIn our paper, we did evaluation on five datasets, mapping results are presented below:\n<div align=\"center\">\n    <img src=\"doc/fig_map_compare.png\" width = 100% >\n</div>\n\n\n\n## 5. Evaluate the results\nIn our paper, we use [rpg_trajectory_evaluation](https://github.com/uzh-rpg/rpg_trajectory_evaluation.git), the performance indices used are RE (relative error) and ATE (absolute trajectory error).\n\n## 6. Collect your own datasets\nYou need a 4D Imaging radar (we use Oculii's Eagle). Also, a barometer (we use BMP388) and GPS/RTK-GPS (we use ZED-F9P) are optional. If you need to compare Lidar SLAM between the algorithum, or use its trajectory as ground truth, calibrating the transform between Radar and Lidar is a precondition.\n\n## 7. Acknowlegement\n1. 4DRadarSLAM is based on [koide3/hdl_graph_slam](https://github.com/koide3/hdl_graph_slam) \n2. [irapkaist/scancontext](https://github.com/irapkaist/scancontext) scan context\n3. [wh200720041/iscloam](https://github.com/wh200720041/iscloam) intensity scan context\n4. [christopherdoer/reve](https://github.com/christopherdoer/reve) radar ego-velocity estimator\n5. [NeBula-Autonomy/LAMP](https://github.com/NeBula-Autonomy/LAMP) odometry check for loop closure validation\n6. [slambook-en](https://github.com/gaoxiang12/slambook-en) and [Dr. Gao Xiang (高翔)](https://github.com/gaoxiang12). His SLAM tutorial and blogs are the starting point of our SLAM journey.\n7. [lvt2calib](https://github.com/Clothooo/lvt2calib) by LIUY Clothooo for sensor calibration.\n## 8. Citation\nIf you find this work is useful for your research, please consider citing:\n```\n@INPROCEEDINGS{ZhangZhuge2023ICRA,\n  author={Zhang, Jun and Zhuge, Huayang and Wu, Zhenyu and Peng, Guohao and Wen, Mingxing and Liu, Yiyao and Wang, Danwei},\n  booktitle={2023 IEEE International Conference on Robotics and Automation (ICRA)}, \n  title={4DRadarSLAM: A 4D Imaging Radar SLAM System for Large-scale Environments based on Pose Graph Optimization}, \n  year={2023},\n  volume={},\n  number={},\n  pages={8333-8340},\n  doi={10.1109/ICRA48891.2023.10160670}}\n```\n\n\n```\n@INPROCEEDINGS{ZhangZhugeLiu2023ITSC,  \nauthor={Jun Zhang∗, Huayang Zhuge∗, Yiyao Liu∗, Guohao Peng, Zhenyu Wu, Haoyuan Zhang, Qiyang Lyu, Heshan Li, Chunyang Zhao, Dogan Kircali, Sanat Mharolkar, Xun Yang, Su Yi, Yuanzhe Wang+ and Danwei Wang},  \nbooktitle={2023 IEEE 26th International Conference on Intelligent Transportation Systems (ITSC)},   \ntitle={NTU4DRadLM: 4D Radar-centric Multi-Modal Dataset for Localization and Mapping},  \nyear={2023},  \nvolume={},  \nnumber={},  \npages={},  \ndoi={}}\n```\n"
  },
  {
    "path": "apps/preprocessing_nodelet.cpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#include <string>\n#include <fstream>\n#include <functional>\n\n#include <ros/ros.h>\n#include <ros/time.h>\n#include <pcl_ros/transforms.h>\n#include <pcl_ros/point_cloud.h>\n#include <tf/transform_listener.h>\n#include <tf/transform_broadcaster.h>\n\n#include <std_msgs/String.h>\n#include <sensor_msgs/Imu.h>\n#include <sensor_msgs/PointCloud2.h>\n#include <sensor_msgs/PointCloud.h>\n#include <geometry_msgs/TwistWithCovarianceStamped.h>\n#include <nav_msgs/Odometry.h>\n\n#include <nodelet/nodelet.h>\n#include <pluginlib/class_list_macros.h>\n\n#include <pcl/point_types.h>\n#include <pcl/point_cloud.h>\n#include <pcl/filters/passthrough.h>\n#include <pcl/filters/voxel_grid.h>\n#include <pcl/filters/approximate_voxel_grid.h>\n#include <pcl/filters/radius_outlier_removal.h>\n#include <pcl/filters/statistical_outlier_removal.h>\n#include <pcl/filters/fast_bilateral.h>\n#include <pcl/filters/filter.h>\n\n#include <opencv2/imgproc/imgproc.hpp>\n\n#include <Eigen/Dense>\n\n#include \"radar_ego_velocity_estimator.h\"\n#include \"rio_utils/radar_point_cloud.h\"\n#include \"utility_radar.h\"\n\nusing namespace std;\n\nnamespace radar_graph_slam {\n\nclass PreprocessingNodelet : public nodelet::Nodelet, public ParamServer {\npublic: \n  // typedef pcl::PointXYZI PointT;\n  typedef pcl::PointXYZI PointT;\n\n  PreprocessingNodelet() {}\n  virtual ~PreprocessingNodelet() {}\n\n  virtual void onInit() {\n    nh = getNodeHandle();\n    private_nh = getPrivateNodeHandle();\n\n    initializeTransformation();\n    initializeParams();\n\n    points_sub = nh.subscribe(pointCloudTopic, 64, &PreprocessingNodelet::cloud_callback, this);\n    imu_sub = nh.subscribe(imuTopic, 1, &PreprocessingNodelet::imu_callback, this);\n    command_sub = nh.subscribe(\"/command\", 10, &PreprocessingNodelet::command_callback, this);\n\n    points_pub = nh.advertise<sensor_msgs::PointCloud2>(\"/filtered_points\", 32);\n    colored_pub = nh.advertise<sensor_msgs::PointCloud2>(\"/colored_points\", 32);\n    imu_pub = nh.advertise<sensor_msgs::Imu>(\"/imu\", 32);\n    gt_pub = nh.advertise<nav_msgs::Odometry>(\"/aftmapped_to_init\", 16);\n  \n    std::string topic_twist = private_nh.param<std::string>(\"topic_twist\", \"/eagle_data/twist\");\n    std::string topic_inlier_pc2 = private_nh.param<std::string>(\"topic_inlier_pc2\", \"/eagle_data/inlier_pc2\");\n    std::string topic_outlier_pc2 = private_nh.param<std::string>(\"topic_outlier_pc2\", \"/eagle_data/outlier_pc2\");\n    pub_twist = nh.advertise<geometry_msgs::TwistWithCovarianceStamped>(topic_twist, 5);\n    pub_inlier_pc2 = nh.advertise<sensor_msgs::PointCloud2>(topic_inlier_pc2, 5);\n    pub_outlier_pc2 = nh.advertise<sensor_msgs::PointCloud2>(topic_outlier_pc2, 5);\n    pc2_raw_pub = nh.advertise<sensor_msgs::PointCloud2>(\"/eagle_data/pc2_raw\",1);\n    enable_dynamic_object_removal = private_nh.param<bool>(\"enable_dynamic_object_removal\", false);\n    power_threshold = private_nh.param<float>(\"power_threshold\", 0);\n  }\n\nprivate:\n  void initializeTransformation(){\n    livox_to_RGB = (cv::Mat_<double>(4,4) << \n    -0.006878330000, -0.999969000000, 0.003857230000, 0.029164500000,  \n    -7.737180000000E-05, -0.003856790000, -0.999993000000, 0.045695200000,\n     0.999976000000, -0.006878580000, -5.084110000000E-05, -0.19018000000,\n    0,  0,  0,  1);\n    RGB_to_livox =livox_to_RGB.inv();\n    Thermal_to_RGB = (cv::Mat_<double>(4,4) <<\n    0.9999526089706319, 0.008963747151337641, -0.003798822163962599, 0.18106962419014,  \n    -0.008945181135788245, 0.9999481006917174, 0.004876439015823288, -0.04546324090016857,\n    0.00384233617405678, -0.004842226763999368, 0.999980894463835, 0.08046453079998771,\n    0,0,0,1);\n    Radar_to_Thermal = (cv::Mat_<double>(4,4) <<\n    0.999665,    0.00925436,  -0.0241851,  -0.0248342,\n    -0.00826999, 0.999146,    0.0404891,   0.0958317,\n    0.0245392,   -0.0402755,  0.998887,    0.0268037,\n    0,  0,  0,  1);\n    Change_Radarframe=(cv::Mat_<double>(4,4) <<\n    0,-1,0,0,\n    0,0,-1,0,\n    1,0,0,0,\n    0,0,0,1);\n    Radar_to_livox=RGB_to_livox*Thermal_to_RGB*Radar_to_Thermal*Change_Radarframe;\n    std::cout << \"Radar_to_livox = \"<< std::endl << \" \"  << Radar_to_livox << std::endl << std::endl;\n  }\n  void initializeParams() {\n    std::string downsample_method = private_nh.param<std::string>(\"downsample_method\", \"VOXELGRID\");\n    double downsample_resolution = private_nh.param<double>(\"downsample_resolution\", 0.1);\n\n    if(downsample_method == \"VOXELGRID\") {\n      std::cout << \"downsample: VOXELGRID \" << downsample_resolution << std::endl;\n      auto voxelgrid = new pcl::VoxelGrid<PointT>();\n      voxelgrid->setLeafSize(downsample_resolution, downsample_resolution, downsample_resolution);\n      downsample_filter.reset(voxelgrid);\n    } else if(downsample_method == \"APPROX_VOXELGRID\") {\n      std::cout << \"downsample: APPROX_VOXELGRID \" << downsample_resolution << std::endl;\n      pcl::ApproximateVoxelGrid<PointT>::Ptr approx_voxelgrid(new pcl::ApproximateVoxelGrid<PointT>());\n      approx_voxelgrid->setLeafSize(downsample_resolution, downsample_resolution, downsample_resolution);\n      downsample_filter = approx_voxelgrid;\n    } else {\n      if(downsample_method != \"NONE\") {\n        std::cerr << \"warning: unknown downsampling type (\" << downsample_method << \")\" << std::endl;\n        std::cerr << \"       : use passthrough filter\" << std::endl;\n      }\n      std::cout << \"downsample: NONE\" << std::endl;\n    }\n\n    std::string outlier_removal_method = private_nh.param<std::string>(\"outlier_removal_method\", \"STATISTICAL\");\n    if(outlier_removal_method == \"STATISTICAL\") {\n      int mean_k = private_nh.param<int>(\"statistical_mean_k\", 20);\n      double stddev_mul_thresh = private_nh.param<double>(\"statistical_stddev\", 1.0);\n      std::cout << \"outlier_removal: STATISTICAL \" << mean_k << \" - \" << stddev_mul_thresh << std::endl;\n\n      pcl::StatisticalOutlierRemoval<PointT>::Ptr sor(new pcl::StatisticalOutlierRemoval<PointT>());\n      sor->setMeanK(mean_k);\n      sor->setStddevMulThresh(stddev_mul_thresh);\n      outlier_removal_filter = sor;\n    } else if(outlier_removal_method == \"RADIUS\") {\n      double radius = private_nh.param<double>(\"radius_radius\", 0.8);\n      int min_neighbors = private_nh.param<int>(\"radius_min_neighbors\", 2);\n      std::cout << \"outlier_removal: RADIUS \" << radius << \" - \" << min_neighbors << std::endl;\n\n      pcl::RadiusOutlierRemoval<PointT>::Ptr rad(new pcl::RadiusOutlierRemoval<PointT>());\n      rad->setRadiusSearch(radius);\n      rad->setMinNeighborsInRadius(min_neighbors);\n      outlier_removal_filter = rad;\n    } \n    // else if (outlier_removal_method == \"BILATERAL\")\n    // {\n    //   double sigma_s = private_nh.param<double>(\"bilateral_sigma_s\", 5.0);\n    //   double sigma_r = private_nh.param<double>(\"bilateral_sigma_r\", 0.03);\n    //   std::cout << \"outlier_removal: BILATERAL \" << sigma_s << \" - \" << sigma_r << std::endl;\n\n    //   pcl::FastBilateralFilter<PointT>::Ptr fbf(new pcl::FastBilateralFilter<PointT>());\n    //   fbf->setSigmaS (sigma_s);\n    //   fbf->setSigmaR (sigma_r);\n    //   outlier_removal_filter = fbf;\n    // }\n     else {\n      std::cout << \"outlier_removal: NONE\" << std::endl;\n    }\n\n    use_distance_filter = private_nh.param<bool>(\"use_distance_filter\", true);\n    distance_near_thresh = private_nh.param<double>(\"distance_near_thresh\", 1.0);\n    distance_far_thresh = private_nh.param<double>(\"distance_far_thresh\", 100.0);\n    z_low_thresh = private_nh.param<double>(\"z_low_thresh\", -5.0);\n    z_high_thresh = private_nh.param<double>(\"z_high_thresh\", 20.0);\n\n\n    std::string file_name = private_nh.param<std::string>(\"gt_file_location\", \"\");\n    publish_tf = private_nh.param<bool>(\"publish_tf\", false);\n\n    ifstream file_in(file_name);\n    if (!file_in.is_open()) {\n        cout << \"Can not open this gt file\" << endl;\n    }\n    else{\n      std::vector<std::string> vectorLines;\n      std::string line;\n      while (getline(file_in, line)) {\n          vectorLines.push_back(line);\n      }\n      \n      for (size_t i = 1; i < vectorLines.size(); i++) {\n          std::string line_ = vectorLines.at(i);\n          double stamp,tx,ty,tz,qx,qy,qz,qw;\n          stringstream data(line_);\n          data >> stamp >> tx >> ty >> tz >> qx >> qy >> qz >> qw;\n          nav_msgs::Odometry odom_msg;\n          odom_msg.header.frame_id = mapFrame;\n          odom_msg.child_frame_id = baselinkFrame;\n          odom_msg.header.stamp = ros::Time().fromSec(stamp);\n          odom_msg.pose.pose.orientation.w = qw;\n          odom_msg.pose.pose.orientation.x = qx;\n          odom_msg.pose.pose.orientation.y = qy;\n          odom_msg.pose.pose.orientation.z = qz;\n          odom_msg.pose.pose.position.x = tx;\n          odom_msg.pose.pose.position.y = ty;\n          odom_msg.pose.pose.position.z = tz;\n          std::lock_guard<std::mutex> lock(odom_queue_mutex);\n          odom_msgs.push_back(odom_msg);\n      }\n    }\n    file_in.close();\n  }\n\n  void imu_callback(const sensor_msgs::ImuConstPtr& imu_msg) {\n    sensor_msgs::Imu imu_data;\n    imu_data.header.stamp = imu_msg->header.stamp;\n    imu_data.header.seq = imu_msg->header.seq;\n    imu_data.header.frame_id = \"imu_frame\";\n    Eigen::Quaterniond q_ahrs(imu_msg->orientation.w,\n                              imu_msg->orientation.x,\n                              imu_msg->orientation.y,\n                              imu_msg->orientation.z);\n    Eigen::Quaterniond q_r = \n        Eigen::AngleAxisd( M_PI, Eigen::Vector3d::UnitZ()) * \n        Eigen::AngleAxisd( M_PI, Eigen::Vector3d::UnitY()) * \n        Eigen::AngleAxisd( 0.00000, Eigen::Vector3d::UnitX());\n    Eigen::Quaterniond q_rr = \n        Eigen::AngleAxisd( 0.00000, Eigen::Vector3d::UnitZ()) * \n        Eigen::AngleAxisd( 0.00000, Eigen::Vector3d::UnitY()) * \n        Eigen::AngleAxisd( M_PI, Eigen::Vector3d::UnitX());\n    Eigen::Quaterniond q_out =  q_r * q_ahrs * q_rr;\n    imu_data.orientation.w = q_out.w();\n    imu_data.orientation.x = q_out.x();\n    imu_data.orientation.y = q_out.y();\n    imu_data.orientation.z = q_out.z();\n    imu_data.angular_velocity.x = imu_msg->angular_velocity.x;\n    imu_data.angular_velocity.y = -imu_msg->angular_velocity.y;\n    imu_data.angular_velocity.z = -imu_msg->angular_velocity.z;\n    imu_data.linear_acceleration.x = imu_msg->linear_acceleration.x;\n    imu_data.linear_acceleration.y = -imu_msg->linear_acceleration.y;\n    imu_data.linear_acceleration.z = -imu_msg->linear_acceleration.z;\n    imu_pub.publish(imu_data);\n    // imu_queue.push_back(imu_msg);\n    double time_now = imu_msg->header.stamp.toSec();\n    bool updated = false;\n    if (odom_msgs.size() != 0) {\n      while (odom_msgs.front().header.stamp.toSec() + 0.001 < time_now) {\n        std::lock_guard<std::mutex> lock(odom_queue_mutex);\n        odom_msgs.pop_front();\n        updated = true;\n        if (odom_msgs.size() == 0)\n          break;\n      }\n    }\n    if (updated == true && odom_msgs.size() > 0){\n      if (publish_tf) {\n        geometry_msgs::TransformStamped tf_msg;\n        tf_msg.child_frame_id = baselinkFrame;\n        tf_msg.header.frame_id = mapFrame;\n        tf_msg.header.stamp = odom_msgs.front().header.stamp;\n        // tf_msg.header.stamp = ros::Time().now();\n        tf_msg.transform.rotation = odom_msgs.front().pose.pose.orientation;\n        tf_msg.transform.translation.x = odom_msgs.front().pose.pose.position.x;\n        tf_msg.transform.translation.y = odom_msgs.front().pose.pose.position.y;\n        tf_msg.transform.translation.z = odom_msgs.front().pose.pose.position.z;\n        tf_broadcaster.sendTransform(tf_msg);\n      }\n      \n      gt_pub.publish(odom_msgs.front());\n    }\n  }\n\n  void cloud_callback(const sensor_msgs::PointCloud::ConstPtr&  eagle_msg) { // const pcl::PointCloud<PointT>& src_cloud_r\n    \n    RadarPointCloudType radarpoint_raw;\n    PointT radarpoint_xyzi;\n    pcl::PointCloud<RadarPointCloudType>::Ptr radarcloud_raw( new pcl::PointCloud<RadarPointCloudType> );\n    pcl::PointCloud<PointT>::Ptr radarcloud_xyzi( new pcl::PointCloud<PointT> );\n\n    radarcloud_xyzi->header.frame_id = baselinkFrame;\n    radarcloud_xyzi->header.seq = eagle_msg->header.seq;\n    radarcloud_xyzi->header.stamp = eagle_msg->header.stamp.toSec() * 1e6;\n    for(int i = 0; i < eagle_msg->points.size(); i++)\n    {\n        // cout << i << \":    \" <<eagle_msg->points[i].x<<endl;\n        if(eagle_msg->channels[2].values[i] > power_threshold) //\"Power\"\n        {\n            if (eagle_msg->points[i].x == NAN || eagle_msg->points[i].y == NAN || eagle_msg->points[i].z == NAN) continue;\n            if (eagle_msg->points[i].x == INFINITY || eagle_msg->points[i].y == INFINITY || eagle_msg->points[i].z == INFINITY) continue;\n            cv::Mat ptMat, dstMat;\n            ptMat = (cv::Mat_<double>(4, 1) << eagle_msg->points[i].x, eagle_msg->points[i].y, eagle_msg->points[i].z, 1);    \n            // Perform matrix multiplication and save as Mat_ for easy element access\n            dstMat= Radar_to_livox * ptMat;\n            radarpoint_raw.x = dstMat.at<double>(0,0);\n            radarpoint_raw.y = dstMat.at<double>(1,0);\n            radarpoint_raw.z = dstMat.at<double>(2,0);\n            radarpoint_raw.intensity = eagle_msg->channels[2].values[i];\n            radarpoint_raw.doppler = eagle_msg->channels[0].values[i];\n            radarpoint_xyzi.x = dstMat.at<double>(0,0);\n            radarpoint_xyzi.y = dstMat.at<double>(1,0);\n            radarpoint_xyzi.z = dstMat.at<double>(2,0);\n            radarpoint_xyzi.intensity = eagle_msg->channels[2].values[i];\n\n            radarcloud_raw->points.push_back(radarpoint_raw);\n            radarcloud_xyzi->points.push_back(radarpoint_xyzi);\n        }\n    }\n\n    //********** Publish PointCloud2 Format Raw Cloud **********\n    sensor_msgs::PointCloud2 pc2_raw_msg;\n    pcl::toROSMsg(*radarcloud_raw, pc2_raw_msg);\n    pc2_raw_msg.header.stamp = eagle_msg->header.stamp;\n    pc2_raw_msg.header.frame_id = baselinkFrame;\n    pc2_raw_pub.publish(pc2_raw_msg);\n\n    //********** Ego Velocity Estimation **********\n    Eigen::Vector3d v_r, sigma_v_r;\n    sensor_msgs::PointCloud2 inlier_radar_msg, outlier_radar_msg;\n    clock_t start_ms = clock();\n    if (estimator.estimate(pc2_raw_msg, v_r, sigma_v_r, inlier_radar_msg, outlier_radar_msg))\n    {\n        clock_t end_ms = clock();\n        double time_used = double(end_ms - start_ms) / CLOCKS_PER_SEC;\n        egovel_time.push_back(time_used);\n        \n        geometry_msgs::TwistWithCovarianceStamped twist;\n        twist.header.stamp         = pc2_raw_msg.header.stamp;\n        twist.twist.twist.linear.x = v_r.x();\n        twist.twist.twist.linear.y = v_r.y();\n        twist.twist.twist.linear.z = v_r.z();\n\n        twist.twist.covariance.at(0)  = std::pow(sigma_v_r.x(), 2);\n        twist.twist.covariance.at(7)  = std::pow(sigma_v_r.y(), 2);\n        twist.twist.covariance.at(14) = std::pow(sigma_v_r.z(), 2);\n\n        pub_twist.publish(twist);\n        pub_inlier_pc2.publish(inlier_radar_msg);\n        pub_outlier_pc2.publish(outlier_radar_msg);\n\n    }\n    else{;}\n\n    pcl::PointCloud<PointT>::Ptr radarcloud_inlier( new pcl::PointCloud<PointT> );\n    pcl::fromROSMsg (inlier_radar_msg, *radarcloud_inlier);\n\n    pcl::PointCloud<PointT>::ConstPtr src_cloud;\n    if (enable_dynamic_object_removal)\n      src_cloud = radarcloud_inlier;\n    else\n      src_cloud = radarcloud_xyzi;\n\n    \n    if(src_cloud->empty()) {\n      return;\n    }\n\n    src_cloud = deskewing(src_cloud);\n\n    // if baselinkFrame is defined, transform the input cloud to the frame\n    if(!baselinkFrame.empty()) {\n      if(!tf_listener.canTransform(baselinkFrame, src_cloud->header.frame_id, ros::Time(0))) {\n        std::cerr << \"failed to find transform between \" << baselinkFrame << \" and \" << src_cloud->header.frame_id << std::endl;\n      }\n\n      tf::StampedTransform transform;\n      tf_listener.waitForTransform(baselinkFrame, src_cloud->header.frame_id, ros::Time(0), ros::Duration(2.0));\n      tf_listener.lookupTransform(baselinkFrame, src_cloud->header.frame_id, ros::Time(0), transform);\n\n      pcl::PointCloud<PointT>::Ptr transformed(new pcl::PointCloud<PointT>());\n      pcl_ros::transformPointCloud(*src_cloud, *transformed, transform);\n      transformed->header.frame_id = baselinkFrame;\n      transformed->header.stamp = src_cloud->header.stamp;\n      src_cloud = transformed;\n    }\n\n    pcl::PointCloud<PointT>::ConstPtr filtered = distance_filter(src_cloud);\n    // filtered = passthrough(filtered);\n    filtered = downsample(filtered);\n    filtered = outlier_removal(filtered);\n\n    // Distance Histogram\n    static size_t num_frame = 0;\n    if (num_frame % 10 == 0) {\n      Eigen::VectorXi num_at_dist = Eigen::VectorXi::Zero(100);\n      for (int i = 0; i < filtered->size(); i++){\n        int dist = floor(filtered->at(i).getVector3fMap().norm());\n        if (dist < 100)\n          num_at_dist(dist) += 1;\n      }\n      num_at_dist_vec.push_back(num_at_dist);\n    }\n\n    points_pub.publish(*filtered);\n    \n  }\n\n\n  pcl::PointCloud<PointT>::ConstPtr passthrough(const pcl::PointCloud<PointT>::ConstPtr& cloud) const {\n    pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>());\n    PointT pt;\n    for(int i = 0; i < cloud->size(); i++){\n      if (cloud->at(i).z < 10 && cloud->at(i).z > -2){\n        pt.x = (*cloud)[i].x;\n        pt.y = (*cloud)[i].y;\n        pt.z = (*cloud)[i].z;\n        pt.intensity = (*cloud)[i].intensity;\n        filtered->points.push_back(pt);\n      }\n    }\n    filtered->header = cloud->header;\n    return filtered;\n  }\n\n  pcl::PointCloud<PointT>::ConstPtr downsample(const pcl::PointCloud<PointT>::ConstPtr& cloud) const {\n    if(!downsample_filter) {\n      // Remove NaN/Inf points\n      pcl::PointCloud<PointT>::Ptr cloudout(new pcl::PointCloud<PointT>());\n      std::vector<int> indices;\n      pcl::removeNaNFromPointCloud(*cloud, *cloudout, indices);\n      \n      return cloudout;\n    }\n\n    pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>());\n    downsample_filter->setInputCloud(cloud);\n    downsample_filter->filter(*filtered);\n    filtered->header = cloud->header;\n\n    return filtered;\n  }\n\n  pcl::PointCloud<PointT>::ConstPtr outlier_removal(const pcl::PointCloud<PointT>::ConstPtr& cloud) const {\n    if(!outlier_removal_filter) {\n      return cloud;\n    }\n\n    pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>());\n    outlier_removal_filter->setInputCloud(cloud);\n    outlier_removal_filter->filter(*filtered);\n    filtered->header = cloud->header;\n\n    return filtered;\n  }\n\n  pcl::PointCloud<PointT>::ConstPtr distance_filter(const pcl::PointCloud<PointT>::ConstPtr& cloud) const {\n    pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>());\n\n    filtered->reserve(cloud->size());\n    std::copy_if(cloud->begin(), cloud->end(), std::back_inserter(filtered->points), [&](const PointT& p) {\n      double d = p.getVector3fMap().norm();\n      double z = p.z;\n      return d > distance_near_thresh && d < distance_far_thresh && z < z_high_thresh && z > z_low_thresh;\n    });\n    // for (size_t i=0; i<cloud->size(); i++){\n    //   const PointT p = cloud->points.at(i);\n    //   double d = p.getVector3fMap().norm();\n    //   double z = p.z;\n    //   if (d > distance_near_thresh && d < distance_far_thresh && z < z_high_thresh && z > z_low_thresh)\n    //     filtered->points.push_back(p);\n    // }\n\n    filtered->width = filtered->size();\n    filtered->height = 1;\n    filtered->is_dense = false;\n\n    filtered->header = cloud->header;\n\n    return filtered;\n  }\n\n  pcl::PointCloud<PointT>::ConstPtr deskewing(const pcl::PointCloud<PointT>::ConstPtr& cloud) {\n    ros::Time stamp = pcl_conversions::fromPCL(cloud->header.stamp);\n    if(imu_queue.empty()) {\n      return cloud;\n    }\n\n    // the color encodes the point number in the point sequence\n    if(colored_pub.getNumSubscribers()) {\n      pcl::PointCloud<pcl::PointXYZRGB>::Ptr colored(new pcl::PointCloud<pcl::PointXYZRGB>());\n      colored->header = cloud->header;\n      colored->is_dense = cloud->is_dense;\n      colored->width = cloud->width;\n      colored->height = cloud->height;\n      colored->resize(cloud->size());\n\n      for(int i = 0; i < cloud->size(); i++) {\n        double t = static_cast<double>(i) / cloud->size();\n        colored->at(i).getVector4fMap() = cloud->at(i).getVector4fMap();\n        colored->at(i).r = 255 * t;\n        colored->at(i).g = 128;\n        colored->at(i).b = 255 * (1 - t);\n      }\n      colored_pub.publish(*colored);\n    }\n\n    sensor_msgs::ImuConstPtr imu_msg = imu_queue.front();\n\n    auto loc = imu_queue.begin();\n    for(; loc != imu_queue.end(); loc++) {\n      imu_msg = (*loc);\n      if((*loc)->header.stamp > stamp) {\n        break;\n      }\n    }\n\n    imu_queue.erase(imu_queue.begin(), loc);\n\n    Eigen::Vector3f ang_v(imu_msg->angular_velocity.x, imu_msg->angular_velocity.y, imu_msg->angular_velocity.z);\n    ang_v *= -1;\n\n    pcl::PointCloud<PointT>::Ptr deskewed(new pcl::PointCloud<PointT>());\n    deskewed->header = cloud->header;\n    deskewed->is_dense = cloud->is_dense;\n    deskewed->width = cloud->width;\n    deskewed->height = cloud->height;\n    deskewed->resize(cloud->size());\n\n    double scan_period = private_nh.param<double>(\"scan_period\", 0.1);\n    for(int i = 0; i < cloud->size(); i++) {\n      const auto& pt = cloud->at(i);\n\n      // TODO: transform IMU data into the LIDAR frame\n      double delta_t = scan_period * static_cast<double>(i) / cloud->size();\n      Eigen::Quaternionf delta_q(1, delta_t / 2.0 * ang_v[0], delta_t / 2.0 * ang_v[1], delta_t / 2.0 * ang_v[2]);\n      Eigen::Vector3f pt_ = delta_q.inverse() * pt.getVector3fMap();\n\n      deskewed->at(i) = cloud->at(i);\n      deskewed->at(i).getVector3fMap() = pt_;\n    }\n\n    return deskewed;\n  }\n\n  bool RadarRaw2PointCloudXYZ(const pcl::PointCloud<RadarPointCloudType>::ConstPtr &raw, pcl::PointCloud<pcl::PointXYZ>::Ptr &cloudxyz)\n  {\n      pcl::PointXYZ point_xyz;\n      for(int i = 0; i < raw->size(); i++)\n      {\n          point_xyz.x = (*raw)[i].x;\n          point_xyz.y = (*raw)[i].y;\n          point_xyz.z = (*raw)[i].z;\n          cloudxyz->points.push_back(point_xyz);\n      }\n      return true;\n  }\n  bool RadarRaw2PointCloudXYZI(const pcl::PointCloud<RadarPointCloudType>::ConstPtr &raw, pcl::PointCloud<pcl::PointXYZI>::Ptr &cloudxyzi)\n  {\n      pcl::PointXYZI radarpoint_xyzi;\n      for(int i = 0; i < raw->size(); i++)\n      {\n          radarpoint_xyzi.x = (*raw)[i].x;\n          radarpoint_xyzi.y = (*raw)[i].y;\n          radarpoint_xyzi.z = (*raw)[i].z;\n          radarpoint_xyzi.intensity = (*raw)[i].intensity;\n          cloudxyzi->points.push_back(radarpoint_xyzi);\n      }\n      return true;\n  }\n\n  void command_callback(const std_msgs::String& str_msg) {\n    if (str_msg.data == \"time\") {\n      std::sort(egovel_time.begin(), egovel_time.end());\n      double median = egovel_time.at(size_t(egovel_time.size() / 2));\n      cout << \"Ego velocity time cost (median): \" << median << endl;\n    }\n    else if (str_msg.data == \"point_distribution\") {\n      Eigen::VectorXi data(100);\n      for (size_t i = 0; i < num_at_dist_vec.size(); i++){ // N\n        Eigen::VectorXi& nad = num_at_dist_vec.at(i);\n        for (int j = 0; j< 100; j++){\n          data(j) += nad(j);\n        }\n      }\n      data /= num_at_dist_vec.size();\n      for (int i=0; i<data.size(); i++){\n        cout << data(i) << \", \";\n      }\n      cout << endl;\n    }\n  }\n\nprivate:\n  ros::NodeHandle nh;\n  ros::NodeHandle private_nh;\n\n  ros::Subscriber imu_sub;\n  std::vector<sensor_msgs::ImuConstPtr> imu_queue;\n  ros::Subscriber points_sub;\n  \n  ros::Publisher points_pub;\n  ros::Publisher colored_pub;\n  ros::Publisher imu_pub;\n  ros::Publisher gt_pub;\n\n  tf::TransformListener tf_listener;\n  tf::TransformBroadcaster tf_broadcaster;\n\n\n  bool use_distance_filter;\n  double distance_near_thresh;\n  double distance_far_thresh;\n  double z_low_thresh;\n  double z_high_thresh;\n\n  pcl::Filter<PointT>::Ptr downsample_filter;\n  pcl::Filter<PointT>::Ptr outlier_removal_filter;\n\n  cv::Mat Radar_to_livox; // Transform Radar point cloud to LiDAR Frame\n  cv::Mat Thermal_to_RGB,Radar_to_Thermal,RGB_to_livox,livox_to_RGB,Change_Radarframe;\n  rio::RadarEgoVelocityEstimator estimator;\n  ros::Publisher pub_twist, pub_inlier_pc2, pub_outlier_pc2, pc2_raw_pub;\n\n  float power_threshold;\n  bool enable_dynamic_object_removal = false;\n\n  std::mutex odom_queue_mutex;\n  std::deque<nav_msgs::Odometry> odom_msgs;\n  bool publish_tf;\n\n  ros::Subscriber command_sub;\n  std::vector<double> egovel_time;\n\n  std::vector<Eigen::VectorXi> num_at_dist_vec;\n};\n\n}  // namespace radar_graph_slam\n\nPLUGINLIB_EXPORT_CLASS(radar_graph_slam::PreprocessingNodelet, nodelet::Nodelet)\n"
  },
  {
    "path": "apps/radar_graph_slam_nodelet.cpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#include <ctime>\n#include <mutex>\n#include <atomic>\n#include <memory>\n#include <iomanip>\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <unordered_map>\n#include <boost/format.hpp>\n#include <boost/thread.hpp>\n#include <boost/filesystem.hpp>\n#include <boost/algorithm/string.hpp>\n#include <Eigen/Dense>\n#include <pcl/io/pcd_io.h>\n#include <pcl_ros/point_cloud.h>\n#include <pcl/registration/icp.h>\n#include <pcl/octree/octree_search.h>\n\n#include <ros/ros.h>\n#include <geodesy/utm.h>\n#include <geodesy/wgs84.h>\n#include <message_filters/subscriber.h>\n#include <message_filters/time_synchronizer.h>\n#include <message_filters/sync_policies/approximate_time.h>\n#include <tf_conversions/tf_eigen.h>\n#include <tf/transform_listener.h>\n#include <tf/transform_broadcaster.h>\n\n#include <std_msgs/Time.h>\n#include <std_msgs/String.h>\n#include <nav_msgs/Odometry.h>\n#include <nmea_msgs/Sentence.h>\n#include <sensor_msgs/Imu.h>\n#include <sensor_msgs/NavSatFix.h>\n#include <sensor_msgs/PointCloud2.h>\n#include <geographic_msgs/GeoPointStamped.h>\n#include <visualization_msgs/MarkerArray.h>\n\n#include <nodelet/nodelet.h>\n#include <pluginlib/class_list_macros.h>\n\n#include <radar_graph_slam/SaveMap.h>\n#include <radar_graph_slam/DumpGraph.h>\n#include <radar_graph_slam/ros_utils.hpp>\n#include <radar_graph_slam/ros_time_hash.hpp>\n#include <radar_graph_slam/FloorCoeffs.h>\n#include <radar_graph_slam/graph_slam.hpp>\n#include <radar_graph_slam/keyframe.hpp>\n#include <radar_graph_slam/keyframe_updater.hpp>\n#include <radar_graph_slam/loop_detector.hpp>\n#include <radar_graph_slam/information_matrix_calculator.hpp>\n#include <radar_graph_slam/map_cloud_generator.hpp>\n#include <radar_graph_slam/nmea_sentence_parser.hpp>\n#include \"radar_graph_slam/polynomial_interpolation.hpp\"\n#include <radar_graph_slam/registrations.hpp>\n\n#include \"scan_context/Scancontext.h\"\n\n#include <g2o/types/slam3d/edge_se3.h>\n#include <g2o/types/slam3d/vertex_se3.h>\n#include <g2o/edge_se3_plane.hpp>\n#include <g2o/edge_se3_priorxy.hpp>\n#include <g2o/edge_se3_priorxyz.hpp>\n#include <g2o/edge_se3_priorz.hpp>\n#include <g2o/edge_se3_priorvec.hpp>\n#include <g2o/edge_se3_priorquat.hpp>\n\n#include <barometer_bmp388/Barometer.h>\n\n#include \"utility_radar.h\"\n\nusing namespace std;\n\nnamespace radar_graph_slam {\n\nclass RadarGraphSlamNodelet : public nodelet::Nodelet, public ParamServer {\npublic:\n  typedef pcl::PointXYZI PointT;\n  typedef PointXYZIRPYT  PointTypePose;\n  typedef message_filters::sync_policies::ApproximateTime<nav_msgs::Odometry, sensor_msgs::PointCloud2> ApproxSyncPolicy;\n\n  RadarGraphSlamNodelet() {}\n  virtual ~RadarGraphSlamNodelet() {}\n\n  virtual void onInit() {\n    nh = getNodeHandle();\n    mt_nh = getMTNodeHandle();\n    private_nh = getPrivateNodeHandle();\n\n    // init parameters\n    map_cloud_resolution = private_nh.param<double>(\"map_cloud_resolution\", 0.05);\n    trans_odom2map.setIdentity();\n    trans_aftmapped.setIdentity();\n    trans_aftmapped_incremental.setIdentity();\n    initial_pose.setIdentity();\n\n    max_keyframes_per_update = private_nh.param<int>(\"max_keyframes_per_update\", 10);\n\n    anchor_node = nullptr;\n    anchor_edge = nullptr;\n    floor_plane_node = nullptr;\n    graph_slam.reset(new GraphSLAM(private_nh.param<std::string>(\"g2o_solver_type\", \"lm_var\")));\n    keyframe_updater.reset(new KeyframeUpdater(private_nh));\n    loop_detector.reset(new LoopDetector(private_nh));\n    map_cloud_generator.reset(new MapCloudGenerator());\n    inf_calclator.reset(new InformationMatrixCalculator(private_nh));\n    nmea_parser.reset(new NmeaSentenceParser());\n\n    gps_edge_intervals = private_nh.param<int>(\"gps_edge_intervals\", 10);\n    gps_time_offset = private_nh.param<double>(\"gps_time_offset\", 0.0);\n    gps_edge_stddev_xy = private_nh.param<double>(\"gps_edge_stddev_xy\", 10000.0);\n    gps_edge_stddev_z = private_nh.param<double>(\"gps_edge_stddev_z\", 10.0);\n    max_gps_edge_stddev_xy = private_nh.param<double>(\"max_gps_edge_stddev_xy\", 1.0);\n    max_gps_edge_stddev_z = private_nh.param<double>(\"max_gps_edge_stddev_z\", 2.0);\n\n    // Preintegration Parameters\n    enable_preintegration = private_nh.param<bool>(\"enable_preintegration\", false);\n    use_egovel_preinteg_trans = private_nh.param<bool>(\"use_egovel_preinteg_trans\", false);\n    preinteg_trans_stddev = private_nh.param<double>(\"preinteg_trans_stddev\", 1.0);\n    preinteg_orient_stddev = private_nh.param<double>(\"preinteg_orient_stddev\", 2.0);\n\n    enable_barometer = private_nh.param<bool>(\"enable_barometer\", false);\n    barometer_edge_type = private_nh.param<int>(\"barometer_edge_type\", 2);\n    barometer_edge_stddev = private_nh.param<double>(\"barometer_edge_stddev\", 0.5);\n\n    points_topic = private_nh.param<std::string>(\"points_topic\", \"/radar_enhanced_pcl\");\n\n    show_sphere = private_nh.param<bool>(\"show_sphere\", false);\n\n    dataset_name = private_nh.param<std::string>(\"dataset_name\", \"\");\n\n    registration = select_registration_method(private_nh);\n\n    // subscribers\n    odom_sub.reset(new message_filters::Subscriber<nav_msgs::Odometry>(mt_nh, odomTopic, 256));\n    cloud_sub.reset(new message_filters::Subscriber<sensor_msgs::PointCloud2>(mt_nh, \"/filtered_points\", 32));\n    sync.reset(new message_filters::Synchronizer<ApproxSyncPolicy>(ApproxSyncPolicy(32), *odom_sub, *cloud_sub));\n    sync->registerCallback(boost::bind(&RadarGraphSlamNodelet::cloud_callback, this, _1, _2));\n    \n    if(private_nh.param<bool>(\"enable_gps\", true)) {\n      gps_sub = mt_nh.subscribe(\"/gps/geopoint\", 1024, &RadarGraphSlamNodelet::gps_callback, this);\n      nmea_sub = mt_nh.subscribe(\"/gpsimu_driver/nmea_sentence\", 1024, &RadarGraphSlamNodelet::nmea_callback, this);\n      navsat_sub = mt_nh.subscribe(gpsTopic, 1024, &RadarGraphSlamNodelet::navsat_callback, this);\n    }\n    if(private_nh.param<bool>(\"enable_barometer\", false)) {\n      barometer_sub = mt_nh.subscribe(\"/barometer/filtered\", 16, &RadarGraphSlamNodelet::barometer_callback, this);\n    }\n    if (enable_preintegration)\n      imu_odom_sub = nh.subscribe(\"/imu_pre_integ/imu_odom_incre\", 1024, &RadarGraphSlamNodelet::imu_odom_callback, this);\n    imu_sub = nh.subscribe(\"/imu\", 1024, &RadarGraphSlamNodelet::imu_callback, this);\n    command_sub = nh.subscribe(\"/command\", 10, &RadarGraphSlamNodelet::command_callback, this);\n\n    //***** publishers ******\n    markers_pub = mt_nh.advertise<visualization_msgs::MarkerArray>(\"/radar_graph_slam/markers\", 16);\n    // Transform RadarOdom_to_base\n    odom2base_pub = mt_nh.advertise<geometry_msgs::TransformStamped>(\"/radar_graph_slam/odom2base\", 16);\n    aftmapped_odom_pub = mt_nh.advertise<nav_msgs::Odometry>(\"/radar_graph_slam/aftmapped_odom\", 16);\n    aftmapped_odom_incremenral_pub = mt_nh.advertise<nav_msgs::Odometry>(\"/radar_graph_slam/aftmapped_odom_incremental\", 16);\n    map_points_pub = mt_nh.advertise<sensor_msgs::PointCloud2>(\"/radar_graph_slam/map_points\", 1, true);\n    read_until_pub = mt_nh.advertise<std_msgs::Header>(\"/radar_graph_slam/read_until\", 32);\n    odom_frame2frame_pub = mt_nh.advertise<nav_msgs::Odometry>(\"/radar_graph_slam/odom_frame2frame\", 16);\n\n    dump_service_server = mt_nh.advertiseService(\"/radar_graph_slam/dump\", &RadarGraphSlamNodelet::dump_service, this);\n    save_map_service_server = mt_nh.advertiseService(\"/radar_graph_slam/save_map\", &RadarGraphSlamNodelet::save_map_service, this);\n\n    graph_updated = false;\n    double graph_update_interval = private_nh.param<double>(\"graph_update_interval\", 3.0);\n    double map_cloud_update_interval = private_nh.param<double>(\"map_cloud_update_interval\", 10.0);\n    optimization_timer = mt_nh.createWallTimer(ros::WallDuration(graph_update_interval), &RadarGraphSlamNodelet::optimization_timer_callback, this);\n    map_publish_timer = mt_nh.createWallTimer(ros::WallDuration(map_cloud_update_interval), &RadarGraphSlamNodelet::map_points_publish_timer_callback, this);\n  \n    if (dataset_name == \"loop3\")\n    utm_to_world << \n     -0.057621,       0.996222,      -0.064972, -128453.624105,\n     -0.998281,      -0.058194,      -0.006954,  361869.958099,\n     -0.010708,       0.064459,       0.997863,   -5882.237973,\n      0.000000,       0.000000,       0.000000,       1.000000;\n    else if (dataset_name == \"loop2\")\n    utm_to_world <<\n     -0.085585,       0.995774,      -0.033303, -117561.214476,\n     -0.996323,      -0.085401,       0.006904,  364927.287181,\n      0.004031,       0.033772,       0.999421,   -6478.377842,\n      0.000000,       0.000000,       0.000000,       1.000000;\n  }\n\n\nprivate:\n  /**\n   * @brief received point clouds are pushed to #keyframe_queue\n   * @param odom_msg\n   * @param cloud_msg\n   */\n  void cloud_callback(const nav_msgs::OdometryConstPtr& odom_msg, const sensor_msgs::PointCloud2::ConstPtr& cloud_msg) {\n    const ros::Time& stamp = cloud_msg->header.stamp;\n    Eigen::Isometry3d odom_now = odom2isometry(odom_msg);\n    Eigen::Matrix4d matrix_map2base;\n    // Publish TF between /map and /base_link\n    if(keyframes.size() > 0)\n    {\n      const KeyFrame::Ptr& keyframe_last = keyframes.back();\n      Eigen::Isometry3d lastkeyframe_odom_incre =  keyframe_last->odom_scan2scan.inverse() * odom_now;\n      Eigen::Isometry3d keyframe_map2base_matrix = keyframe_last->node->estimate();\n      // map2base = odom^(-1) * base\n      matrix_map2base = (keyframe_map2base_matrix * lastkeyframe_odom_incre).matrix();\n    }\n    geometry_msgs::TransformStamped map2base_trans = matrix2transform(cloud_msg->header.stamp, matrix_map2base, mapFrame, baselinkFrame);\n    if (pow(map2base_trans.transform.rotation.w,2)+pow(map2base_trans.transform.rotation.x,2)+\n      pow(map2base_trans.transform.rotation.y,2)+pow(map2base_trans.transform.rotation.z,2) < pow(0.9,2)) \n      {map2base_trans.transform.rotation.w=1; map2base_trans.transform.rotation.x=0; map2base_trans.transform.rotation.y=0; map2base_trans.transform.rotation.z=0;}\n    map2base_broadcaster.sendTransform(map2base_trans);\n   \n    pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>());\n    pcl::fromROSMsg(*cloud_msg, *cloud);\n    if(baselinkFrame.empty()) {\n      baselinkFrame = cloud_msg->header.frame_id;\n    }\n    \n    // Push ego velocity to queue\n    geometry_msgs::TwistStamped::Ptr twist_(new geometry_msgs::TwistStamped);\n    twist_->header.stamp = cloud_msg->header.stamp;\n    twist_->twist.linear = odom_msg->twist.twist.linear;\n    {\n      std::lock_guard<std::mutex> lock(keyframe_queue_mutex);\n      twist_queue.push_back(twist_);\n    }\n\n    //********** Decided whether to accept the frame as a key frame or not **********\n    if(!keyframe_updater->decide(odom_now, stamp)) {\n      std::lock_guard<std::mutex> lock(keyframe_queue_mutex);\n      if(keyframe_queue.empty()) {\n        std_msgs::Header read_until;\n        read_until.stamp = stamp + ros::Duration(10, 0);\n        read_until.frame_id = points_topic;\n        read_until_pub.publish(read_until);\n        read_until.frame_id = \"/filtered_points\";\n        read_until_pub.publish(read_until);\n      }\n      return;\n    }\n    // Get time of this key frame for Intergeration, to integerate between two key frames\n    thisKeyframeTime = cloud_msg->header.stamp.toSec();\n    \n    double accum_d = keyframe_updater->get_accum_distance();\n    // Construct keyframe\n    KeyFrame::Ptr keyframe(new KeyFrame(keyframe_index, stamp, odom_now, accum_d, cloud));\n    keyframe_index ++;\n\n    if (enable_preintegration){\n      // Intergerate translation of ego velocity, add rotation\n      geometry_msgs::Transform transf_integ = preIntegrationTransform();\n      static uint32_t sequ = 0;\n      nav_msgs::Odometry odom_frame2frame;\n      odom_frame2frame.pose.pose.orientation = transf_integ.rotation;\n      odom_frame2frame.pose.pose.position.x = transf_integ.translation.x;\n      odom_frame2frame.pose.pose.position.y = transf_integ.translation.y;\n      odom_frame2frame.pose.pose.position.z = transf_integ.translation.z;\n      odom_frame2frame.header.frame_id = \"map\";\n      odom_frame2frame.header.stamp = cloud_msg->header.stamp;\n      odom_frame2frame.header.seq = sequ; sequ ++;\n      odom_frame2frame_pub.publish(odom_frame2frame);\n      keyframe->trans_integrated = transf_integ;\n    }\n\n    std::lock_guard<std::mutex> lock(keyframe_queue_mutex);\n    keyframe_queue.push_back(keyframe);\n\n    // Scan Context loop detector - giseop\n    // - SINGLE_SCAN_FULL: using downsampled original point cloud (/full_cloud_projected + downsampling)\n    // - SINGLE_SCAN_FEAT: using surface feature as an input point cloud for scan context (2020.04.01: checked it works.)\n    // - MULTI_SCAN_FEAT: using NearKeyframes (because a MulRan scan does not have beyond region, so to solve this issue ... )\n    const SCInputType sc_input_type = SCInputType::SINGLE_SCAN_FULL; // change this \n\n    if( sc_input_type == SCInputType::SINGLE_SCAN_FULL ) {\n        loop_detector->scManager->makeAndSaveScancontextAndKeys(*cloud);\n    }\n    // else if (sc_input_type == SCInputType::SINGLE_SCAN_FEAT) { \n    //     scManager.makeAndSaveScancontextAndKeys(*thisSurfKeyFrame); \n    // }\n    // else if (sc_input_type == SCInputType::MULTI_SCAN_FEAT) { \n    //     pcl::PointCloud<PointT>::Ptr multiKeyFrameFeatureCloud(new pcl::PointCloud<PointT>());\n    //     loopFindNearKeyframes(multiKeyFrameFeatureCloud, cloudKeyPoses6D->size() - 1, historyKeyframeSearchNum);\n    //     scManager.makeAndSaveScancontextAndKeys(*multiKeyFrameFeatureCloud); \n    // }\n    \n\n    lastKeyframeTime = thisKeyframeTime;\n  }\n\n\n  void imu_callback(const sensor_msgs::ImuConstPtr& imu_odom_msg) {\n    // Transform to Radar's Frame\n    geometry_msgs::QuaternionStamped::Ptr imu_quat(new geometry_msgs::QuaternionStamped);\n    imu_quat->quaternion = imu_odom_msg->orientation;\n    Eigen::Quaterniond imu_quat_from(imu_quat->quaternion.w, imu_quat->quaternion.x, imu_quat->quaternion.y, imu_quat->quaternion.z);\n    Eigen::Quaterniond imu_quat_deskew = imu_quat_from * extQRPY;\n    imu_quat_deskew.normalize();\n\n    static int cnt = 0;\n    if(cnt == 0) {\n      double roll, pitch, yaw;\n      tf::Quaternion orientation = tf::Quaternion(imu_quat_deskew.x(),imu_quat_deskew.y(),imu_quat_deskew.z(),imu_quat_deskew.w());\n      tf::quaternionMsgToTF(imu_odom_msg->orientation, orientation);\n      tf::Matrix3x3(orientation).getRPY(roll, pitch, yaw);\n      // Eigen::Matrix3d imu_mat_deskew = imu_quat_deskew.toRotationMatrix();\n      // Eigen::Vector3d eulerAngle = imu_mat_deskew.eulerAngles(0,1,2); // roll pitch yaw\n      Eigen::AngleAxisd rollAngle(AngleAxisd(roll,Vector3d::UnitX()));\n      Eigen::AngleAxisd pitchAngle(AngleAxisd(pitch,Vector3d::UnitY()));\n      Eigen::AngleAxisd yawAngle(AngleAxisd(0.0,Vector3d::UnitZ()));\n      Eigen::Matrix3d imu_mat_final; imu_mat_final = yawAngle * pitchAngle * rollAngle;\n\n      Eigen::Isometry3d isom_initial_pose;\n      isom_initial_pose.setIdentity();\n      isom_initial_pose.rotate(imu_mat_final); // Set rotation\n      initial_pose = isom_initial_pose.matrix();\n      ROS_INFO(\"Initial Position Matrix = \");\n      std::cout << \n        initial_pose(0,0) << \", \" << initial_pose(0,1) << \", \" << initial_pose(0,2) << \", \" << initial_pose(0,3) << \", \" << std::endl <<\n        initial_pose(1,0) << \", \" << initial_pose(1,1) << \", \" << initial_pose(1,2) << \", \" << initial_pose(1,3) << \", \" << std::endl <<\n        initial_pose(2,0) << \", \" << initial_pose(2,1) << \", \" << initial_pose(2,2) << \", \" << initial_pose(2,3) << \", \" << std::endl <<\n        initial_pose(3,0) << \", \" << initial_pose(3,1) << \", \" << initial_pose(3,2) << \", \" << initial_pose(3,3) << \", \" << std::endl << std::endl;\n      cnt = 1;\n    }\n  }\n\n  void imu_odom_callback(const nav_msgs::OdometryConstPtr& imu_odom_msg) {\n    {\n    std::lock_guard<std::mutex> lock(keyframe_queue_mutex);\n    imu_odom_queue.push_back(imu_odom_msg);\n    }\n  }\n\n  geometry_msgs::Transform preIntegrationTransform(){\n    double lastImuOdomQT = -1;\n    // double lastTwistQT = -1;\n    double delta_t = 0;\n    size_t imu_odom_end_index = 0; // The index of the last used IMU odom\n    geometry_msgs::Transform trans_; // Transform to return\n    // pop old IMU orientation message\n    while (!imu_odom_queue.empty() && imu_odom_queue.front()->header.stamp.toSec() < lastKeyframeTime - delta_t){\n        lastImuOdomQT = imu_odom_queue.front()->header.stamp.toSec();\n        imu_odom_queue.pop_front();\n    }\n    // pop old Twist message\n    while (!twist_queue.empty() && twist_queue.front()->header.stamp.toSec() < lastKeyframeTime - delta_t){\n        // lastTwistQT = twist_queue.front()->header.stamp.toSec();\n        twist_queue.pop_front();\n    }\n    // repropogate\n    Eigen::Isometry3d isom_frame2frame;  // Translation increment between last key frame and this IMU msg\n    Eigen::Isometry3d isometry_rotation; // Rotation of IMU odom\n    Eigen::Isometry3d isometry_translation;  // Translation of IMU odom\n    isom_frame2frame.setIdentity();\n    isometry_rotation.setIdentity();\n    geometry_msgs::Vector3 translation_frame2frame; // Translation between two key frames\n    if (!imu_odom_queue.empty())\n    {\n      // (Doing this because some imu_odom later than thisKeyframeTime will be recieved)\n      for (size_t i = 0; i < imu_odom_queue.size(); ++i){\n        if (imu_odom_queue.at(i)->header.stamp.toSec() < thisKeyframeTime)\n          imu_odom_end_index = i;\n      }\n      std::unique_ptr<CubicInterpolation> egovelIntegratorX;\n      std::unique_ptr<CubicInterpolation> egovelIntegratorY;\n      std::unique_ptr<CubicInterpolation> egovelIntegratorZ;\n      if (use_egovel_preinteg_trans) {\n        // integrate imu message from the beginning of this optimization\n        size_t twist_length = twist_queue.size();\n        Eigen::MatrixXd q_via_x(twist_length,3),q_via_y(twist_length,3),q_via_z(twist_length,3);\n        Eigen::VectorXd t_via_x(twist_length),t_via_y(twist_length),t_via_z(twist_length);\n        // double dt = 1.0/12;\n        for (size_t i=0; i < twist_length; i++){\n          q_via_x(i, 0) = twist_queue.at(i)->twist.linear.x;  q_via_x(i, 1) = 0;  q_via_x(i, 2) = 0;\n          // acc: q(i,1)=(twist_queue.at(i+1).twist.linear.x - twist_queue.at(i).twist.linear.x) / dt;\n          t_via_x(i) = twist_queue.at(i)->header.stamp.toSec();\n          q_via_y(i, 0) = twist_queue.at(i)->twist.linear.y;  q_via_y(i, 1) = 0;  q_via_y(i, 2) = 0;\n          t_via_y(i) = t_via_x(i);\n          q_via_z(i, 0) = twist_queue.at(i)->twist.linear.z;  q_via_z(i, 1) = 0;  q_via_z(i, 2) = 0;\n          t_via_z(i) = t_via_x(i);\n        }\n        egovelIntegratorX.reset(new CubicInterpolation(q_via_x, t_via_x)); // Integrator of ego velocity at X axis\n        egovelIntegratorY.reset(new CubicInterpolation(q_via_y, t_via_y)); // Integrator of ego velocity at Y axis\n        egovelIntegratorZ.reset(new CubicInterpolation(q_via_z, t_via_z)); // Integrator of ego velocity at Z axis\n        // ROS_INFO(\"Twist queue - Start time: %f Stop time: %f\", t_via_x(0), t_via_x(t_via_x.size()-1));\n      }\n      Eigen::Isometry3d isom_imu_odom_last(Eigen::Isometry3d::Identity());\n      Eigen::Quaterniond q_imu_odom(imu_odom_queue.front()->pose.pose.orientation.w,\n                                imu_odom_queue.front()->pose.pose.orientation.x,\n                                imu_odom_queue.front()->pose.pose.orientation.y,\n                                imu_odom_queue.front()->pose.pose.orientation.z);\n      isom_imu_odom_last.rotate(q_imu_odom);\n      isom_imu_odom_last.pretranslate(Eigen::Vector3d(imu_odom_queue.front()->pose.pose.position.x,\n                                                  imu_odom_queue.front()->pose.pose.position.y,\n                                                  imu_odom_queue.front()->pose.pose.position.z));\n      Eigen::Isometry3d isom_imu_odom_this(Eigen::Isometry3d::Identity());\n      q_imu_odom = Eigen::Quaterniond(imu_odom_queue.at(imu_odom_end_index)->pose.pose.orientation.w,\n                                imu_odom_queue.at(imu_odom_end_index)->pose.pose.orientation.x,\n                                imu_odom_queue.at(imu_odom_end_index)->pose.pose.orientation.y,\n                                imu_odom_queue.at(imu_odom_end_index)->pose.pose.orientation.z);\n      isom_imu_odom_this.rotate(q_imu_odom);\n      isom_imu_odom_this.pretranslate(Eigen::Vector3d(imu_odom_queue.at(imu_odom_end_index)->pose.pose.position.x,\n                                                  imu_odom_queue.at(imu_odom_end_index)->pose.pose.position.y,\n                                                  imu_odom_queue.at(imu_odom_end_index)->pose.pose.position.z));\n      Eigen::Isometry3d isom_imu_odom_btn = isom_imu_odom_last.inverse() * isom_imu_odom_this;\n      // cout << isom_imu_odom_btn.matrix() << endl;\n      // ROS_INFO(\"IMU Odom queue - Start time: %f Stop time: %f\", imu_odom_queue.at(0)->header.stamp.toSec(), imu_odom_queue.at(imu_odom_end_index)->header.stamp.toSec());\n      if (use_egovel_preinteg_trans){ // Use Ego vel as translation integration\n        for (size_t i = 0; i < imu_odom_end_index + 1; i++)\n        {\n            nav_msgs::Odometry::ConstPtr thisImu = imu_odom_queue.at(i);\n            double imuOdomTime = thisImu->header.stamp.toSec();\n            isometry_translation.setIdentity();\n            isometry_rotation.setIdentity();\n            \n            double dt = (lastImuOdomQT < 0) ? (1.0 / 200.0) :(imuOdomTime - lastImuOdomQT);\n            Eigen::Vector3d translation_ = Eigen::Vector3d( egovelIntegratorX->getPosition(imuOdomTime),\n                                                            egovelIntegratorY->getPosition(imuOdomTime),\n                                                            egovelIntegratorZ->getPosition(imuOdomTime))* dt;\n            isometry_translation.pretranslate(translation_); // Set translation\n            isom_frame2frame = isom_frame2frame.pretranslate(isometry_translation.translation());\n\n            lastImuOdomQT = imuOdomTime;\n        }\n      }\n      else {  // Use IMU as translation integration\n        isom_frame2frame = isom_imu_odom_btn;\n      }\n      translation_frame2frame.x = isom_frame2frame.translation().x();\n      translation_frame2frame.y = isom_frame2frame.translation().y();\n      translation_frame2frame.z = isom_frame2frame.translation().z();\n      const auto& last_imu_orien = imu_odom_queue.at(imu_odom_end_index);\n      trans_.rotation = last_imu_orien->pose.pose.orientation;\n      trans_.translation = translation_frame2frame;\n    }\n    return trans_;\n  }\n\n\n  void barometer_callback(const barometer_bmp388::BarometerPtr& baro_msg) {\n    if(!enable_barometer) {\n      return;\n    }\n    std::lock_guard<std::mutex> lock(barometer_queue_mutex);\n    barometer_queue.push_back(baro_msg);\n  }\n\n  bool flush_barometer_queue() {\n    std::lock_guard<std::mutex> lock(barometer_queue_mutex);\n    if(keyframes.empty() || barometer_queue.empty()) {\n      return false;\n    }\n    bool updated = false;\n    auto barometer_cursor = barometer_queue.begin();\n\n    for(size_t i=0; i < keyframes.size(); i++) {\n      auto keyframe = keyframes.at(i);\n      if(keyframe->stamp > barometer_queue.back()->header.stamp) {\n        break;\n      }\n      if(keyframe->stamp < (*barometer_cursor)->header.stamp || keyframe->altitude) {\n        continue;\n      }\n      // find the barometer data which is closest to the keyframe_\n      auto closest_barometer = barometer_cursor;\n      for(auto baro = barometer_cursor; baro != barometer_queue.end(); baro++) {\n        auto dt = ((*closest_barometer)->header.stamp - keyframe->stamp).toSec();\n        auto dt2 = ((*baro)->header.stamp - keyframe->stamp).toSec();\n        if(std::abs(dt) < std::abs(dt2)) {\n          break;\n        }\n        closest_barometer = baro;\n      }\n      // if the time residual between the barometer and keyframe_ is too large, skip it\n      barometer_cursor = closest_barometer;\n      if(0.2 < std::abs(((*closest_barometer)->header.stamp - keyframe->stamp).toSec())) {\n        continue;\n      }\n\n      Eigen::Vector4d aftmapped_pos(keyframe->odom_scan2scan.translation().x(), keyframe->odom_scan2scan.translation().y(), (*closest_barometer)->altitude, 1.0);\n      aftmapped_pos = initial_pose * aftmapped_pos;\n      // std::cout << \"baro position: \" << aftmapped_pos(0) << \", \" << aftmapped_pos(1) << \", \" << aftmapped_pos(2) << std::endl;\n      \n      nav_msgs::Odometry initial_odom; // Initial posture\n      initial_odom = matrix2odom(keyframe->stamp, initial_pose, mapFrame, baselinkFrame);\n\n      Eigen::Vector1d z(aftmapped_pos(2));\n      // the first barometer data position will be the origin of the map\n      if(!zero_alt) {\n        zero_alt = z;\n      }\n      z -= (*zero_alt);\n      keyframe->altitude = z;\n\n      if (enable_barometer){ \n        //********** G2O Edge *********** \n        if (barometer_edge_type == 1){\n          g2o::OptimizableGraph::Edge* edge;\n          Eigen::Vector1d information_matrix = Eigen::Vector1d::Identity() / barometer_edge_stddev;\n          edge = graph_slam->add_se3_prior_z_edge(keyframe->node, z, information_matrix);\n          graph_slam->add_robust_kernel(edge, private_nh.param<std::string>(\"barometer_edge_robust_kernel\", \"NONE\"), private_nh.param<double>(\"barometer_edge_robust_kernel_size\", 1.0));\n        }\n        else if (barometer_edge_type == 2 && i != 0 && keyframes.at(i-1)->altitude.is_initialized()){\n          g2o::OptimizableGraph::Edge* edge;\n          Eigen::Vector1d information_matrix = Eigen::Vector1d::Identity() / barometer_edge_stddev;\n          Eigen::Vector1d relative_z(keyframe->altitude.value() - keyframes.at(i-1)->altitude.value());\n          edge = graph_slam->add_se3_z_edge(keyframe->node, keyframes.at(i-1)->node, relative_z, information_matrix);\n          graph_slam->add_robust_kernel(edge, private_nh.param<std::string>(\"barometer_edge_robust_kernel\", \"NONE\"), private_nh.param<double>(\"barometer_edge_robust_kernel_size\", 1.0));\n        }\n      }\n      updated = true;\n    }\n    auto remove_loc = std::upper_bound(barometer_queue.begin(), barometer_queue.end(), keyframes.back()->stamp, [=](const ros::Time& stamp, const barometer_bmp388::BarometerConstPtr& baropoint) { return stamp < baropoint->header.stamp; });\n    barometer_queue.erase(barometer_queue.begin(), remove_loc);\n    return updated;\n  }\n\n  /**\n   * @brief this method adds all the keyframes_ in #keyframe_queue to the pose graph (odometry edges)\n   * @return if true, at least one keyframe_ was added to the pose graph\n   */\n  bool flush_keyframe_queue() {\n    std::lock_guard<std::mutex> lock(keyframe_queue_mutex);\n\n    if(keyframe_queue.empty()) {\n      return false;\n    }\n\n    trans_odom2map_mutex.lock();\n    Eigen::Isometry3d odom2map(trans_odom2map.cast<double>());\n    trans_odom2map_mutex.unlock();\n\n    int num_processed = 0;\n    // ********** Select number of keyframess to be optimized **********\n    for(int i = 0; i < std::min<int>(keyframe_queue.size(), max_keyframes_per_update); i++) {\n      num_processed = i;\n\n      const auto& keyframe = keyframe_queue[i];\n      // new_keyframess will be tested later for loop closure\n      new_keyframes.push_back(keyframe);\n\n      // add pose node\n      Eigen::Isometry3d odom = odom2map * keyframe->odom_scan2scan;\n      // ********** Vertex of keyframess is contructed here ***********\n      keyframe->node = graph_slam->add_se3_node(odom);\n      keyframe_hash[keyframe->stamp] = keyframe;\n\n      // fix the first node\n      if(keyframes.empty() && new_keyframes.size() == 1) {\n        if(private_nh.param<bool>(\"fix_first_node\", false)) {\n          Eigen::MatrixXd inf = Eigen::MatrixXd::Identity(6, 6);\n          std::stringstream sst(private_nh.param<std::string>(\"fix_first_node_stddev\", \"1 1 1 1 1 1\"));\n          for(int i = 0; i < 6; i++) {\n            double stddev = 1.0;\n            sst >> stddev;\n            inf(i, i) = 1.0 / stddev;\n          }\n          anchor_node = graph_slam->add_se3_node(Eigen::Isometry3d::Identity());\n          anchor_node->setFixed(true);\n          anchor_edge = graph_slam->add_se3_edge(anchor_node, keyframe->node, Eigen::Isometry3d::Identity(), inf);\n        }\n      }\n      \n      if(i == 0 && keyframes.empty()) {\n        continue;\n      }\n\n      /***** Scan-to-Scan Add edge to between consecutive keyframes *****/\n      const auto& prev_keyframe = i == 0 ? keyframes.back() : keyframe_queue[i - 1];\n      // relative pose between odom of previous frame and this frame R2=R12*R1 => R12 = inv(R2) * R1\n      Eigen::Isometry3d relative_pose = keyframe->odom_scan2scan.inverse() * prev_keyframe->odom_scan2scan;\n      // calculate fitness score as information \n      Eigen::MatrixXd information = inf_calclator->calc_information_matrix(keyframe->cloud, prev_keyframe->cloud, relative_pose);\n      auto edge = graph_slam->add_se3_edge(keyframe->node, prev_keyframe->node, relative_pose, information);\n      // cout << information << endl;\n      graph_slam->add_robust_kernel(edge, private_nh.param<std::string>(\"odometry_edge_robust_kernel\", \"NONE\"), private_nh.param<double>(\"odometry_edge_robust_kernel_size\", 1.0));\n      \n\n      if (enable_preintegration){\n        // Add Preintegration edge\n        geometry_msgs::Transform relative_trans = keyframe->trans_integrated;\n        g2o::SE3Quat relative_se3quat ( Eigen::Quaterniond(relative_trans.rotation.w, relative_trans.rotation.x, relative_trans.rotation.y, relative_trans.rotation.z), \n                                        Eigen::Vector3d(relative_trans.translation.x, relative_trans.translation.y, relative_trans.translation.z));\n        Eigen::Isometry3d relative_isometry = transform2isometry(relative_trans);\n        Eigen::MatrixXd information_integ = Eigen::MatrixXd::Identity(6, 6);\n        information_integ <<  1.0 / preinteg_trans_stddev, 0, 0, 0, 0, 0,\n                              0, 1.0 / preinteg_trans_stddev, 0, 0, 0, 0,\n                              0, 0, 1.0 / preinteg_trans_stddev, 0, 0, 0,\n                              0, 0, 0, 1.0 / preinteg_orient_stddev, 0, 0,\n                              0, 0, 0, 0, 1.0 / preinteg_orient_stddev, 0,\n                              0, 0, 0, 0, 0, 1.0 / preinteg_orient_stddev; \n        auto edge_integ = graph_slam->add_se3_edge(prev_keyframe->node, keyframe->node, relative_isometry, information_integ);\n        graph_slam->add_robust_kernel(edge_integ, private_nh.param<std::string>(\"integ_edge_robust_kernel\", \"NONE\"), private_nh.param<double>(\"integ_edge_robust_kernel_size\", 1.0));\n      }\n    }\n\n    std_msgs::Header read_until;\n    read_until.stamp = keyframe_queue[num_processed]->stamp + ros::Duration(10, 0);\n    read_until.frame_id = points_topic;\n    read_until_pub.publish(read_until);\n    read_until.frame_id = \"/filtered_points\";\n    read_until_pub.publish(read_until);\n\n    keyframe_queue.erase(keyframe_queue.begin(), keyframe_queue.begin() + num_processed + 1);\n    return true;\n  }\n\n\n  /**\n   * @brief Back-end Optimization. This methods adds all the data in the queues to the pose graph, and then optimizes the pose graph\n   * @param event\n   */\n  void optimization_timer_callback(const ros::WallTimerEvent& event) {\n    std::lock_guard<std::mutex> lock(main_thread_mutex);\n\n    // add keyframes_ and floor coeffs in the queues to the pose graph\n    bool keyframe_updated = flush_keyframe_queue();\n\n    if(!keyframe_updated) {\n      std_msgs::Header read_until;\n      read_until.stamp = ros::Time::now() + ros::Duration(30, 0);\n      read_until.frame_id = points_topic;\n      read_until_pub.publish(read_until);\n      read_until.frame_id = \"/filtered_points\";\n      read_until_pub.publish(read_until);\n    }\n\n    if(!keyframe_updated & !flush_gps_queue() & !flush_barometer_queue()) {\n      return;\n    }\n    \n    // loop detection\n    if(private_nh.param<bool>(\"enable_loop_closure\", false)){\n      std::vector<Loop::Ptr> loops = loop_detector->detect(keyframes, new_keyframes, *graph_slam);\n    }\n\n    // Copy \"new_keyframes_\" to vector  \"keyframes_\", \"new_keyframes_\" was used for loop detaction \n    std::copy(new_keyframes.begin(), new_keyframes.end(), std::back_inserter(keyframes));\n    new_keyframes.clear();\n\n    if(private_nh.param<bool>(\"enable_loop_closure\", false))\n      addLoopFactor();\n\n    // move the first node / position to the current estimate of the first node pose\n    // so the first node moves freely while trying to stay around the origin\n    if(anchor_node && private_nh.param<bool>(\"fix_first_node_adaptive\", true)) {\n      Eigen::Isometry3d anchor_target = static_cast<g2o::VertexSE3*>(anchor_edge->vertices()[1])->estimate();\n      anchor_node->setEstimate(anchor_target);\n    }\n\n    // optimize the pose graph\n    int num_iterations = private_nh.param<int>(\"g2o_solver_num_iterations\", 1024);\n    clock_t start_ms = clock();\n    graph_slam->optimize(num_iterations);\n    clock_t end_ms = clock();\n    double time_used = double(end_ms - start_ms) / CLOCKS_PER_SEC;\n    opt_time.push_back(time_used);\n\n    //********** publish tf **********\n    const auto& keyframe = keyframes.back();\n    // RadarOdom_to_base = map_to_base * map_to_RadarOdom^(-1)\n    Eigen::Isometry3d trans = keyframe->node->estimate() * keyframe->odom_scan2scan.inverse();\n    Eigen::Isometry3d map2base_trans = keyframe->node->estimate();\n    trans_odom2map_mutex.lock();\n    trans_odom2map = trans.matrix();\n    // map2base_incremental = map2base_last^(-1) * map2base_this \n    trans_aftmapped_incremental = trans_aftmapped.inverse() * map2base_trans;\n    trans_aftmapped = map2base_trans;\n    trans_odom2map_mutex.unlock();\n\n    std::vector<KeyFrameSnapshot::Ptr> snapshot(keyframes.size());\n    std::transform(keyframes.begin(), keyframes.end(), snapshot.begin(), [=](const KeyFrame::Ptr& k) { return std::make_shared<KeyFrameSnapshot>(k); });\n\n    keyframes_snapshot_mutex.lock();\n    keyframes_snapshot.swap(snapshot);\n    keyframes_snapshot_mutex.unlock();\n    graph_updated = true;\n\n    // Publish After-Mapped Odometry\n    nav_msgs::Odometry aft = isometry2odom(keyframe->stamp, trans_aftmapped, mapFrame, odometryFrame);\n    aftmapped_odom_pub.publish(aft);\n\n    // Publish After-Mapped Odometry Incrementation\n    nav_msgs::Odometry aft_incre = isometry2odom(keyframe->stamp, trans_aftmapped_incremental, mapFrame, odometryFrame);\n    aftmapped_odom_incremenral_pub.publish(aft_incre);\n\n    // Publish /odom to /base_link\n    if(odom2base_pub.getNumSubscribers()) {  // Returns the number of subscribers that are currently connected to this Publisher\n      geometry_msgs::TransformStamped ts = matrix2transform(keyframe->stamp, trans.matrix(), mapFrame, odometryFrame);\n      odom2base_pub.publish(ts);\n    }\n\n    if(markers_pub.getNumSubscribers()) {\n      auto markers = create_marker_array(ros::Time::now());\n      markers_pub.publish(markers);\n    }\n  }\n\n  void addLoopFactor()\n  {\n    if (loop_detector->loopIndexQueue.empty())\n      return;\n    for (int i = 0; i < (int)loop_detector->loopIndexQueue.size(); ++i){\n      int indexFrom = loop_detector->loopIndexQueue[i].first;\n      int indexTo = loop_detector->loopIndexQueue[i].second;\n      Eigen::Isometry3d poseBetween = loop_detector->loopPoseQueue[i];\n      Eigen::MatrixXd information_matrix = loop_detector->loopInfoQueue[i];\n      auto edge = graph_slam->add_se3_edge(keyframes[indexFrom]->node, keyframes[indexTo]->node, poseBetween, information_matrix);\n      graph_slam->add_robust_kernel(edge, private_nh.param<std::string>(\"loop_closure_edge_robust_kernel\", \"NONE\"), private_nh.param<double>(\"loop_closure_edge_robust_kernel_size\", 1.0));\n    }\n    // loopIndexQueue.clear();\n    // loopPoseQueue.clear();\n    // loopInfoQueue.clear();\n    // aLoopIsClosed = true;\n  }\n\n  /**\n   * @brief generate map point cloud and publish it\n   * @param event\n   */\n  void map_points_publish_timer_callback(const ros::WallTimerEvent& event) {\n    if(!map_points_pub.getNumSubscribers() || !graph_updated) {\n      return;\n    }\n    std::vector<KeyFrameSnapshot::Ptr> snapshot;\n    keyframes_snapshot_mutex.lock();\n    snapshot = keyframes_snapshot;\n    keyframes_snapshot_mutex.unlock();\n\n    auto cloud = map_cloud_generator->generate(snapshot, map_cloud_resolution);\n    if(!cloud) {\n      return;\n    }\n    cloud->header.frame_id = mapFrame;\n    cloud->header.stamp = snapshot.back()->cloud->header.stamp;\n\n    sensor_msgs::PointCloud2Ptr cloud_msg(new sensor_msgs::PointCloud2());\n    pcl::toROSMsg(*cloud, *cloud_msg);\n\n    map_points_pub.publish(cloud_msg);\n  }\n\n  /**\n   * @brief create visualization marker\n   * @param stamp\n   * @return\n   */\n  visualization_msgs::MarkerArray create_marker_array(const ros::Time& stamp) const {\n    visualization_msgs::MarkerArray markers;\n    if (show_sphere)\n      markers.markers.resize(5);\n    else\n      markers.markers.resize(4);\n\n    // loop edges\n    visualization_msgs::Marker& loop_marker = markers.markers[0];\n    loop_marker.header.frame_id = \"map\";\n    loop_marker.header.stamp = stamp;\n    loop_marker.action = visualization_msgs::Marker::ADD;\n    loop_marker.type = visualization_msgs::Marker::LINE_LIST;\n    loop_marker.ns = \"loop_edges\";\n    loop_marker.id = 1;\n    loop_marker.pose.orientation.w = 1;\n    loop_marker.scale.x = 0.1; loop_marker.scale.y = 0.1; loop_marker.scale.z = 0.1;\n    loop_marker.color.r = 0.9; loop_marker.color.g = 0.9; loop_marker.color.b = 0;\n    loop_marker.color.a = 1;\n    for (auto it = loop_detector->loopIndexContainer.begin(); it != loop_detector->loopIndexContainer.end(); ++it)\n    {\n      int key_cur = it->first;\n      int key_pre = it->second;\n      geometry_msgs::Point p;\n      Eigen::Vector3d pos = keyframes[key_cur]->node->estimate().translation();\n      p.x = pos.x();\n      p.y = pos.y();\n      p.z = pos.z();\n      loop_marker.points.push_back(p);\n      pos = keyframes[key_pre]->node->estimate().translation();\n      p.x = pos.x();\n      p.y = pos.y();\n      p.z = pos.z();\n      loop_marker.points.push_back(p);\n    }\n\n    // node markers\n    visualization_msgs::Marker& traj_marker = markers.markers[1];\n    traj_marker.header.frame_id = \"map\";\n    traj_marker.header.stamp = stamp;\n    traj_marker.ns = \"nodes\";\n    traj_marker.id = 0;\n    traj_marker.type = visualization_msgs::Marker::SPHERE_LIST;\n\n    traj_marker.pose.orientation.w = 1.0;\n    traj_marker.scale.x = traj_marker.scale.y = traj_marker.scale.z = 0.3;\n\n    visualization_msgs::Marker& imu_marker = markers.markers[2];\n    imu_marker.header = traj_marker.header;\n    imu_marker.ns = \"imu\";\n    imu_marker.id = 1;\n    imu_marker.type = visualization_msgs::Marker::SPHERE_LIST;\n\n    imu_marker.pose.orientation.w = 1.0;\n    imu_marker.scale.x = imu_marker.scale.y = imu_marker.scale.z = 0.75;\n\n    traj_marker.points.resize(keyframes.size());\n    traj_marker.colors.resize(keyframes.size());\n    for(size_t i = 0; i < keyframes.size(); i++) {\n      Eigen::Vector3d pos = keyframes[i]->node->estimate().translation();\n      traj_marker.points[i].x = pos.x();\n      traj_marker.points[i].y = pos.y();\n      traj_marker.points[i].z = pos.z();\n\n      double p = static_cast<double>(i) / keyframes.size();\n      traj_marker.colors[i].r = 0.0;//1.0 - p;\n      traj_marker.colors[i].g = 1.0;//p;\n      traj_marker.colors[i].b = 0.0;\n      traj_marker.colors[i].a = 1.0;\n\n      if(keyframes[i]->acceleration) {\n        Eigen::Vector3d pos = keyframes[i]->node->estimate().translation();\n        geometry_msgs::Point point;\n        point.x = pos.x();\n        point.y = pos.y();\n        point.z = pos.z();\n\n        std_msgs::ColorRGBA color;\n        color.r = 0.0;\n        color.g = 0.0;\n        color.b = 1.0;\n        color.a = 0.1;\n\n        imu_marker.points.push_back(point);\n        imu_marker.colors.push_back(color);\n      }\n    }\n\n    // edge markers\n    visualization_msgs::Marker& edge_marker = markers.markers[3];\n    edge_marker.header.frame_id = \"map\";\n    edge_marker.header.stamp = stamp;\n    edge_marker.ns = \"edges\";\n    edge_marker.id = 2;\n    edge_marker.type = visualization_msgs::Marker::LINE_LIST;\n\n    edge_marker.pose.orientation.w = 1.0;\n    edge_marker.scale.x = 0.05;\n\n    edge_marker.points.resize(graph_slam->graph->edges().size() * 2);\n    edge_marker.colors.resize(graph_slam->graph->edges().size() * 2);\n\n    auto edge_itr = graph_slam->graph->edges().begin();\n    for(int i = 0; edge_itr != graph_slam->graph->edges().end(); edge_itr++, i++) {\n      g2o::HyperGraph::Edge* edge = *edge_itr;\n      g2o::EdgeSE3* edge_se3 = dynamic_cast<g2o::EdgeSE3*>(edge);\n      if(edge_se3) {\n        g2o::VertexSE3* v1 = dynamic_cast<g2o::VertexSE3*>(edge_se3->vertices()[0]);\n        g2o::VertexSE3* v2 = dynamic_cast<g2o::VertexSE3*>(edge_se3->vertices()[1]);\n        \n        Eigen::Vector3d pt1 = v1->estimate().translation();\n        Eigen::Vector3d pt2 = v2->estimate().translation();\n\n        edge_marker.points[i * 2].x = pt1.x();\n        edge_marker.points[i * 2].y = pt1.y();\n        edge_marker.points[i * 2].z = pt1.z();\n        edge_marker.points[i * 2 + 1].x = pt2.x();\n        edge_marker.points[i * 2 + 1].y = pt2.y();\n        edge_marker.points[i * 2 + 1].z = pt2.z();\n\n        double p1 = static_cast<double>(v1->id()) / graph_slam->graph->vertices().size();\n        double p2 = static_cast<double>(v2->id()) / graph_slam->graph->vertices().size();\n        edge_marker.colors[i * 2].r = 0.0;//1.0 - p1;\n        edge_marker.colors[i * 2].g = 1.0;//p1;\n        edge_marker.colors[i * 2].a = 1.0;\n        edge_marker.colors[i * 2 + 1].r = 0.0;//1.0 - p2;\n        edge_marker.colors[i * 2 + 1].g = 1.0;//p2;\n        edge_marker.colors[i * 2 + 1].a = 1.0;\n\n        if(std::abs(v1->id() - v2->id()) > 2) {\n          // edge_marker.points[i * 2].z += 0.5;\n          // edge_marker.points[i * 2 + 1].z += 0.5;\n          edge_marker.colors[i * 2].r = 0.9;\n          edge_marker.colors[i * 2].g = 0.9;\n          edge_marker.colors[i * 2].b = 0.0;\n          edge_marker.colors[i * 2 + 1].r = 0.9;\n          edge_marker.colors[i * 2 + 1].g = 0.9;\n          edge_marker.colors[i * 2 + 1].b = 0.0;\n          edge_marker.colors[i * 2].a = 0.0;\n          edge_marker.colors[i * 2 + 1].a += 0.0;\n        }\n        continue;\n      }\n\n      g2o::EdgeSE3Plane* edge_plane = dynamic_cast<g2o::EdgeSE3Plane*>(edge);\n      if(edge_plane) {\n        g2o::VertexSE3* v1 = dynamic_cast<g2o::VertexSE3*>(edge_plane->vertices()[0]);\n        Eigen::Vector3d pt1 = v1->estimate().translation();\n        Eigen::Vector3d pt2(pt1.x(), pt1.y(), 0.0);\n\n        edge_marker.points[i * 2].x = pt1.x();\n        edge_marker.points[i * 2].y = pt1.y();\n        edge_marker.points[i * 2].z = pt1.z();\n        edge_marker.points[i * 2 + 1].x = pt2.x();\n        edge_marker.points[i * 2 + 1].y = pt2.y();\n        edge_marker.points[i * 2 + 1].z = pt2.z();\n\n        edge_marker.colors[i * 2].b = 1.0;\n        edge_marker.colors[i * 2].a = 1.0;\n        edge_marker.colors[i * 2 + 1].b = 1.0;\n        edge_marker.colors[i * 2 + 1].a = 1.0;\n\n        continue;\n      }\n\n      g2o::EdgeSE3PriorXY* edge_priori_xy = dynamic_cast<g2o::EdgeSE3PriorXY*>(edge);\n      if(edge_priori_xy) {\n        g2o::VertexSE3* v1 = dynamic_cast<g2o::VertexSE3*>(edge_priori_xy->vertices()[0]);\n        Eigen::Vector3d pt1 = v1->estimate().translation();\n        Eigen::Vector3d pt2 = Eigen::Vector3d::Zero();\n        pt2.head<2>() = edge_priori_xy->measurement();\n\n        edge_marker.points[i * 2].x = pt1.x();\n        edge_marker.points[i * 2].y = pt1.y();\n        edge_marker.points[i * 2].z = pt1.z() + 0.5;\n        edge_marker.points[i * 2 + 1].x = pt2.x();\n        edge_marker.points[i * 2 + 1].y = pt2.y();\n        edge_marker.points[i * 2 + 1].z = pt2.z() + 0.5;\n\n        edge_marker.colors[i * 2].r = 1.0;\n        edge_marker.colors[i * 2].a = 1.0;\n        edge_marker.colors[i * 2 + 1].r = 1.0;\n        edge_marker.colors[i * 2 + 1].a = 1.0;\n\n        continue;\n      }\n\n      g2o::EdgeSE3PriorXYZ* edge_priori_xyz = dynamic_cast<g2o::EdgeSE3PriorXYZ*>(edge);\n      if(edge_priori_xyz) {\n        g2o::VertexSE3* v1 = dynamic_cast<g2o::VertexSE3*>(edge_priori_xyz->vertices()[0]);\n        Eigen::Vector3d pt1 = v1->estimate().translation();\n        Eigen::Vector3d pt2 = edge_priori_xyz->measurement();\n\n        edge_marker.points[i * 2].x = pt1.x();\n        edge_marker.points[i * 2].y = pt1.y();\n        edge_marker.points[i * 2].z = pt1.z() + 0.5;\n        edge_marker.points[i * 2 + 1].x = pt2.x();\n        edge_marker.points[i * 2 + 1].y = pt2.y();\n        edge_marker.points[i * 2 + 1].z = pt2.z();\n\n        edge_marker.colors[i * 2].r = 1.0;\n        edge_marker.colors[i * 2].a = 1.0;\n        edge_marker.colors[i * 2 + 1].r = 1.0;\n        edge_marker.colors[i * 2 + 1].a = 1.0;\n\n        continue;\n      }\n    }\n\n    if (show_sphere)\n    {\n      // sphere\n      visualization_msgs::Marker& sphere_marker = markers.markers[4];\n      sphere_marker.header.frame_id = \"map\";\n      sphere_marker.header.stamp = stamp;\n      sphere_marker.ns = \"loop_close_radius\";\n      sphere_marker.id = 3;\n      sphere_marker.type = visualization_msgs::Marker::SPHERE;\n\n      if(!keyframes.empty()) {\n        Eigen::Vector3d pos = keyframes.back()->node->estimate().translation();\n        sphere_marker.pose.position.x = pos.x();\n        sphere_marker.pose.position.y = pos.y();\n        sphere_marker.pose.position.z = pos.z();\n      }\n      sphere_marker.pose.orientation.w = 1.0;\n      sphere_marker.scale.x = sphere_marker.scale.y = sphere_marker.scale.z = loop_detector->get_distance_thresh() * 2.0;\n\n      sphere_marker.color.r = 1.0;\n      sphere_marker.color.a = 0.3;\n    }\n\n    return markers;\n  }\n\n  /**\n   * @brief dump all data to the current directory\n   * @param req\n   * @param res\n   * @return\n   */\n  bool dump_service(radar_graph_slam::DumpGraphRequest& req, radar_graph_slam::DumpGraphResponse& res) {\n    std::lock_guard<std::mutex> lock(main_thread_mutex);\n\n    std::string directory = req.destination;\n\n    if(directory.empty()) {\n      std::array<char, 64> buffer;\n      buffer.fill(0);\n      time_t rawtime;\n      time(&rawtime);\n      const auto timeinfo = localtime(&rawtime);\n      strftime(buffer.data(), sizeof(buffer), \"%d-%m-%Y %H:%M:%S\", timeinfo);\n    }\n\n    if(!boost::filesystem::is_directory(directory)) {\n      boost::filesystem::create_directory(directory);\n    }\n\n    std::cout << \"all data dumped to:\" << directory << std::endl;\n\n    graph_slam->save(directory + \"/graph.g2o\");\n    for(size_t i = 0; i < keyframes.size(); i++) {\n      std::stringstream sst;\n      sst << boost::format(\"%s/%06d\") % directory % i;\n\n      keyframes[i]->save(sst.str());\n    }\n\n    if(zero_utm) {\n      std::ofstream zero_utm_ofs(directory + \"/zero_utm\");\n      zero_utm_ofs << boost::format(\"%.6f %.6f %.6f\") % zero_utm->x() % zero_utm->y() % zero_utm->z() << std::endl;\n    }\n\n    std::ofstream ofs(directory + \"/special_nodes.csv\");\n    ofs << \"anchor_node \" << (anchor_node == nullptr ? -1 : anchor_node->id()) << std::endl;\n    ofs << \"anchor_edge \" << (anchor_edge == nullptr ? -1 : anchor_edge->id()) << std::endl;\n    ofs << \"floor_node \" << (floor_plane_node == nullptr ? -1 : floor_plane_node->id()) << std::endl;\n\n    res.success = true;\n    return true;\n  }\n\n  /**\n   * @brief save map data as pcd\n   * @param req\n   * @param res\n   * @return\n   */\n  bool save_map_service(radar_graph_slam::SaveMapRequest& req, radar_graph_slam::SaveMapResponse& res) {\n    std::vector<KeyFrameSnapshot::Ptr> snapshot;\n\n    keyframes_snapshot_mutex.lock();\n    snapshot = keyframes_snapshot;\n    keyframes_snapshot_mutex.unlock();\n\n    auto cloud = map_cloud_generator->generate(snapshot, req.resolution);\n    if(!cloud) {\n      res.success = false;\n      return true;\n    }\n\n    if(zero_utm && req.utm) {\n      for(auto& pt : cloud->points) {\n        pt.getVector3fMap() += (*zero_utm).cast<float>();\n      }\n    }\n\n    cloud->header.frame_id = mapFrame;\n    cloud->header.stamp = snapshot.back()->cloud->header.stamp;\n\n    if(zero_utm) {\n      std::ofstream ofs(req.destination + \".utm\");\n      ofs << boost::format(\"%.6f %.6f %.6f\") % zero_utm->x() % zero_utm->y() % zero_utm->z() << std::endl;\n    }\n\n    int ret = pcl::io::savePCDFileBinary(req.destination, *cloud);\n    res.success = ret == 0;\n\n    return true;\n  }\n\n  void nmea_callback(const nmea_msgs::SentenceConstPtr& nmea_msg) {\n    GPRMC grmc = nmea_parser->parse(nmea_msg->sentence);\n    if(grmc.status != 'A')\n      return;\n    geographic_msgs::GeoPointStampedPtr gps_msg(new geographic_msgs::GeoPointStamped());\n    gps_msg->header = nmea_msg->header;\n    gps_msg->position.latitude = grmc.latitude;\n    gps_msg->position.longitude = grmc.longitude;\n    gps_msg->position.altitude = NAN;\n    gps_callback(gps_msg);\n  }\n\n  void navsat_callback(const sensor_msgs::NavSatFixConstPtr& navsat_msg) {\n    sensor_msgs::NavSatFix gps_msg;\n    gps_msg.header = navsat_msg->header;\n    gps_msg.latitude = navsat_msg->latitude;\n    gps_msg.longitude = navsat_msg->longitude;\n    gps_msg.altitude = navsat_msg->altitude;\n    gps_msg.position_covariance = navsat_msg->position_covariance;\n    gps_msg.position_covariance_type = navsat_msg->position_covariance_type;\n    gps_msg.status = navsat_msg->status;\n    gps_navsat_queue.push_back(gps_msg);\n  }\n\n  /**\n   * @brief received gps data is added to #gps_queue_\n   * @param gps_msg\n   */\n  void gps_callback(const geographic_msgs::GeoPointStampedPtr& gps_msg) {\n    std::lock_guard<std::mutex> lock(gps_queue_mutex);\n    gps_msg->header.stamp += ros::Duration(gps_time_offset);\n    gps_geopoint_queue.push_back(gps_msg);\n  }\n\n  /**\n   * @brief\n   * @return\n   */\n  bool flush_gps_queue() {\n    std::lock_guard<std::mutex> lock(gps_queue_mutex);\n\n    if(keyframes.empty() || gps_navsat_queue.empty()) {\n      return false;\n    }\n    \n    bool updated = false;\n    auto gps_cursor = gps_navsat_queue.begin();\n\n    for(auto& keyframe : keyframes) {\n      if (keyframe->index - last_gps_edge_index < gps_edge_intervals) continue;\n      if (keyframe->stamp > gps_navsat_queue.back().header.stamp) {\n        break;\n      }\n      if (keyframe->stamp < (*gps_cursor).header.stamp || keyframe->utm_coord) {\n        continue;\n      }\n      // find the gps data which is closest to the keyframe_\n      auto closest_gps = gps_cursor;\n      for(auto gps = gps_cursor; gps != gps_navsat_queue.end(); gps++) {\n        auto dt = ((*closest_gps).header.stamp - keyframe->stamp).toSec();\n        auto dt2 = ((*gps).header.stamp - keyframe->stamp).toSec();\n        if(std::abs(dt) < std::abs(dt2)) {\n          break;\n        }\n        closest_gps = gps;\n      }\n      // if the time residual between the gps and keyframe_ is too large, skip it\n      gps_cursor = closest_gps;\n      if(0.2 < std::abs(((*closest_gps).header.stamp - keyframe->stamp).toSec())) {\n        continue;\n      }\n\n      // convert (latitude, longitude, altitude) -> (easting, northing, altitude) in UTM coordinate\n      geographic_msgs::GeoPoint gps_geopoint;\n      gps_geopoint.altitude = (*closest_gps).altitude;\n      gps_geopoint.latitude = (*closest_gps).latitude;\n      gps_geopoint.longitude = (*closest_gps).longitude;\n      geodesy::UTMPoint utm;\n      geodesy::fromMsg(gps_geopoint, utm); \n      Eigen::Vector3d xyz(utm.easting, utm.northing, utm.altitude);\n      double cov_x = (*closest_gps).position_covariance.at(0);\n      double cov_y = (*closest_gps).position_covariance.at(4);\n      double cov_z = (*closest_gps).position_covariance.at(8);\n      if (cov_x > max_gps_edge_stddev_xy || cov_y > max_gps_edge_stddev_xy || cov_z > max_gps_edge_stddev_z)\n        continue;\n\n      // the first gps data position will be the origin of the map\n      // if(!zero_utm) {\n      //   zero_utm = xyz;\n      // }\n      // xyz -= (*zero_utm);\n      keyframe->utm_coord = xyz;\n      Eigen::Vector3d world_coordinate = (utm_to_world * Eigen::Vector4d(utm.easting, utm.northing, utm.altitude, 1)).head(3);\n      Eigen::Vector3d trans_err = keyframe->node->estimate().translation() - world_coordinate;\n      if (trans_err.norm() < 5.0) continue;\n      //********** G2O Edge ***********\n      g2o::OptimizableGraph::Edge* edge;\n      if(std::isnan(world_coordinate.z())) {\n        Eigen::Matrix2d information_matrix = Eigen::Matrix2d::Identity() / cov_x;\n        edge = graph_slam->add_se3_prior_xy_edge(keyframe->node, world_coordinate.head<2>(), information_matrix);\n      } else {\n        Eigen::Matrix3d information_matrix = Eigen::Matrix3d::Identity();\n        information_matrix(0, 0) /= cov_x;\n        information_matrix(1, 1) /= cov_y;\n        information_matrix(2, 2) /= cov_z;\n        edge = graph_slam->add_se3_prior_xyz_edge(keyframe->node, world_coordinate, information_matrix);\n      }\n      graph_slam->add_robust_kernel(edge, private_nh.param<std::string>(\"gps_edge_robust_kernel\", \"NONE\"), private_nh.param<double>(\"gps_edge_robust_kernel_size\", 1.0));\n\n      last_gps_edge_index = keyframe->index;\n      updated = true;\n    }\n    \n    auto remove_loc = std::upper_bound(gps_navsat_queue.begin(), gps_navsat_queue.end(), keyframes.back()->stamp, [=](const ros::Time& stamp, const sensor_msgs::NavSatFix& geopoint) { return stamp < geopoint.header.stamp; });\n    gps_navsat_queue.erase(gps_navsat_queue.begin(), remove_loc);\n    \n    return updated;\n  }\n  \n  void command_callback(const std_msgs::String& str_msg) {\n    if (str_msg.data == \"output_aftmapped\") {\n      ofstream fout;\n      fout.open(\"/home/zhuge/stamped_pose_graph_estimate.txt\", ios::out);\n      fout << \"# timestamp tx ty tz qx qy qz qw\" << endl;\n      fout.setf(ios::fixed, ios::floatfield);  // fixed mode，float\n      fout.precision(8);  // Set precision 8\n      for(size_t i = 0; i < keyframes.size(); i++) {\n        Eigen::Vector3d pos_ = keyframes[i]->node->estimate().translation();\n        Eigen::Matrix3d rot_ = keyframes[i]->node->estimate().rotation();\n        Eigen::Quaterniond quat_(rot_);\n        double timestamp = keyframes[i]->stamp.toSec();\n        double tx = pos_(0), ty = pos_(1), tz = pos_(2);\n        double qx = quat_.x(), qy = quat_.y(), qz = quat_.z(), qw = quat_.w();\n\n        fout << timestamp << \" \"\n          << tx << \" \" << ty << \" \" << tz << \" \"\n          << qx << \" \" << qy << \" \" << qz << \" \" << qw << endl;\n      }\n      fout.close();\n      ROS_INFO(\"Optimized edges have been output!\");\n    }\n    else if (str_msg.data == \"time\") {\n      if (loop_detector->pf_time.size() > 0) {\n        std::sort(loop_detector->pf_time.begin(), loop_detector->pf_time.end());\n        double median = loop_detector->pf_time.at(floor((double)loop_detector->pf_time.size() / 2));\n        cout << \"Pre-filtering Matching time cost (median): \" << median << endl;\n      }\n      if (loop_detector->sc_time.size() > 0) {\n        std::sort(loop_detector->sc_time.begin(), loop_detector->sc_time.end());\n        double median = loop_detector->sc_time.at(floor((double)loop_detector->sc_time.size() / 2));\n        cout << \"Scan Context time cost (median): \" << median << endl;\n      }\n      if (loop_detector->oc_time.size() > 0) {\n        std::sort(loop_detector->oc_time.begin(), loop_detector->oc_time.end());\n        double median = loop_detector->oc_time.at(floor((double)loop_detector->oc_time.size() / 2));\n        cout << \"Odometry Check time cost (median): \" << median << endl;\n      }\n      if (opt_time.size() > 0) {\n        std::sort(opt_time.begin(), opt_time.end());\n        double median = opt_time.at(floor((double)opt_time.size() / 2));\n        cout << \"Optimization time cost (median): \" << median << endl;\n      }\n    }\n  }\n\n\nprivate:\n  // ROS\n  ros::NodeHandle nh;\n  ros::NodeHandle mt_nh;\n  ros::NodeHandle private_nh;\n  ros::WallTimer optimization_timer;\n  ros::WallTimer map_publish_timer;\n\n  std::unique_ptr<message_filters::Subscriber<nav_msgs::Odometry>> odom_sub;\n  std::unique_ptr<message_filters::Subscriber<sensor_msgs::PointCloud2>> cloud_sub;\n  std::unique_ptr<message_filters::Synchronizer<ApproxSyncPolicy>> sync;\n\n  ros::Subscriber barometer_sub;\n  ros::Subscriber gps_sub;\n  ros::Subscriber nmea_sub;\n  ros::Subscriber navsat_sub;\n\n  ros::Subscriber imu_odom_sub;\n  ros::Subscriber imu_sub;\n  ros::Subscriber command_sub;\n\n  ros::Publisher imu_odom_pub;\n  ros::Publisher markers_pub;\n\n  std::mutex trans_odom2map_mutex;\n  Eigen::Matrix4d trans_odom2map; // keyframe->node->estimate() * keyframe->odom.inverse();\n  Eigen::Isometry3d trans_aftmapped;  // Odometry from /map to /base_link\n  Eigen::Isometry3d trans_aftmapped_incremental;\n  ros::Publisher odom2base_pub;\n  ros::Publisher aftmapped_odom_pub;\n  ros::Publisher aftmapped_odom_incremenral_pub;\n  ros::Publisher odom_frame2frame_pub;\n\n  std::string points_topic;\n  ros::Publisher read_until_pub;\n  ros::Publisher map_points_pub;\n\n  tf::TransformListener tf_listener;\n  tf::TransformBroadcaster map2base_broadcaster; // odom_frame => base_frame\n\n  ros::ServiceServer dump_service_server;\n  ros::ServiceServer save_map_service_server; \n\n  // keyframe queue\n  std::mutex keyframe_queue_mutex;\n  std::deque<KeyFrame::Ptr> keyframe_queue;\n  std::deque<geometry_msgs::TwistStampedConstPtr> twist_queue;\n  std::deque<nav_msgs::OdometryConstPtr> imu_odom_queue;\n  double thisKeyframeTime;\n  double lastKeyframeTime;\n  size_t keyframe_index = 0;\n\n  // IMU / Ego Velocity Integration\n  bool enable_preintegration;\n  double preinteg_orient_stddev;\n  double preinteg_trans_stddev;\n  bool enable_imu_orientation;\n  bool use_egovel_preinteg_trans;\n  Eigen::Matrix4d initial_pose;\n\n  // barometer queue\n  bool enable_barometer;\n  int barometer_edge_type;\n  double barometer_edge_stddev;\n  boost::optional<Eigen::Vector1d> zero_alt;\n  std::mutex barometer_queue_mutex;\n  std::deque<barometer_bmp388::BarometerConstPtr> barometer_queue;\n\n  // gps queue\n  int gps_edge_intervals;\n  int last_gps_edge_index;\n  double gps_time_offset;\n  double gps_edge_stddev_xy;\n  double gps_edge_stddev_z;\n  double max_gps_edge_stddev_xy;\n  double max_gps_edge_stddev_z;\n  boost::optional<Eigen::Vector3d> zero_utm;\n  std::mutex gps_queue_mutex;\n  std::deque<geographic_msgs::GeoPointStampedConstPtr> gps_geopoint_queue;\n  std::deque<sensor_msgs::NavSatFix>           gps_navsat_queue;\n  Eigen::Matrix4d utm_to_world;\n  std::string dataset_name;\n\n  // Marker coefficients\n  bool show_sphere;\n\n  // for map cloud generation\n  std::atomic_bool graph_updated;\n  double map_cloud_resolution;\n  std::mutex keyframes_snapshot_mutex;\n  std::vector<KeyFrameSnapshot::Ptr> keyframes_snapshot;\n  std::unique_ptr<MapCloudGenerator> map_cloud_generator;\n\n  // graph slam\n  // all the below members must be accessed after locking main_thread_mutex\n  std::mutex main_thread_mutex;\n\n  int max_keyframes_per_update;\n  //  Used for Loop Closure detection source, \n  //  pushed form keyframe_queue at \"flush_keyframe_queue()\", \n  //  inserted to \"keyframes_\" before optimization\n  std::deque<KeyFrame::Ptr> new_keyframes;\n  //  Previous keyframes_\n  std::vector<KeyFrame::Ptr> keyframes;\n  std::unordered_map<ros::Time, KeyFrame::Ptr, RosTimeHash> keyframe_hash;\n  g2o::VertexSE3* anchor_node;\n  g2o::EdgeSE3* anchor_edge;\n  g2o::VertexPlane* floor_plane_node;\n\n  std::unique_ptr<GraphSLAM> graph_slam;\n  std::unique_ptr<LoopDetector> loop_detector;\n  std::unique_ptr<KeyframeUpdater> keyframe_updater;\n  std::unique_ptr<NmeaSentenceParser> nmea_parser;\n  std::unique_ptr<InformationMatrixCalculator> inf_calclator;\n\n  // Registration Method\n  pcl::Registration<PointT, PointT>::Ptr registration;\n  pcl::KdTreeFLANN<PointT>::Ptr kdtreeHistoryKeyPoses;\n\n  std::vector<double> opt_time;\n};\n\n}  // namespace radar_graph_slam\n\nPLUGINLIB_EXPORT_CLASS(radar_graph_slam::RadarGraphSlamNodelet, nodelet::Nodelet)\n"
  },
  {
    "path": "apps/scan_matching_odometry_nodelet.cpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#include <ctime>\n#include <chrono>\n#include <mutex>\n#include <atomic>\n#include <memory>\n#include <iomanip>\n#include <iostream>\n#include <cmath>\n#include <unordered_map>\n#include <boost/format.hpp>\n#include <boost/thread.hpp>\n#include <boost/filesystem.hpp>\n#include <boost/algorithm/string.hpp>\n\n#include <ros/ros.h>\n#include <ros/time.h>\n#include <ros/duration.h>\n\n#include <tf_conversions/tf_eigen.h>\n#include <tf/transform_listener.h>\n#include <tf/transform_broadcaster.h>\n#include <message_filters/subscriber.h>\n#include <message_filters/time_synchronizer.h>\n#include <message_filters/synchronizer.h>\n#include <message_filters/sync_policies/approximate_time.h>\n\n#include <std_msgs/String.h>\n#include <std_msgs/Time.h>\n#include <nav_msgs/Odometry.h>\n#include <sensor_msgs/PointCloud2.h>\n#include <sensor_msgs/Imu.h>\n#include <geometry_msgs/TransformStamped.h>\n#include <geometry_msgs/PoseWithCovarianceStamped.h>\n#include <geometry_msgs/TwistWithCovarianceStamped.h>\n\n#include <nodelet/nodelet.h>\n#include <pluginlib/class_list_macros.h>\n\n#include <pcl_ros/point_cloud.h>\n#include <pcl/filters/voxel_grid.h>\n#include <pcl/filters/passthrough.h>\n#include <pcl/filters/approximate_voxel_grid.h>\n#include <pcl/octree/octree_search.h>\n\n#include <Eigen/Dense>\n\n#include <radar_graph_slam/ros_utils.hpp>\n#include <radar_graph_slam/registrations.hpp>\n#include <radar_graph_slam/ScanMatchingStatus.h>\n#include <radar_graph_slam/keyframe.hpp>\n#include <radar_graph_slam/keyframe_updater.hpp>\n#include <radar_graph_slam/graph_slam.hpp>\n#include <radar_graph_slam/information_matrix_calculator.hpp>\n\n#include \"utility_radar.h\"\n\nusing namespace std;\n\nnamespace radar_graph_slam {\n\nclass ScanMatchingOdometryNodelet : public nodelet::Nodelet, public ParamServer {\npublic:\n  typedef pcl::PointXYZI PointT;\n  typedef message_filters::sync_policies::ApproximateTime<geometry_msgs::TwistWithCovarianceStamped, sensor_msgs::PointCloud2> ApproxSyncPolicy;\n  // typedef message_filters::sync_policies::ApproximateTime<sensor_msgs::PointCloud2, geometry_msgs::TransformStamped> ApproxSyncPolicy2;\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n\n  ScanMatchingOdometryNodelet() {}\n  virtual ~ScanMatchingOdometryNodelet() {}\n\n  virtual void onInit() {\n    NODELET_DEBUG(\"initializing scan_matching_odometry_nodelet...\");\n    nh = getNodeHandle();\n    mt_nh = getMTNodeHandle();\n    private_nh = getPrivateNodeHandle();\n\n    initialize_params(); // this\n\n    if(private_nh.param<bool>(\"enable_imu_frontend\", false)) {\n      msf_pose_sub = nh.subscribe<geometry_msgs::PoseWithCovarianceStamped>(\"/msf_core/pose\", 1, boost::bind(&ScanMatchingOdometryNodelet::msf_pose_callback, this, _1, false));\n      msf_pose_after_update_sub = nh.subscribe<geometry_msgs::PoseWithCovarianceStamped>(\"/msf_core/pose_after_update\", 1, boost::bind(&ScanMatchingOdometryNodelet::msf_pose_callback, this, _1, true));\n    }\n    //******** Subscribers **********\n    ego_vel_sub.reset(new message_filters::Subscriber<geometry_msgs::TwistWithCovarianceStamped>(mt_nh, \"/eagle_data/twist\", 256));\n    points_sub.reset(new message_filters::Subscriber<sensor_msgs::PointCloud2>(mt_nh, \"/filtered_points\", 32));\n    sync.reset(new message_filters::Synchronizer<ApproxSyncPolicy>(ApproxSyncPolicy(32), *ego_vel_sub, *points_sub));\n    sync->registerCallback(boost::bind(&ScanMatchingOdometryNodelet::pointcloud_callback, this, _1, _2));\n    imu_sub = nh.subscribe(\"/imu\", 1024, &ScanMatchingOdometryNodelet::imu_callback, this);\n    command_sub = nh.subscribe(\"/command\", 10, &ScanMatchingOdometryNodelet::command_callback, this);\n\n    //******** Publishers **********\n    read_until_pub = nh.advertise<std_msgs::Header>(\"/scan_matching_odometry/read_until\", 32);\n    // Odometry of Radar scan-matching_\n    odom_pub = nh.advertise<nav_msgs::Odometry>(odomTopic, 32);\n    // Transformation of Radar scan-matching_\n    trans_pub = nh.advertise<geometry_msgs::TransformStamped>(\"/scan_matching_odometry/transform\", 32);\n    status_pub = private_nh.advertise<ScanMatchingStatus>(\"/scan_matching_odometry/status\", 8);\n    aligned_points_pub = nh.advertise<sensor_msgs::PointCloud2>(\"/aligned_points\", 32);\n    submap_pub = nh.advertise<sensor_msgs::PointCloud2>(\"/radar_graph_slam/submap\", 2);\n  }\n\nprivate:\n  /**\n   * @brief initialize parameters\n   */\n  void initialize_params() {\n    auto& pnh = private_nh;\n    points_topic = pnh.param<std::string>(\"points_topic\", \"/radar_enhanced_pcl\");\n    use_ego_vel = pnh.param<bool>(\"use_ego_vel\", false);\n\n    // The minimum tranlational distance and rotation angle between keyframes_.\n    // If this value is zero, frames are always compared with the previous frame\n    keyframe_delta_trans = pnh.param<double>(\"keyframe_delta_trans\", 0.25);\n    keyframe_delta_angle = pnh.param<double>(\"keyframe_delta_angle\", 0.15);\n    keyframe_delta_time = pnh.param<double>(\"keyframe_delta_time\", 1.0);\n\n    // Registration validation by thresholding\n    enable_transform_thresholding = pnh.param<bool>(\"enable_transform_thresholding\", false);\n    enable_imu_thresholding = pnh.param<bool>(\"enable_imu_thresholding\", false);\n    max_acceptable_trans = pnh.param<double>(\"max_acceptable_trans\", 1.0);\n    max_acceptable_angle = pnh.param<double>(\"max_acceptable_angle\", 1.0);\n    max_diff_trans = pnh.param<double>(\"max_diff_trans\", 1.0);\n    max_diff_angle = pnh.param<double>(\"max_diff_angle\", 1.0);\n    max_egovel_cum = pnh.param<double>(\"max_egovel_cum\", 1.0);\n\n    map_cloud_resolution = pnh.param<double>(\"map_cloud_resolution\", 0.05);\n    keyframe_updater.reset(new KeyframeUpdater(pnh));\n\n    enable_scan_to_map = pnh.param<bool>(\"enable_scan_to_map\", false);\n    max_submap_frames = pnh.param<int>(\"max_submap_frames\", 5);\n\n    enable_imu_fusion = private_nh.param<bool>(\"enable_imu_fusion\", false);\n    imu_debug_out = private_nh.param<bool>(\"imu_debug_out\", false);\n    cout << \"enable_imu_fusion = \" << enable_imu_fusion << endl;\n    imu_fusion_ratio = private_nh.param<double>(\"imu_fusion_ratio\", 0.1);\n\n    // graph_slam.reset(new GraphSLAM(pnh.param<std::string>(\"g2o_solver_type\", \"lm_var\")));\n\n    // select a downsample method (VOXELGRID, APPROX_VOXELGRID, NONE)\n    std::string downsample_method = pnh.param<std::string>(\"downsample_method\", \"VOXELGRID\");\n    double downsample_resolution = pnh.param<double>(\"downsample_resolution\", 0.1);\n    if(downsample_method == \"VOXELGRID\") {\n      std::cout << \"downsample: VOXELGRID \" << downsample_resolution << std::endl;\n      auto voxelgrid = new pcl::VoxelGrid<PointT>();\n      voxelgrid->setLeafSize(downsample_resolution, downsample_resolution, downsample_resolution);\n      downsample_filter.reset(voxelgrid);\n    } else if(downsample_method == \"APPROX_VOXELGRID\") {\n      std::cout << \"downsample: APPROX_VOXELGRID \" << downsample_resolution << std::endl;\n      pcl::ApproximateVoxelGrid<PointT>::Ptr approx_voxelgrid(new pcl::ApproximateVoxelGrid<PointT>());\n      approx_voxelgrid->setLeafSize(downsample_resolution, downsample_resolution, downsample_resolution);\n      downsample_filter = approx_voxelgrid;\n    } else {\n      if(downsample_method != \"NONE\") {\n        std::cerr << \"warning: unknown downsampling type (\" << downsample_method << \")\" << std::endl;\n        std::cerr << \"       : use passthrough filter\" << std::endl;\n      }\n      std::cout << \"downsample: NONE\" << std::endl;\n      pcl::PassThrough<PointT>::Ptr passthrough(new pcl::PassThrough<PointT>());\n      downsample_filter = passthrough;\n    }\n    registration_s2s = select_registration_method(pnh);\n    registration_s2m = select_registration_method(pnh);\n  }\n\n  void imu_callback(const sensor_msgs::ImuConstPtr& imu_msg) {\n    \n    Eigen::Quaterniond imu_quat_from(imu_msg->orientation.w, imu_msg->orientation.x, imu_msg->orientation.y, imu_msg->orientation.z);\n    Eigen::Quaterniond imu_quat_deskew = imu_quat_from * extQRPY;\n    imu_quat_deskew.normalize();\n\n    double roll, pitch, yaw;\n    // tf::quaternionMsgToTF(imu_odom_msg->orientation, orientation);\n    tf::Quaternion orientation = tf::Quaternion(imu_quat_deskew.x(),imu_quat_deskew.y(),imu_quat_deskew.z(),imu_quat_deskew.w());\n    tf::Matrix3x3(orientation).getRPY(roll, pitch, yaw);\n    imuPointerLast = (imuPointerLast + 1) % imuQueLength;\n    imuTime[imuPointerLast] = imu_msg->header.stamp.toSec();\n    imuRoll[imuPointerLast] = roll;\n    imuPitch[imuPointerLast] = pitch;\n    // cout << \"get imu rp: \" << roll << \" \" << pitch << endl;\n\n    sensor_msgs::ImuPtr imu(new sensor_msgs::Imu);\n    imu->header = imu_msg->header;\n    imu->angular_velocity = imu_msg->angular_velocity; imu->linear_acceleration = imu_msg->linear_acceleration;\n    imu->angular_velocity_covariance = imu_msg->angular_velocity_covariance;\n    imu->linear_acceleration_covariance, imu_msg->linear_acceleration_covariance;\n    imu->orientation_covariance = imu_msg->orientation_covariance;\n    imu->orientation.w=imu_quat_deskew.w(); imu->orientation.x = imu_quat_deskew.x(); imu->orientation.y = imu_quat_deskew.y(); imu->orientation.z = imu_quat_deskew.z();\n    {\n      std::lock_guard<std::mutex> lock(imu_queue_mutex);\n      imu_queue.push_back(imu);\n    }\n\n    static int cnt = 0;\n    if(cnt == 0) {\n      geometry_msgs::Quaternion imuQuat = tf::createQuaternionMsgFromRollPitchYaw(roll, pitch, 0);\n      global_orient_matrix = Eigen::Quaterniond(imuQuat.w, imuQuat.x, imuQuat.y, imuQuat.z).toRotationMatrix();\n      ROS_INFO_STREAM(\"Initial IMU euler angles (RPY): \"\n            << RAD2DEG(roll) << \", \" << RAD2DEG(pitch) << \", \" << RAD2DEG(yaw));\n      cnt = 1;\n    }\n    \n  }\n\n  bool flush_imu_queue() {\n    std::lock_guard<std::mutex> lock(imu_queue_mutex);\n    if(keyframes.empty() || imu_queue.empty()) {\n      return false;\n    }\n    bool updated = false;\n    auto imu_cursor = imu_queue.begin();\n\n    for(size_t i=0; i < keyframes.size(); i++) {\n      auto keyframe = keyframes.at(i);\n      if(keyframe->stamp < (*imu_cursor)->header.stamp) {\n        continue;\n      }\n      if(keyframe->stamp > imu_queue.back()->header.stamp) {\n        break;\n      }\n      // find the imu data which is closest to the keyframe_\n      auto closest_imu = imu_cursor;\n      for(auto imu = imu_cursor; imu != imu_queue.end(); imu++) {\n        auto dt = ((*closest_imu)->header.stamp - keyframe->stamp).toSec();\n        auto dt2 = ((*imu)->header.stamp - keyframe->stamp).toSec();\n        if(std::abs(dt) < std::abs(dt2)) {\n          break;\n        }\n        closest_imu = imu;\n      }\n      // if the time residual between the imu and keyframe_ is too large, skip it\n      imu_cursor = closest_imu;\n      if(0.2 < std::abs(((*closest_imu)->header.stamp - keyframe->stamp).toSec())) {\n        continue;\n      }\n      sensor_msgs::Imu imu_;\n      imu_.header = (*closest_imu)->header; imu_.orientation = (*closest_imu)->orientation;\n      imu_.angular_velocity = (*closest_imu)->angular_velocity; imu_.linear_acceleration = (*closest_imu)->linear_acceleration;\n      imu_.angular_velocity_covariance = (*closest_imu)->angular_velocity_covariance;\n      imu_.linear_acceleration_covariance = (*closest_imu)->linear_acceleration_covariance;\n      imu_.orientation_covariance = (*closest_imu)->orientation_covariance;\n      keyframe->imu = imu_;\n      updated = true;\n    }\n    auto remove_loc = std::upper_bound(imu_queue.begin(), imu_queue.end(), keyframes.back()->stamp, [=](const ros::Time& stamp, const sensor_msgs::ImuConstPtr& imupoint) { return stamp < imupoint->header.stamp; });\n    imu_queue.erase(imu_queue.begin(), remove_loc);\n    return updated;\n  }\n\n  std::pair<bool, sensor_msgs::Imu> get_closest_imu(ros::Time frame_stamp) {\n    sensor_msgs::Imu imu_;\n    std::pair<bool, sensor_msgs::Imu> false_result {false, imu_};\n    if(keyframes.empty() || imu_queue.empty())\n      return false_result;\n    bool updated = false;\n    auto imu_cursor = imu_queue.begin();\n    \n    // find the imu data which is closest to the keyframe_\n    auto closest_imu = imu_cursor;\n    for(auto imu = imu_cursor; imu != imu_queue.end(); imu++) {\n      auto dt = ((*closest_imu)->header.stamp - frame_stamp).toSec();\n      auto dt2 = ((*imu)->header.stamp - frame_stamp).toSec();\n      if(std::abs(dt) < std::abs(dt2)) {\n        break;\n      }\n      closest_imu = imu;\n    }\n    // if the time residual between the imu and keyframe_ is too large, skip it\n    imu_cursor = closest_imu;\n    if(0.2 < std::abs(((*closest_imu)->header.stamp - frame_stamp).toSec()))\n      return false_result;\n\n    imu_.header = (*closest_imu)->header; imu_.orientation = (*closest_imu)->orientation;\n    imu_.angular_velocity = (*closest_imu)->angular_velocity; imu_.linear_acceleration = (*closest_imu)->linear_acceleration;\n    imu_.angular_velocity_covariance = (*closest_imu)->angular_velocity_covariance; \n    imu_.linear_acceleration_covariance = (*closest_imu)->linear_acceleration_covariance;\n    imu_.orientation_covariance = (*closest_imu)->orientation_covariance;\n\n    updated = true;\n    // cout << (*closest_imu)->orientation <<endl;\n    std::pair<bool, sensor_msgs::Imu> result {updated, imu_};\n    return result;\n  }\n\n\n  void transformUpdate(Eigen::Matrix4d& odom_to_update) // IMU\n  {\n\t\tif (imuPointerLast >= 0) \n    {\n      // cout << \"    \";\n      float imuRollLast = 0, imuPitchLast = 0;\n      while (imuPointerFront != imuPointerLast) {\n        if (timeLaserOdometry + scanPeriod < imuTime[imuPointerFront]) {\n          break;\n        }\n        imuPointerFront = (imuPointerFront + 1) % imuQueLength;\n      }\n      cout << \"    \";\n      if (timeLaserOdometry + scanPeriod > imuTime[imuPointerFront]) {\n        imuRollLast = imuRoll[imuPointerFront];\n        imuPitchLast = imuPitch[imuPointerFront];\n        cout << \"    \";\n      }\n      else {\n        cout << \"    \";\n        int imuPointerBack = (imuPointerFront + imuQueLength - 1) % imuQueLength;\n        float ratioFront = (timeLaserOdometry + scanPeriod - imuTime[imuPointerBack])\n                          / (imuTime[imuPointerFront] - imuTime[imuPointerBack]);\n        float ratioBack = (imuTime[imuPointerFront] - timeLaserOdometry - scanPeriod) \n                        / (imuTime[imuPointerFront] - imuTime[imuPointerBack]);\n\n        imuRollLast = imuRoll[imuPointerFront] * ratioFront + imuRoll[imuPointerBack] * ratioBack;\n        imuPitchLast = imuPitch[imuPointerFront] * ratioFront + imuPitch[imuPointerBack] * ratioBack;\n      }\n      \n      Eigen::Matrix3d matr = odom_to_update.block<3, 3>(0, 0);\n      // Eigen::Vector3d xyz = odom_to_update.block<3, 1>(0, 3);\n      Eigen::Vector3d ypr_odom = R2ypr(matr.block<3,3>(0,0));\n      geometry_msgs::Quaternion imuQuat = tf::createQuaternionMsgFromRollPitchYaw(imuRollLast, imuPitchLast, ypr_odom(0));\n      Eigen::Matrix3d imu_rot = Eigen::Matrix3d(Eigen::Quaterniond(imuQuat.w, imuQuat.x, imuQuat.y, imuQuat.z));\n      Eigen::Vector3d ypr_imu = R2ypr(imu_rot);\n      // IMU orientation transformed from world coordinate to map coordinate\n      Eigen::Matrix3d imu_rot_transed = global_orient_matrix.inverse() * imu_rot;\n      Eigen::Vector3d ypr_imu_trans = R2ypr(imu_rot_transed);\n      double& yaw_ = ypr_odom(0);\n      double pitch_fused = (1 - imu_fusion_ratio) * ypr_odom(1) + imu_fusion_ratio * ypr_imu_trans(1);\n      double roll_fused = (1 - imu_fusion_ratio) * ypr_odom(2) + imu_fusion_ratio * ypr_imu_trans(2);\n      geometry_msgs::Quaternion rosQuat = tf::createQuaternionMsgFromRollPitchYaw(roll_fused, pitch_fused, yaw_);\n      Eigen::Quaterniond quat_updated = Eigen::Quaterniond(rosQuat.w, rosQuat.x, rosQuat.y, rosQuat.z);\n      odom_to_update.block<3, 3>(0, 0) = quat_updated.toRotationMatrix();\n\n      if (imu_debug_out)\n        cout << \"IMU rp: \" << RAD2DEG(ypr_imu(2)) << \" \" << RAD2DEG(ypr_imu(1))\n            << \". IMU transed rp: \" << RAD2DEG(ypr_imu_trans(2)) << \" \" << RAD2DEG(ypr_imu_trans(1))\n            // << \". Odom rp: \" << RAD2DEG(ypr_odom(2)) << \" \" << RAD2DEG(ypr_odom(1))\n            // << \". Updated rp: \" << RAD2DEG(roll_fused) << \" \" << RAD2DEG(pitch_fused)\n            // << \". Roll Pitch increment: \" << RAD2DEG(roll_fused - ypr_odom(2)) << \" \" << RAD2DEG(pitch_fused - ypr_odom(1)) \n            << endl;\n\t\t}\n  }\n\n  /**\n   * @brief callback for point clouds\n   * @param cloud_msg  point cloud msg\n   */\n  void pointcloud_callback(const geometry_msgs::TwistWithCovarianceStampedConstPtr& twistMsg, const sensor_msgs::PointCloud2ConstPtr& cloud_msg) {\n    if(!ros::ok()) {\n      return;\n    }\n    timeLaserOdometry = cloud_msg->header.stamp.toSec();\n    double this_cloud_time = cloud_msg->header.stamp.toSec();\n    static double last_cloud_time = this_cloud_time;\n\n    double dt = this_cloud_time - last_cloud_time;\n    double egovel_cum_x = twistMsg->twist.twist.linear.x * dt;\n    double egovel_cum_y = twistMsg->twist.twist.linear.y * dt;\n    double egovel_cum_z = twistMsg->twist.twist.linear.z * dt;\n    // If too large, set 0\n    if (pow(egovel_cum_x,2)+pow(egovel_cum_y,2)+pow(egovel_cum_z,2) > pow(max_egovel_cum, 2));\n    else egovel_cum.block<3, 1>(0, 3) = Eigen::Vector3d(egovel_cum_x, egovel_cum_y, egovel_cum_z);\n    \n    last_cloud_time = this_cloud_time;\n\n    pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>());\n    pcl::fromROSMsg(*cloud_msg, *cloud);\n\n    // Matching\n    Eigen::Matrix4d pose = matching(cloud_msg->header.stamp, cloud);\n    geometry_msgs::TwistWithCovariance twist = twistMsg->twist;\n    // publish map to odom frame\n    publish_odometry(cloud_msg->header.stamp, mapFrame, odometryFrame, pose, twist);\n\n    // In offline estimation, point clouds will be supplied until the published time\n    std_msgs::HeaderPtr read_until(new std_msgs::Header());\n    read_until->frame_id = points_topic;\n    read_until->stamp = cloud_msg->header.stamp + ros::Duration(1, 0);\n    read_until_pub.publish(read_until);\n\n    read_until->frame_id = \"/filtered_points\";\n    read_until_pub.publish(read_until);\n  }\n\n\n  void msf_pose_callback(const geometry_msgs::PoseWithCovarianceStampedConstPtr& pose_msg, bool after_update) {\n    if(after_update) {\n      msf_pose_after_update = pose_msg;\n    } else {\n      msf_pose = pose_msg;\n    }\n  }\n\n  /**\n   * @brief downsample a point cloud\n   * @param cloud  input cloud\n   * @return downsampled point cloud\n   */\n  pcl::PointCloud<PointT>::ConstPtr downsample(const pcl::PointCloud<PointT>::ConstPtr& cloud) const {\n    if(!downsample_filter) {\n      return cloud;\n    }\n\n    pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>());\n    downsample_filter->setInputCloud(cloud);\n    downsample_filter->filter(*filtered);\n\n    return filtered;\n  }\n\n  /**\n   * @brief estimate the relative pose between an input cloud and a keyframe_ cloud\n   * @param stamp  the timestamp of the input cloud\n   * @param cloud  the input cloud\n   * @return the relative pose between the input cloud and the keyframe_ cloud\n   */\n  Eigen::Matrix4d matching(const ros::Time& stamp, const pcl::PointCloud<PointT>::ConstPtr& cloud) {\n    if(!keyframe_cloud_s2s) {\n      prev_time = ros::Time();\n      prev_trans_s2s.setIdentity();\n      keyframe_pose_s2s.setIdentity();\n      keyframe_stamp = stamp;\n      keyframe_cloud_s2s = cloud;//downsample(cloud);\n      registration_s2s->setInputTarget(keyframe_cloud_s2s); // Scan-to-scan\n      if (enable_scan_to_map){\n        prev_trans_s2m.setIdentity();\n        keyframe_pose_s2m.setIdentity();\n        keyframe_cloud_s2m = cloud;\n        registration_s2m->setInputTarget(keyframe_cloud_s2m);\n      }\n      return Eigen::Matrix4d::Identity();\n    }\n    // auto filtered = downsample(cloud);\n    auto filtered = cloud;\n    // Set Source Cloud\n    registration_s2s->setInputSource(filtered);\n    if (enable_scan_to_map)\n      registration_s2m->setInputSource(filtered);\n\n    std::string msf_source;\n    Eigen::Isometry3d msf_delta = Eigen::Isometry3d::Identity();\n    \n    pcl::PointCloud<PointT>::Ptr aligned(new pcl::PointCloud<PointT>());\n    Eigen::Matrix4d odom_s2s_now;\n    Eigen::Matrix4d odom_s2m_now;\n\n    // **********  Matching  **********\n    Eigen::Matrix4d guess;\n    if (use_ego_vel)\n      guess = prev_trans_s2s * egovel_cum * msf_delta.matrix();\n    else\n      guess = prev_trans_s2s * msf_delta.matrix();\n\n    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();\n    registration_s2s->align(*aligned, guess.cast<float>());\n    chrono::steady_clock::time_point t2 = chrono::steady_clock::now();\n    double time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1).count();\n    s2s_matching_time.push_back(time_used);\n\n    publish_scan_matching_status(stamp, cloud->header.frame_id, aligned, msf_source, msf_delta);\n\n    // If not converged, use last transformation\n    if(!registration_s2s->hasConverged()) {\n      NODELET_INFO_STREAM(\"scan matching_ has not converged!!\");\n      NODELET_INFO_STREAM(\"ignore this frame(\" << stamp << \")\");\n      if (enable_scan_to_map) return keyframe_pose_s2m * prev_trans_s2m;\n      else return keyframe_pose_s2s * prev_trans_s2s;\n    }\n    Eigen::Matrix4d trans_s2s = registration_s2s->getFinalTransformation().cast<double>();\n    odom_s2s_now = keyframe_pose_s2s * trans_s2s;\n\n    Eigen::Matrix4d trans_s2m;\n    if (enable_scan_to_map){\n      registration_s2m->align(*aligned, guess.cast<float>());\n      if(!registration_s2m->hasConverged()) {\n        NODELET_INFO_STREAM(\"scan matching_ has not converged!!\");\n        NODELET_INFO_STREAM(\"ignore this frame(\" << stamp << \")\");\n        return keyframe_pose_s2m * prev_trans_s2m;\n      }\n      trans_s2m = registration_s2m->getFinalTransformation().cast<double>();\n      odom_s2m_now = keyframe_pose_s2m * trans_s2m;\n    }\n\n    // Add abnormal judgment, that is, if the difference between the two frames matching point cloud \n    // transition matrix is too large, it will be discarded\n    bool thresholded = false;\n    if(enable_transform_thresholding) {\n      Eigen::Matrix4d radar_delta;\n      if(enable_scan_to_map) radar_delta = prev_trans_s2m.inverse() * trans_s2m;\n      else radar_delta = prev_trans_s2s.inverse() * trans_s2s;\n      double dx_rd = radar_delta.block<3, 1>(0, 3).norm();\n      // double da_rd = std::acos(Eigen::Quaterniond(radar_delta.block<3, 3>(0, 0)).w())*180/M_PI;\n      Eigen::AngleAxisd rotation_vector;\n      rotation_vector.fromRotationMatrix(radar_delta.block<3, 3>(0, 0));\n      double da_rd = rotation_vector.angle();\n      Eigen::Matrix3d rot_rd = radar_delta.block<3, 3>(0, 0).cast<double>();\n      bool too_large_trans = dx_rd > max_acceptable_trans || da_rd > max_acceptable_angle;\n      double da, dx, delta_rot_imu = 0;\n      Eigen::Matrix3d matrix_rot; Eigen::Vector3d delta_trans_egovel;\n\n      if (enable_imu_thresholding) {\n        // Use IMU orientation to determine whether the matching result is good or not\n        sensor_msgs::Imu frame_imu;\n        Eigen::Matrix3d rot_imu = Eigen::Matrix3d::Identity();\n        auto result = get_closest_imu(stamp);\n        if (result.first) {\n          frame_imu = result.second;\n          Eigen::Quaterniond imu_quat(frame_imu.orientation.w, frame_imu.orientation.x, frame_imu.orientation.y, frame_imu.orientation.z);\n          Eigen::Quaterniond prev_imu_quat(last_frame_imu.orientation.w, last_frame_imu.orientation.x, last_frame_imu.orientation.y, last_frame_imu.orientation.z);\n          rot_imu = (prev_imu_quat.inverse() * imu_quat).toRotationMatrix();\n          Eigen::Vector3d eulerAngle_imu = rot_imu.eulerAngles(0,1,2); // roll pitch yaw\n          Eigen::Vector3d eulerAngle_rd = last_radar_delta.block<3,3>(0,0).eulerAngles(0,1,2);\n          Eigen::AngleAxisd rollAngle(Eigen::AngleAxisd(restrict_rad(eulerAngle_imu(0)),Eigen::Vector3d::UnitX()));\n          Eigen::AngleAxisd pitchAngle(Eigen::AngleAxisd(restrict_rad(eulerAngle_imu(1)),Eigen::Vector3d::UnitY()));\n          Eigen::AngleAxisd yawAngle(Eigen::AngleAxisd(restrict_rad(eulerAngle_rd(2)),Eigen::Vector3d::UnitZ()));\n          matrix_rot = yawAngle * pitchAngle * rollAngle;\n          da = fabs(std::acos(Eigen::Quaterniond(rot_rd.inverse() * rot_imu).w()))*180/M_PI;\n          delta_rot_imu = fabs(std::acos(Eigen::Quaterniond(rot_imu).w()))*180/M_PI;\n          last_frame_imu = frame_imu;\n        }\n        delta_trans_egovel = egovel_cum.block<3,1>(0,3).cast<double>();\n        Eigen::Vector3d delta_trans_radar = radar_delta.block<3,1>(0,3).cast<double>();\n        dx = (delta_trans_egovel - delta_trans_radar).norm();\n\n        if (dx > max_diff_trans || da > max_diff_angle || too_large_trans) {\n          Eigen::Matrix4d mat_est(Eigen::Matrix4d::Identity());\n          mat_est.block<3, 3>(0, 0) = matrix_rot;\n          mat_est.block<3, 1>(0, 3) = delta_trans_egovel;\n          if (too_large_trans) cout << \"Too large transform! \" << dx_rd << \"[m] \" << da_rd << \"[deg] \";\n          cout << \"Difference of Odom and IMU/EgoVel too large \" << dx << \"[m] \" << da << \"[deg] (\" << stamp << \")\" << endl;\n          prev_trans_s2s = prev_trans_s2s * mat_est;\n          thresholded = true;\n          if (enable_scan_to_map){\n            prev_trans_s2m = prev_trans_s2m * mat_est;\n            odom_s2m_now = keyframe_pose_s2m * prev_trans_s2m;\n          }\n          else odom_s2s_now = keyframe_pose_s2s * prev_trans_s2s;\n        }\n      }\n      else {\n        if (too_large_trans) {\n          cout << \"Too large transform!!  \" << dx_rd << \"[m] \" << da_rd << \"[degree]\"<<\n            \" Ignore this frame (\" << stamp << \")\" << endl;\n          prev_trans_s2s = trans_s2s;\n          thresholded = true;\n          if (enable_scan_to_map){\n            prev_trans_s2m = trans_s2m;\n            odom_s2m_now = keyframe_pose_s2m * prev_trans_s2m * radar_delta;\n          }\n          else odom_s2s_now = keyframe_pose_s2s * prev_trans_s2s * radar_delta;\n        }\n      }\n      last_radar_delta = radar_delta;\n      \n      if(0){\n        cout << \"radar trans:\" << dx_rd << \" m rot:\" << da_rd << \" degree. EgoVel \" << delta_trans_egovel.norm() << \" m. \"\n        << \"IMU rot \" << delta_rot_imu << \" degree.\" << endl;\n        cout << \"dx \" << dx << \" m. da \" << da << \" degree.\" << endl;\n      }\n    }\n    prev_time = stamp;\n    if (!thresholded) {\n      prev_trans_s2s = trans_s2s;\n      prev_trans_s2m = trans_s2m;\n    }\n    \n    //********** Decided whether to accept the frame as a key frame or not **********\n    if(keyframe_updater->decide(Eigen::Isometry3d(odom_s2s_now), stamp)) {\n      // Loose Coupling the IMU roll & pitch\n      if (enable_imu_fusion){\n        if(enable_scan_to_map) transformUpdate(odom_s2m_now);\n        else transformUpdate(odom_s2s_now);\n      }\n\n      keyframe_cloud_s2s = filtered;\n      registration_s2s->setInputTarget(keyframe_cloud_s2s);\n      keyframe_pose_s2s = odom_s2s_now;\n      keyframe_stamp = stamp;\n      prev_time = stamp;\n      prev_trans_s2s.setIdentity();\n\n      double accum_d = keyframe_updater->get_accum_distance();\n      KeyFrame::Ptr keyframe(new KeyFrame(keyframe_index, stamp, Eigen::Isometry3d(odom_s2s_now.cast<double>()), accum_d, cloud));\n      keyframe_index ++;\n      keyframes.push_back(keyframe);\n\n      // record keyframe's imu\n      flush_imu_queue();\n\n      if (enable_scan_to_map){\n        pcl::PointCloud<PointT>::Ptr submap_cloud(new pcl::PointCloud<PointT>());\n        pcl::PointCloud<PointT>::ConstPtr submap_cloud_downsampled;\n        for(size_t i=std::max(0, (int)keyframes.size()-max_submap_frames); i < keyframes.size()-1; i++){\n          Eigen::Matrix4d rel_pose = keyframes.at(i)->odom_scan2scan.matrix().inverse() * keyframes.back()->odom_scan2scan.matrix();\n          pcl::PointCloud<PointT>::Ptr cloud_transformed(new pcl::PointCloud<PointT>());\n          pcl::transformPointCloud(*keyframes.at(i)->cloud, *cloud_transformed, rel_pose);\n          *submap_cloud += *cloud_transformed;\n        }\n        submap_cloud_downsampled = downsample(submap_cloud);\n        keyframe_cloud_s2m = submap_cloud_downsampled;\n        registration_s2m->setInputTarget(keyframe_cloud_s2m);\n        \n        keyframes.back()->odom_scan2map = Eigen::Isometry3d(odom_s2m_now);\n        keyframe_pose_s2m = odom_s2m_now;\n        prev_trans_s2m.setIdentity();\n      }\n    }\n    \n    if (aligned_points_pub.getNumSubscribers() > 0)\n    {\n      pcl::transformPointCloud (*cloud, *aligned, odom_s2s_now);\n      aligned->header.frame_id = odometryFrame;\n      aligned_points_pub.publish(*aligned);\n    }\n\n    if (enable_scan_to_map)\n      return odom_s2m_now;\n    else\n      return odom_s2s_now;\n  }\n\n\n  /**\n   * @brief publish odometry\n   * @param stamp  timestamp\n   * @param pose   odometry pose to be published\n   */\n  void publish_odometry(const ros::Time& stamp, const std::string& father_frame_id, const std::string& child_frame_id, const Eigen::Matrix4d& pose_in, const geometry_msgs::TwistWithCovariance twist_in) {\n    // publish transform stamped for IMU integration\n    geometry_msgs::TransformStamped odom_trans = matrix2transform(stamp, pose_in, father_frame_id, child_frame_id); //\"map\" \n    trans_pub.publish(odom_trans);\n\n    // broadcast the transform over TF\n    map2odom_broadcaster.sendTransform(odom_trans);\n\n    // publish the transform\n    nav_msgs::Odometry odom;\n    odom.header.stamp = stamp;\n    odom.header.frame_id = father_frame_id;   // frame: /odom\n    odom.child_frame_id = child_frame_id;\n\n    odom.pose.pose.position.x = pose_in(0, 3);\n    odom.pose.pose.position.y = pose_in(1, 3);\n    odom.pose.pose.position.z = pose_in(2, 3);\n    odom.pose.pose.orientation = odom_trans.transform.rotation;\n    odom.twist = twist_in;\n\n    odom_pub.publish(odom);\n  }\n\n  /**\n   * @brief publish scan matching_ status\n   */\n  void publish_scan_matching_status(const ros::Time& stamp, const std::string& frame_id, pcl::PointCloud<pcl::PointXYZI>::ConstPtr aligned, const std::string& msf_source, const Eigen::Isometry3d& msf_delta) {\n    if(!status_pub.getNumSubscribers()) {\n      return;\n    }\n\n    ScanMatchingStatus status;\n    status.header.frame_id = frame_id;\n    status.header.stamp = stamp;\n    status.has_converged = registration_s2s->hasConverged();\n    status.matching_error = registration_s2s->getFitnessScore();\n\n    const double max_correspondence_dist = 0.5;\n\n    int num_inliers = 0;\n    std::vector<int> k_indices;\n    std::vector<float> k_sq_dists;\n    for(int i=0; i<aligned->size(); i++) {\n      const auto& pt = aligned->at(i);\n      registration_s2s->getSearchMethodTarget()->nearestKSearch(pt, 1, k_indices, k_sq_dists);\n      if(k_sq_dists[0] < max_correspondence_dist * max_correspondence_dist) {\n        num_inliers++;\n      }\n    }\n    status.inlier_fraction = static_cast<float>(num_inliers) / aligned->size();\n\n    status.relative_pose = isometry2pose(Eigen::Isometry3d(registration_s2s->getFinalTransformation().cast<double>()));\n\n    if(!msf_source.empty()) {\n      status.prediction_labels.resize(1);\n      status.prediction_labels[0].data = msf_source;\n\n      status.prediction_errors.resize(1);\n      Eigen::Isometry3d error = Eigen::Isometry3d(registration_s2s->getFinalTransformation().cast<double>()).inverse() * msf_delta;\n      status.prediction_errors[0] = isometry2pose(error.cast<double>());\n    }\n\n    status_pub.publish(status);\n  }\n\n  void command_callback(const std_msgs::String& str_msg) {\n    if (str_msg.data == \"time\") {\n      std::sort(s2s_matching_time.begin(), s2s_matching_time.end());\n      double median = s2s_matching_time.at(size_t(s2s_matching_time.size() / 2));\n      cout << \"Scan Matching time cost (median): \" << median << endl;\n    }\n  }\n\nprivate:\n  // ROS topics\n  ros::NodeHandle nh;\n  ros::NodeHandle mt_nh;\n  ros::NodeHandle private_nh;\n\n  // ros::Subscriber points_sub;\n  ros::Subscriber msf_pose_sub;\n  ros::Subscriber msf_pose_after_update_sub;\n  ros::Subscriber imu_sub;\n\n  std::mutex imu_queue_mutex;\n  std::deque<sensor_msgs::ImuConstPtr> imu_queue;\n  sensor_msgs::Imu last_frame_imu;\n\n  bool enable_imu_fusion;\n  bool imu_debug_out;\n  Eigen::Matrix3d global_orient_matrix;  // The rotation matrix with initial IMU roll & pitch measurement (yaw = 0)\n    double timeLaserOdometry = 0;\n    int imuPointerFront;\n    int imuPointerLast;\n    double imuTime[imuQueLength];\n    float imuRoll[imuQueLength];\n    float imuPitch[imuQueLength];\n    double imu_fusion_ratio;\n\n  std::unique_ptr<message_filters::Subscriber<geometry_msgs::TwistWithCovarianceStamped>> ego_vel_sub;\n  std::unique_ptr<message_filters::Subscriber<sensor_msgs::PointCloud2>> points_sub;\n  std::unique_ptr<message_filters::Synchronizer<ApproxSyncPolicy>> sync;\n\n  // Submap\n  ros::Publisher submap_pub;\n  std::unique_ptr<KeyframeUpdater> keyframe_updater;\n  std::vector<KeyFrame::Ptr> keyframes;\n  size_t keyframe_index = 0;\n  double map_cloud_resolution;\n  int  max_submap_frames;\n  bool enable_scan_to_map;\n\n  // std::unique_ptr<GraphSLAM> graph_slam;\n  // std::unique_ptr<InformationMatrixCalculator> inf_calclator;\n  \n  ros::Publisher odom_pub;\n  ros::Publisher trans_pub;\n  // ros::Publisher keyframe_trans_pub;\n  ros::Publisher aligned_points_pub;\n  ros::Publisher status_pub;\n  ros::Publisher read_until_pub;\n  tf::TransformListener tf_listener;\n  tf::TransformBroadcaster map2odom_broadcaster; // map => odom_frame\n\n  std::string points_topic;\n\n\n  // keyframe_ parameters\n  double keyframe_delta_trans;  // minimum distance between keyframes_\n  double keyframe_delta_angle;  //\n  double keyframe_delta_time;   //\n\n  // registration validation by thresholding\n  bool enable_transform_thresholding;  //\n  bool enable_imu_thresholding;\n  double max_acceptable_trans;  //\n  double max_acceptable_angle;\n  double max_diff_trans;\n  double max_diff_angle;\n  double max_egovel_cum;\n  Eigen::Matrix4d last_radar_delta = Eigen::Matrix4d::Identity();\n\n  // odometry calculation\n  geometry_msgs::PoseWithCovarianceStampedConstPtr msf_pose;\n  geometry_msgs::PoseWithCovarianceStampedConstPtr msf_pose_after_update;\n\n  Eigen::Matrix4d egovel_cum = Eigen::Matrix4d::Identity();\n  bool use_ego_vel;\n\n  ros::Time prev_time;\n  Eigen::Matrix4d prev_trans_s2s;                  // previous relative transform from keyframe_\n  Eigen::Matrix4d keyframe_pose_s2s;               // keyframe_ pose\n  Eigen::Matrix4d prev_trans_s2m;\n  Eigen::Matrix4d keyframe_pose_s2m;               // keyframe_ pose\n  ros::Time keyframe_stamp;                    // keyframe_ time\n  pcl::PointCloud<PointT>::ConstPtr keyframe_cloud_s2s;  // keyframe_ point cloud\n  pcl::PointCloud<PointT>::ConstPtr keyframe_cloud_s2m;  // keyframe_ point cloud\n\n  // Registration\n  pcl::Filter<PointT>::Ptr downsample_filter;\n  pcl::Registration<PointT, PointT>::Ptr registration_s2s;    // Scan-to-Scan Registration\n  pcl::Registration<PointT, PointT>::Ptr registration_s2m;    // Scan-to-Submap Registration\n\n  // Time evaluation\n  std::vector<double> s2s_matching_time;\n  ros::Subscriber command_sub;\n};\n\n}  // namespace radar_graph_slam\n\nPLUGINLIB_EXPORT_CLASS(radar_graph_slam::ScanMatchingOdometryNodelet, nodelet::Nodelet)\n"
  },
  {
    "path": "cmake/FindG2O.cmake",
    "content": "# Find the header files\n\nFIND_PATH(G2O_INCLUDE_DIR g2o/core/base_vertex.h\n  ${G2O_ROOT}/include\n  $ENV{G2O_ROOT}/include\n  $ENV{G2O_ROOT}\n  /usr/local/include\n  /usr/include\n  /opt/local/include\n  /sw/local/include\n  /sw/include\n  /opt/ros/$ENV{ROS_DISTRO}/include\n  NO_DEFAULT_PATH\n  )\n\n# Macro to unify finding both the debug and release versions of the\n# libraries; this is adapted from the OpenSceneGraph FIND_LIBRARY\n# macro.\n\nMACRO(FIND_G2O_LIBRARY MYLIBRARY MYLIBRARYNAME)\n\n  FIND_LIBRARY(\"${MYLIBRARY}_DEBUG\"\n    NAMES \"g2o_${MYLIBRARYNAME}_d\"\n    PATHS\n    ${G2O_ROOT}/lib/Debug\n    ${G2O_ROOT}/lib\n    $ENV{G2O_ROOT}/lib/Debug\n    $ENV{G2O_ROOT}/lib\n    /opt/ros/$ENV{ROS_DISTRO}/lib\n    NO_DEFAULT_PATH\n    )\n\n  FIND_LIBRARY(\"${MYLIBRARY}_DEBUG\"\n    NAMES \"g2o_${MYLIBRARYNAME}_d\"\n    PATHS\n    ~/Library/Frameworks\n    /Library/Frameworks\n    /usr/local/lib\n    /usr/local/lib64\n    /usr/lib\n    /usr/lib64\n    /opt/local/lib\n    /sw/local/lib\n    /sw/lib\n    )\n  \n  FIND_LIBRARY(${MYLIBRARY}\n    NAMES \"g2o_${MYLIBRARYNAME}\"\n    PATHS\n    ${G2O_ROOT}/lib/Release\n    ${G2O_ROOT}/lib\n    $ENV{G2O_ROOT}/lib/Release\n    $ENV{G2O_ROOT}/lib\n    /opt/ros/$ENV{ROS_DISTRO}/lib\n    NO_DEFAULT_PATH\n    )\n\n  FIND_LIBRARY(${MYLIBRARY}\n    NAMES \"g2o_${MYLIBRARYNAME}\"\n    PATHS\n    ~/Library/Frameworks\n    /Library/Frameworks\n    /usr/local/lib\n    /usr/local/lib64\n    /usr/lib\n    /usr/lib64\n    /opt/local/lib\n    /sw/local/lib\n    /sw/lib\n    )\n  \n  IF(NOT ${MYLIBRARY}_DEBUG)\n    IF(MYLIBRARY)\n      SET(${MYLIBRARY}_DEBUG ${MYLIBRARY})\n    ENDIF(MYLIBRARY)\n  ENDIF( NOT ${MYLIBRARY}_DEBUG)\n  \nENDMACRO(FIND_G2O_LIBRARY LIBRARY LIBRARYNAME)\n\n# Find the core elements\nFIND_G2O_LIBRARY(G2O_STUFF_LIBRARY stuff)\nFIND_G2O_LIBRARY(G2O_CORE_LIBRARY core)\n\n# Find the CLI library\nFIND_G2O_LIBRARY(G2O_CLI_LIBRARY cli)\n\n# Find the pluggable solvers\nFIND_G2O_LIBRARY(G2O_SOLVER_CHOLMOD solver_cholmod)\nFIND_G2O_LIBRARY(G2O_SOLVER_CSPARSE solver_csparse)\nFIND_G2O_LIBRARY(G2O_SOLVER_CSPARSE_EXTENSION csparse_extension)\nFIND_G2O_LIBRARY(G2O_SOLVER_DENSE solver_dense)\nFIND_G2O_LIBRARY(G2O_SOLVER_PCG solver_pcg)\nFIND_G2O_LIBRARY(G2O_SOLVER_SLAM2D_LINEAR solver_slam2d_linear)\nFIND_G2O_LIBRARY(G2O_SOLVER_STRUCTURE_ONLY solver_structure_only)\nFIND_G2O_LIBRARY(G2O_SOLVER_EIGEN solver_eigen)\n\n# Find the predefined types\nFIND_G2O_LIBRARY(G2O_TYPES_DATA types_data)\nFIND_G2O_LIBRARY(G2O_TYPES_ICP types_icp)\nFIND_G2O_LIBRARY(G2O_TYPES_SBA types_sba)\nFIND_G2O_LIBRARY(G2O_TYPES_SCLAM2D types_sclam2d)\nFIND_G2O_LIBRARY(G2O_TYPES_SIM3 types_sim3)\nFIND_G2O_LIBRARY(G2O_TYPES_SLAM2D types_slam2d)\nFIND_G2O_LIBRARY(G2O_TYPES_SLAM3D types_slam3d)\nFIND_G2O_LIBRARY(G2O_TYPES_SLAM3D_ADDONS types_slam3d_addons)\n\n# G2O solvers declared found if we found at least one solver\nSET(G2O_SOLVERS_FOUND \"NO\")\nIF(G2O_SOLVER_CHOLMOD OR G2O_SOLVER_CSPARSE OR G2O_SOLVER_DENSE OR G2O_SOLVER_PCG OR G2O_SOLVER_SLAM2D_LINEAR OR G2O_SOLVER_STRUCTURE_ONLY OR G2O_SOLVER_EIGEN)\n  SET(G2O_SOLVERS_FOUND \"YES\")\nENDIF(G2O_SOLVER_CHOLMOD OR G2O_SOLVER_CSPARSE OR G2O_SOLVER_DENSE OR G2O_SOLVER_PCG OR G2O_SOLVER_SLAM2D_LINEAR OR G2O_SOLVER_STRUCTURE_ONLY OR G2O_SOLVER_EIGEN)\n\n# G2O itself declared found if we found the core libraries and at least one solver\nSET(G2O_FOUND \"NO\")\nIF(G2O_STUFF_LIBRARY AND G2O_CORE_LIBRARY AND G2O_INCLUDE_DIR AND G2O_SOLVERS_FOUND)\n  SET(G2O_FOUND \"YES\")\nENDIF(G2O_STUFF_LIBRARY AND G2O_CORE_LIBRARY AND G2O_INCLUDE_DIR AND G2O_SOLVERS_FOUND)\n"
  },
  {
    "path": "config/params.yaml",
    "content": "radar_slam:\n\n  # Topics\n  pointCloudTopic: \"/radar_enhanced_pcl\"               # Point cloud data <!-- pc2_raw inlier_pc2 segmented -->\n  imuTopic: \"/vectornav/imu\"                         # IMU data\n  odomTopic: \"/odom\"                   # IMU pre-preintegration odometry, same frequency as IMU\n  gpsTopic: \"/ublox/fix\"                   # GPS odometry topic from navsat, see module_navsat.launch file\n\n  # Frames\n  lidarFrame: \"\"\n  baselinkFrame: \"base_link\"\n  odometryFrame: \"odom\"\n  mapFrame: \"map\"\n\n  # GPS Settings\n  # useGpsElevation: false                      # if GPS elevation is bad, set to \"false\"\n  # gpsCovThreshold: 2.0                        # m^2, threshold for using GPS data\n  # poseCovThreshold: 25.0                      # m^2, threshold for using GPS data\n  \n  # Export settings\n  # savePCD: false                              # \n  # savePCDDirectory: \"/Downloads/4DRadarSLAM/\"        #\n\n  # Sensor Settings\n  # downsampleRate: 1                           # default: 1. Downsample your data if too many points. i.e., 16 = 64 / 4, 16 = 16 / 1 \n  # lidarMinRange: 1.0                          # default: 1.0, minimum lidar range to be used\n  # lidarMaxRange: 1000.0                       # default: 1000.0, maximum lidar range to be used\n\n  # IMU Settings\n  imuAccNoise: 0.0022281160035059417 # 3.9939570888238808e-03\n  imuGyrNoise: 0.00011667951042710442 # 1.5636343949698187e-03\n  imuAccBiasN: 0.00011782392708033614 # 6.4356659353532566e-05\n  imuGyrBiasN: 2.616129872371749e-06 # 3.5640318696367613e-05\n  imuGravity: 9.80511\n  imuRPYWeight: 0.01\n\n  # Extrinsics (Lidar -> IMU)\n  # extrinsicTrans: [0, 0, 0]\n  extrinsicTrans: [-0.3176955976234, -0.13761019052125, 0.05898352725152]\n  # extrinsicRot: [1, 0, 0,\n  #                 0, -1, 0,\n  #                 0, 0, -1]\n  # extrinsicRPY: [1, 0, 0,\n  #                 0, -1, 0,\n  #                 0, 0, -1]\n  extrinsicRot: [0.999735807578, -0.0215215701795, -0.0081643477385,\n                 -0.02148120581797, -0.9997581134183, 0.00502853428037,\n                 -0.00826995351904, -0.0048509797951, -0.99995400578406]\n  extrinsicRPY: [0.999735807578, -0.0215215701795, -0.0081643477385,\n                 -0.02148120581797, -0.9997581134183, 0.00502853428037,\n                 -0.00826995351904, -0.0048509797951, -0.99995400578406]\n  # extrinsicRot: [1, 0, 0,\n  #                 0, 1, 0,\n  #                 0, 0, 1]\n  # extrinsicRPY: [1, 0, 0,\n  #                 0, 1, 0,\n  #                 0, 0, 1]\n\n\n"
  },
  {
    "path": "include/dbscan/DBSCAN_kdtree.h",
    "content": "#ifndef DBSCAN_KDTREE_H\n#define DBSCAN_KDTREE_H\n\n#include <pcl/point_types.h>\n#include \"DBSCAN_simple.h\"\n\ntemplate <typename PointT>\nclass DBSCANKdtreeCluster: public DBSCANSimpleCluster<PointT> {\nprotected:\n    virtual int radiusSearch (\n        int index, double radius, std::vector<int> &k_indices,\n        std::vector<float> &k_sqr_distances) const\n    {\n        return this->search_method_->radiusSearch(index, radius, k_indices, k_sqr_distances);\n    }\n\n}; // class DBSCANCluster\n\n#endif // DBSCAN_KDTREE_H"
  },
  {
    "path": "include/dbscan/DBSCAN_precomp.h",
    "content": "#ifndef DBSCAN_PRECOMP_H\n#define DBSCAN_PRECOMP_H\n\n#include <pcl/point_types.h>\n#include \"DBSCAN_simple.h\"\n\ntemplate <typename PointT>\nclass DBSCANPrecompCluster : public DBSCANSimpleCluster<PointT>  {\npublic:\n    virtual void setInputCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud) {\n        this->input_cloud_ = cloud;\n        int size = this->input_cloud_->points.size();\n        adjoint_indexes_ = std::vector<std::vector<int>>(size, std::vector<int>());\n        distances_ = std::vector<std::vector<float>>(size, std::vector<float>());\n        precomp();\n    }\n\nprotected:\n    std::vector<std::vector<float>> distances_;\n    std::vector<std::vector<int>> adjoint_indexes_;\n\n    void precomp() {\n        int size = this->input_cloud_->points.size();\n        for (int i = 0; i < size; i++) {\n            adjoint_indexes_[i].push_back(i);\n            distances_[i].push_back(0.0);\n        }\n        double radius_square = this->eps_ * this->eps_;\n        for (int i = 0; i < size; i++) {\n            for (int j = i+1; j < size; j++) {\n                double distance_x = this->input_cloud_->points[i].x - this->input_cloud_->points[j].x;\n                double distance_y = this->input_cloud_->points[i].y - this->input_cloud_->points[j].y;\n                double distance_z = this->input_cloud_->points[i].z - this->input_cloud_->points[j].z;\n                double distance_square = distance_x * distance_x + distance_y * distance_y + distance_z * distance_z;\n                if (distance_square <= radius_square) {\n                    adjoint_indexes_[i].push_back(j);\n                    adjoint_indexes_[j].push_back(i);\n                    double distance = std::sqrt(distance_square);\n                    distances_[i].push_back(distance);\n                    distances_[j].push_back(distance);\n                }\n            }\n        }\n    }\n\n    virtual int radiusSearch(\n        int index, double radius, std::vector<int> &k_indices,\n        std::vector<float> &k_sqr_distances) const\n    {\n        // radius = eps_\n        k_indices = adjoint_indexes_[index];\n        k_sqr_distances = distances_[index];\n        return k_indices.size();\n    }\n}; // class DBSCANCluster\n\n#endif // DBSCAN_PRECOMP_H"
  },
  {
    "path": "include/dbscan/DBSCAN_simple.h",
    "content": "#ifndef DBSCAN_H\n#define DBSCAN_H\n\n#include <pcl/point_types.h>\n\n#define UN_PROCESSED 0\n#define PROCESSING 1\n#define PROCESSED 2\n\ninline bool comparePointClusters (const pcl::PointIndices &a, const pcl::PointIndices &b) {\n    return (a.indices.size () < b.indices.size ());\n}\n\ntemplate <typename PointT>\nclass DBSCANSimpleCluster {\npublic:\n    typedef typename pcl::PointCloud<PointT>::Ptr PointCloudPtr;\n    typedef typename pcl::search::KdTree<PointT>::Ptr KdTreePtr;\n    virtual void setInputCloud(PointCloudPtr cloud) {\n        input_cloud_ = cloud;\n    }\n\n    void setSearchMethod(KdTreePtr tree) {\n        search_method_ = tree;\n    }\n\n    void extract(std::vector<pcl::PointIndices>& cluster_indices) {\n        std::vector<int> nn_indices;\n        std::vector<float> nn_distances;\n        std::vector<bool> is_noise(input_cloud_->points.size(), false);\n        std::vector<int> types(input_cloud_->points.size(), UN_PROCESSED);\n        for (int i = 0; i < input_cloud_->points.size(); i++) {\n            if (types[i] == PROCESSED) {\n                continue;\n            }\n            int nn_size = radiusSearch(i, eps_, nn_indices, nn_distances);\n            if (nn_size < minPts_) {\n                is_noise[i] = true;\n                continue;\n            }\n            \n            std::vector<int> seed_queue;\n            seed_queue.push_back(i);\n            types[i] = PROCESSED;\n            \n            for (int j = 0; j < nn_size; j++) {\n                if (nn_indices[j] != i) {\n                    seed_queue.push_back(nn_indices[j]);\n                    types[nn_indices[j]] = PROCESSING;\n                }\n            } // for every point near the chosen core point.\n            int sq_idx = 1;\n            while (sq_idx < seed_queue.size()) {\n                int cloud_index = seed_queue[sq_idx];\n                if (is_noise[cloud_index] || types[cloud_index] == PROCESSED) {\n                    // seed_queue.push_back(cloud_index);\n                    types[cloud_index] = PROCESSED;\n                    sq_idx++;\n                    continue; // no need to check neighbors.\n                }\n                nn_size = radiusSearch(cloud_index, eps_, nn_indices, nn_distances);\n                if (nn_size >= minPts_) {\n                    for (int j = 0; j < nn_size; j++) {\n                        if (types[nn_indices[j]] == UN_PROCESSED) {\n                            \n                            seed_queue.push_back(nn_indices[j]);\n                            types[nn_indices[j]] = PROCESSING;\n                        }\n                    }\n                }\n                \n                types[cloud_index] = PROCESSED;\n                sq_idx++;\n            }\n            if (seed_queue.size() >= min_pts_per_cluster_ && seed_queue.size () <= max_pts_per_cluster_) {\n                pcl::PointIndices r;\n                r.indices.resize(seed_queue.size());\n                for (int j = 0; j < seed_queue.size(); ++j) {\n                    r.indices[j] = seed_queue[j];\n                }\n                // These two lines should not be needed: (can anyone confirm?) -FF\n                std::sort (r.indices.begin (), r.indices.end ());\n                r.indices.erase (std::unique (r.indices.begin (), r.indices.end ()), r.indices.end ());\n\n                r.header = input_cloud_->header;\n                cluster_indices.push_back (r);   // We could avoid a copy by working directly in the vector\n            }\n        } // for every point in input cloud\n        std::sort (cluster_indices.rbegin (), cluster_indices.rend (), comparePointClusters);\n    }\n\n    void setClusterTolerance(double tolerance) {\n        eps_ = tolerance; \n    }\n\n    void setMinClusterSize (int min_cluster_size) { \n        min_pts_per_cluster_ = min_cluster_size; \n    }\n\n    void setMaxClusterSize (int max_cluster_size) { \n        max_pts_per_cluster_ = max_cluster_size; \n    }\n    \n    void setCorePointMinPts(int core_point_min_pts) {\n        minPts_ = core_point_min_pts;\n    }\n\nprotected:\n    PointCloudPtr input_cloud_;\n    \n    double eps_ {0.0};\n    int minPts_ {1}; // not including the point itself.\n    int min_pts_per_cluster_ {1};\n    int max_pts_per_cluster_ {std::numeric_limits<int>::max()};\n\n    KdTreePtr search_method_;\n\n    virtual int radiusSearch(\n        int index, double radius, std::vector<int> &k_indices,\n        std::vector<float> &k_sqr_distances) const\n    {\n        k_indices.clear();\n        k_sqr_distances.clear();\n        k_indices.push_back(index);\n        k_sqr_distances.push_back(0);\n        int size = input_cloud_->points.size();\n        double radius_square = radius * radius;\n        for (int i = 0; i < size; i++) {\n            if (i == index) {\n                continue;\n            }\n            double distance_x = input_cloud_->points[i].x - input_cloud_->points[index].x;\n            double distance_y = input_cloud_->points[i].y - input_cloud_->points[index].y;\n            double distance_z = input_cloud_->points[i].z - input_cloud_->points[index].z;\n            double distance_square = distance_x * distance_x + distance_y * distance_y + distance_z * distance_z;\n            if (distance_square <= radius_square) {\n                k_indices.push_back(i);\n                k_sqr_distances.push_back(std::sqrt(distance_square));\n            }\n        }\n        return k_indices.size();\n    }\n}; // class DBSCANCluster\n\n#endif // DBSCAN_H"
  },
  {
    "path": "include/g2o/edge_plane_identity.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef EDGE_PLANE_IDENTITY_HPP\n#define EDGE_PLANE_IDENTITY_HPP\n\n#include <Eigen/Dense>\n#include <g2o/core/base_binary_edge.h>\n#include <g2o/types/slam3d_addons/vertex_plane.h>\n\nnamespace g2o {\n\n/**\n * @brief A modified version of g2o::EdgePlane. This class takes care of flipped plane normals.\n *\n */\nclass EdgePlaneIdentity : public BaseBinaryEdge<4, Eigen::Vector4d, VertexPlane, VertexPlane> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgePlaneIdentity() : BaseBinaryEdge<4, Eigen::Vector4d, VertexPlane, VertexPlane>() {\n    _information.setIdentity();\n    _error.setZero();\n  }\n  void computeError() {\n    const VertexPlane* v1 = static_cast<const VertexPlane*>(_vertices[0]);\n    const VertexPlane* v2 = static_cast<const VertexPlane*>(_vertices[1]);\n\n    Eigen::Vector4d p1 = v1->estimate().toVector();\n    Eigen::Vector4d p2 = v2->estimate().toVector();\n\n    if(p1.dot(p2) < 0.0) {\n      p2 = -p2;\n    }\n\n    _error = (p2 - p1) - _measurement;\n  }\n  virtual bool read(std::istream& is) override {\n    Eigen::Vector4d v;\n    for(int i = 0; i < 4; ++i) {\n      is >> v[i];\n    }\n\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i) {\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) {\n          information()(j, i) = information()(i, j);\n        }\n      }\n    }\n\n    return true;\n  }\n\n  virtual bool write(std::ostream& os) const override {\n    for(int i = 0; i < 4; ++i) {\n      os << _measurement[i] << \" \";\n    }\n\n    for(int i = 0; i < information().rows(); ++i) {\n      for(int j = i; j < information().cols(); ++j) {\n        os << \" \" << information()(i, j);\n      };\n    }\n    return os.good();\n  }\n\n  virtual void setMeasurement(const Eigen::Vector4d& m) override {\n    _measurement = m;\n  }\n\n  virtual int measurementDimension() const override {\n    return 4;\n  }\n};\n\n}  // namespace g2o\n\n#endif  // EDGE_PLANE_PARALLEL_HPP\n"
  },
  {
    "path": "include/g2o/edge_plane_parallel.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef EDGE_PLANE_PARALLEL_HPP\n#define EDGE_PLANE_PARALLEL_HPP\n\n#include <Eigen/Dense>\n#include <g2o/core/base_binary_edge.h>\n#include <g2o/types/slam3d_addons/vertex_plane.h>\n\nnamespace g2o {\n\nclass EdgePlaneParallel : public BaseBinaryEdge<3, Eigen::Vector3d, VertexPlane, VertexPlane> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgePlaneParallel() : BaseBinaryEdge<3, Eigen::Vector3d, VertexPlane, VertexPlane>() {\n    _information.setIdentity();\n    _error.setZero();\n  }\n\n  void computeError() override {\n    const VertexPlane* v1 = static_cast<const VertexPlane*>(_vertices[0]);\n    const VertexPlane* v2 = static_cast<const VertexPlane*>(_vertices[1]);\n\n    Eigen::Vector3d normal1 = v1->estimate().normal();\n    Eigen::Vector3d normal2 = v2->estimate().normal();\n\n    if(normal1.dot(normal2) < 0.0) {\n      normal2 = -normal2;\n    }\n\n    _error = (normal2 - normal1) - _measurement;\n  }\n  virtual bool read(std::istream& is) override {\n    Eigen::Vector3d v;\n    for(int i = 0; i < 3; ++i) {\n      is >> v[i];\n    }\n\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i) {\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) {\n          information()(j, i) = information()(i, j);\n        }\n      }\n    }\n\n    return true;\n  }\n\n  virtual bool write(std::ostream& os) const override {\n    for(int i = 0; i < 3; ++i) {\n      os << _measurement[i] << \" \";\n    }\n\n    for(int i = 0; i < information().rows(); ++i) {\n      for(int j = i; j < information().cols(); ++j) {\n        os << \" \" << information()(i, j);\n      };\n    }\n    return os.good();\n  }\n\n  virtual void setMeasurement(const Eigen::Vector3d& m) override {\n    _measurement = m;\n  }\n\n  virtual int measurementDimension() const override {\n    return 3;\n  }\n};\n\nclass EdgePlanePerpendicular : public BaseBinaryEdge<1, Eigen::Vector3d, VertexPlane, VertexPlane> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgePlanePerpendicular() : BaseBinaryEdge<1, Eigen::Vector3d, VertexPlane, VertexPlane>() {\n    _information.setIdentity();\n    _error.setZero();\n  }\n\n  void computeError() override {\n    const VertexPlane* v1 = static_cast<const VertexPlane*>(_vertices[0]);\n    const VertexPlane* v2 = static_cast<const VertexPlane*>(_vertices[1]);\n\n    Eigen::Vector3d normal1 = v1->estimate().normal().normalized();\n    Eigen::Vector3d normal2 = v2->estimate().normal().normalized();\n\n    _error[0] = normal1.dot(normal2);\n  }\n\n  virtual bool read(std::istream& is) override {\n    Eigen::Vector3d v;\n    for(int i = 0; i < 3; ++i) {\n      is >> v[i];\n    }\n\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i) {\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) {\n          information()(j, i) = information()(i, j);\n        }\n      }\n    }\n\n    return true;\n  }\n\n  virtual bool write(std::ostream& os) const override {\n    for(int i = 0; i < 3; ++i) {\n      os << _measurement[i] << \" \";\n    }\n\n    for(int i = 0; i < information().rows(); ++i) {\n      for(int j = i; j < information().cols(); ++j) {\n        os << \" \" << information()(i, j);\n      };\n    }\n    return os.good();\n  }\n\n  virtual void setMeasurement(const Eigen::Vector3d& m) override {\n    _measurement = m;\n  }\n\n  virtual int measurementDimension() const override {\n    return 3;\n  }\n};\n\n}  // namespace g2o\n\n#endif  // EDGE_PLANE_PARALLEL_HPP\n"
  },
  {
    "path": "include/g2o/edge_plane_prior.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef EDGE_PLANE_PRIOR_HPP\n#define EDGE_PLANE_PRIOR_HPP\n\n#include <Eigen/Dense>\n#include <g2o/core/base_unary_edge.h>\n#include <g2o/types/slam3d_addons/vertex_plane.h>\n\nnamespace g2o {\nclass EdgePlanePriorNormal : public g2o::BaseUnaryEdge<3, Eigen::Vector3d, g2o::VertexPlane> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgePlanePriorNormal() : g2o::BaseUnaryEdge<3, Eigen::Vector3d, g2o::VertexPlane>() {}\n\n  void computeError() override {\n    const g2o::VertexPlane* v1 = static_cast<const g2o::VertexPlane*>(_vertices[0]);\n    Eigen::Vector3d normal = v1->estimate().normal();\n\n    if(normal.dot(_measurement) < 0.0) {\n      normal = -normal;\n    }\n\n    _error = normal - _measurement;\n  }\n\n  void setMeasurement(const Eigen::Vector3d& m) override {\n    _measurement = m;\n  }\n\n  virtual bool read(std::istream& is) override {\n    Eigen::Vector3d v;\n    is >> v(0) >> v(1) >> v(2);\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    Eigen::Vector3d v = _measurement;\n    os << v(0) << \" \" << v(1) << \" \" << v(2) << \" \";\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n};\n\nclass EdgePlanePriorDistance : public g2o::BaseUnaryEdge<1, double, g2o::VertexPlane> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgePlanePriorDistance() : g2o::BaseUnaryEdge<1, double, g2o::VertexPlane>() {}\n\n  void computeError() override {\n    const g2o::VertexPlane* v1 = static_cast<const g2o::VertexPlane*>(_vertices[0]);\n    _error[0] = _measurement - v1->estimate().distance();\n  }\n\n  void setMeasurement(const double& m) override {\n    _measurement = m;\n  }\n\n  virtual bool read(std::istream& is) override {\n    is >> _measurement;\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    os << _measurement;\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n};\n}  // namespace g2o\n\n#endif  // EDGE_SE3_PRIORXY_HPP\n"
  },
  {
    "path": "include/g2o/edge_se3_gt_utm.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef KKL_G2O_EDGE_SE3_GTUTM_HPP\n#define KKL_G2O_EDGE_SE3_GTUTM_HPP\n\n#include <g2o/types/slam3d/types_slam3d.h>\n#include <g2o/types/slam3d_addons/types_slam3d_addons.h>\n\nnamespace g2o {\nclass EdgeSE3GtUTM : public g2o::BaseUnaryEdge<3, Eigen::Vector3d, g2o::VertexSE3> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgeSE3GtUTM() : g2o::BaseUnaryEdge<3, Eigen::Vector3d, g2o::VertexSE3>() {}\n\n  void computeError() override {\n    const g2o::VertexSE3* v1 = static_cast<const g2o::VertexSE3*>(_vertices[0]);\n\n    Eigen::Vector4d utm_homogeneous = Eigen::Vector4d(_measurement_utm(0), _measurement_utm(1), _measurement_utm(2), 1);\n    Eigen::Vector3d estimate = (v1->estimate() * utm_homogeneous).head(3);\n    _error = estimate - _measurement;\n  }\n\n  void setMeasurement(const Eigen::Vector3d& m) override {\n    _measurement = m;\n  }\n\n  void setMeasurement_utm(const Eigen::Vector3d& m){\n    _measurement_utm = m;\n  }\n\n  virtual bool read(std::istream& is) override {\n    Eigen::Vector3d v;\n    is >> v(0) >> v(1) >> v(2);\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    Eigen::Vector3d v = _measurement;\n    os << v(0) << \" \" << v(1) << \" \" << v(2) << \" \";\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n  private:\n  Eigen::Vector3d _measurement_utm;\n};\n}  // namespace g2o\n\n#endif\n"
  },
  {
    "path": "include/g2o/edge_se3_plane.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef KKL_G2O_EDGE_SE3_PLANE_HPP\n#define KKL_G2O_EDGE_SE3_PLANE_HPP\n\n#include <g2o/types/slam3d/edge_se3.h>\n#include <g2o/types/slam3d/vertex_se3.h>\n#include <g2o/types/slam3d_addons/vertex_plane.h>\n\nnamespace g2o {\nclass EdgeSE3Plane : public g2o::BaseBinaryEdge<3, g2o::Plane3D, g2o::VertexSE3, g2o::VertexPlane> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgeSE3Plane() : BaseBinaryEdge<3, g2o::Plane3D, g2o::VertexSE3, g2o::VertexPlane>() {}\n\n  void computeError() override {\n    const g2o::VertexSE3* v1 = static_cast<const g2o::VertexSE3*>(_vertices[0]);\n    const g2o::VertexPlane* v2 = static_cast<const g2o::VertexPlane*>(_vertices[1]);\n\n    Eigen::Isometry3d w2n = v1->estimate().inverse();\n    Plane3D local_plane = w2n * v2->estimate();\n    _error = local_plane.ominus(_measurement);\n  }\n\n  void setMeasurement(const g2o::Plane3D& m) override {\n    _measurement = m;\n  }\n\n  virtual bool read(std::istream& is) override {\n    Eigen::Vector4d v;\n    is >> v(0) >> v(1) >> v(2) >> v(3);\n    setMeasurement(Plane3D(v));\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    Eigen::Vector4d v = _measurement.toVector();\n    os << v(0) << \" \" << v(1) << \" \" << v(2) << \" \" << v(3) << \" \";\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n};\n}  // namespace g2o\n\n#endif\n"
  },
  {
    "path": "include/g2o/edge_se3_priorquat.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef KKL_G2O_EDGE_SE3_PRIORQUAT_HPP\n#define KKL_G2O_EDGE_SE3_PRIORQUAT_HPP\n\n#include <g2o/types/slam3d/types_slam3d.h>\n#include <g2o/types/slam3d_addons/types_slam3d_addons.h>\n\nnamespace g2o {\nclass EdgeSE3PriorQuat : public g2o::BaseUnaryEdge<3, Eigen::Quaterniond, g2o::VertexSE3> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgeSE3PriorQuat() : g2o::BaseUnaryEdge<3, Eigen::Quaterniond, g2o::VertexSE3>() {}\n\n  void computeError() override {\n    const g2o::VertexSE3* v1 = static_cast<const g2o::VertexSE3*>(_vertices[0]);\n\n    Eigen::Quaterniond estimate = Eigen::Quaterniond(v1->estimate().linear());\n\n    if(_measurement.coeffs().dot(estimate.coeffs()) < 0.0) {\n      estimate.coeffs() = -estimate.coeffs();\n    }\n    _error = estimate.vec() - _measurement.vec();\n  }\n\n  void setMeasurement(const Eigen::Quaterniond& m) override {\n    _measurement = m;\n    if(m.w() < 0.0) {\n      _measurement.coeffs() = -m.coeffs();\n    }\n  }\n\n  virtual bool read(std::istream& is) override {\n    Eigen::Quaterniond q;\n    is >> q.w() >> q.x() >> q.y() >> q.z();\n    setMeasurement(q);\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    Eigen::Quaterniond q = _measurement;\n    os << q.w() << \" \" << q.x() << \" \" << q.y() << \" \" << q.z();\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n};\n}  // namespace g2o\n\n#endif\n"
  },
  {
    "path": "include/g2o/edge_se3_priorvec.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef KKL_G2O_EDGE_SE3_PRIORVEC_HPP\n#define KKL_G2O_EDGE_SE3_PRIORVEC_HPP\n\n#include <g2o/types/slam3d/types_slam3d.h>\n#include <g2o/types/slam3d_addons/types_slam3d_addons.h>\n\nnamespace g2o {\nclass EdgeSE3PriorVec : public g2o::BaseUnaryEdge<3, Eigen::Matrix<double, 6, 1>, g2o::VertexSE3> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgeSE3PriorVec() : g2o::BaseUnaryEdge<3, Eigen::Matrix<double, 6, 1>, g2o::VertexSE3>() {}\n\n  void computeError() override {\n    const g2o::VertexSE3* v1 = static_cast<const g2o::VertexSE3*>(_vertices[0]);\n\n    Eigen::Vector3d direction = _measurement.head<3>();\n    Eigen::Vector3d measurement = _measurement.tail<3>();\n\n    Eigen::Vector3d estimate = (v1->estimate().linear().inverse() * direction);\n\n    _error = estimate - measurement;\n  }\n\n  void setMeasurement(const Eigen::Matrix<double, 6, 1>& m) override {\n    _measurement.head<3>() = m.head<3>().normalized();\n    _measurement.tail<3>() = m.tail<3>().normalized();\n  }\n\n  virtual bool read(std::istream& is) override {\n    Eigen::Matrix<double, 6, 1> v;\n    is >> v[0] >> v[1] >> v[2] >> v[3] >> v[4] >> v[5];\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    Eigen::Matrix<double, 6, 1> v = _measurement;\n    os << v[0] << \" \" << v[1] << \" \" << v[2] << \" \" << v[3] << \" \" << v[4] << \" \" << v[5];\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n};\n}  // namespace g2o\n\n#endif\n"
  },
  {
    "path": "include/g2o/edge_se3_priorxy.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef EDGE_SE3_PRIORXY_HPP\n#define EDGE_SE3_PRIORXY_HPP\n\n#include <g2o/types/slam3d/types_slam3d.h>\n#include <g2o/types/slam3d_addons/types_slam3d_addons.h>\n\nnamespace g2o {\nclass EdgeSE3PriorXY : public g2o::BaseUnaryEdge<2, Eigen::Vector2d, g2o::VertexSE3> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgeSE3PriorXY() : g2o::BaseUnaryEdge<2, Eigen::Vector2d, g2o::VertexSE3>() {}\n\n  void computeError() override {\n    const g2o::VertexSE3* v1 = static_cast<const g2o::VertexSE3*>(_vertices[0]);\n\n    Eigen::Vector2d estimate = v1->estimate().translation().head<2>();\n    _error = estimate - _measurement; \n  }\n\n  void setMeasurement(const Eigen::Vector2d& m) override {\n    _measurement = m;\n  }\n\n  virtual bool read(std::istream& is) override {\n    Eigen::Vector2d v;\n    is >> v(0) >> v(1);\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    Eigen::Vector2d v = _measurement;\n    os << v(0) << \" \" << v(1) << \" \";\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n};\n}  // namespace g2o\n\n#endif  // EDGE_SE3_PRIORXY_HPP\n"
  },
  {
    "path": "include/g2o/edge_se3_priorxyz.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef KKL_G2O_EDGE_SE3_PRIORXYZ_HPP\n#define KKL_G2O_EDGE_SE3_PRIORXYZ_HPP\n\n#include <g2o/types/slam3d/types_slam3d.h>\n#include <g2o/types/slam3d_addons/types_slam3d_addons.h>\n\nnamespace g2o {\nclass EdgeSE3PriorXYZ : public g2o::BaseUnaryEdge<3, Eigen::Vector3d, g2o::VertexSE3> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgeSE3PriorXYZ() : g2o::BaseUnaryEdge<3, Eigen::Vector3d, g2o::VertexSE3>() {}\n\n  void computeError() override {\n    const g2o::VertexSE3* v1 = static_cast<const g2o::VertexSE3*>(_vertices[0]);\n\n    Eigen::Vector3d estimate = v1->estimate().translation();\n    _error = estimate - _measurement;\n  }\n\n  void setMeasurement(const Eigen::Vector3d& m) override {\n    _measurement = m;\n  }\n\n  virtual bool read(std::istream& is) override {\n    Eigen::Vector3d v;\n    is >> v(0) >> v(1) >> v(2);\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    Eigen::Vector3d v = _measurement;\n    os << v(0) << \" \" << v(1) << \" \" << v(2) << \" \";\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n};\n}  // namespace g2o\n\n#endif\n"
  },
  {
    "path": "include/g2o/edge_se3_priorz.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef EDGE_SE3_PRIORZ_HPP\n#define EDGE_SE3_PRIORZ_HPP\n\n#include <g2o/types/slam3d/types_slam3d.h>\n#include <g2o/types/slam3d_addons/types_slam3d_addons.h>\n\nnamespace Eigen {\ntypedef Eigen::Matrix<double, 1, 1> Vector1d;\n}\n\nnamespace g2o {\nclass EdgeSE3PriorZ : public g2o::BaseUnaryEdge<1, Eigen::Vector1d, g2o::VertexSE3> {\npublic:\n  \n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgeSE3PriorZ() : g2o::BaseUnaryEdge<1, Eigen::Vector1d, g2o::VertexSE3>() {}\n\n  void computeError() override {\n    const g2o::VertexSE3* v1 = static_cast<const g2o::VertexSE3*>(_vertices[0]);\n\n    Eigen::Vector1d estimate = v1->estimate().translation().tail<1>();\n    _error = estimate - _measurement; \n  }\n\n  void setMeasurement(const Eigen::Vector1d& m) override {\n    _measurement = m;\n  }\n\n  virtual bool read(std::istream& is) override {\n    Eigen::Vector1d v;\n    is >> v(0);\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    Eigen::Vector1d v = _measurement;\n    os << v(0) << \" \";\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n};\n}  // namespace g2o\n\n#endif  // EDGE_SE3_PRIORZ_HPP\n"
  },
  {
    "path": "include/g2o/edge_se3_se3.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef KKL_G2O_EDGE_SE3_SE3_HPP\n#define KKL_G2O_EDGE_SE3_SE3_HPP\n\n#include <g2o/types/slam3d/edge_se3.h>\n#include <g2o/types/slam3d/vertex_se3.h>\n// #include <g2o/types/sba/edge_se3_expmap.h>\n// #include <g2o/types/sba/vertex_se3_expmap.h>\n\n#include <Eigen/Dense>\n#include <math.h>\n\nnamespace g2o {\nclass EdgeSE3SE3 : public g2o::BaseBinaryEdge<6, g2o::SE3Quat, g2o::VertexSE3, g2o::VertexSE3> {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgeSE3SE3() : BaseBinaryEdge<6, g2o::SE3Quat, g2o::VertexSE3, g2o::VertexSE3>() {}\n\n  void computeError() override {\n    const g2o::VertexSE3* v1 = static_cast<const g2o::VertexSE3*>(_vertices[0]);\n    const g2o::VertexSE3* v2 = static_cast<const g2o::VertexSE3*>(_vertices[1]);\n\n    g2o::SE3Quat estimate1 (v1->estimate().rotation(), v1->estimate().translation());\n    g2o::SE3Quat estimate2 (v2->estimate().rotation(), v2->estimate().translation());\n\n    g2o::SE3Quat C(_measurement);\n    SE3Quat error= estimate2.inverse() * C * estimate1;\n    _error = error.log();\n\n\n    // const VertexSE3* v1 = dynamic_cast<const VertexSE3*>(_vertices[0]);\n    // const VertexSE3* v2 = dynamic_cast<const VertexSE3*>(_vertices[1]);\n    // SE3Quat delta = _measurement.inverse(). * (v1->estimate().inverse()*v2->estimate());\n    // _error.head<3>() = delta.translation();\n    // // The analytic Jacobians assume the error in this special form (w beeing positive)\n    // if (delta.rotation().w() < 0.)\n    //   _error.tail<3>() =  - delta.rotation().vec();\n    // else\n    //   _error.tail<3>() =  delta.rotation().vec();\n  }\n\n  // void linearizeOplus() override {\n  //   const g2o::VertexSE3* v1 = static_cast<const g2o::VertexSE3*>(_vertices[0]);\n  //   const g2o::VertexSE3* v2 = static_cast<const g2o::VertexSE3*>(_vertices[1]);\n\n  //   SE3Quat Ti (v1->estimate().rotation(), v1->estimate().translation());\n  //   SE3Quat Tj (v2->estimate().rotation(), v2->estimate().translation());\n\n  //   //注意这里把测量标记为Tij应该是标记错误了，应该是Tji，不然整个误差公式说不通了\n  //   //这个可以看orbslam EdgeSim3里添加测量时就是用的Sji\n  //   const SE3Quat & Tij = _measurement; // shoulb be Tji\n  //   SE3Quat invTij = Tij.inverse();\n\n  //   SE3Quat invTj_Tij = Tj.inverse()*Tij;\n  //   SE3Quat infTi_invTij = Ti.inverse()*invTij;\n\n  //   _jacobianOplusXi = invTj_Tij.adj();\n  //   _jacobianOplusXj = - infTi_invTij.adj();\n  // }\n\n\n  void setMeasurement(const g2o::SE3Quat& m) override {\n    _measurement = m;\n  }\n\n  virtual bool read(std::istream& is) override {\n    g2o::Vector6 vec6;\n    is >> vec6(0) >> vec6(1) >> vec6(2) >> vec6(3) >> vec6(4) >> vec6(5);\n    double q = sqrt(1.0-vec6(0)*vec6(0)-vec6(1)*vec6(1)-vec6(2)*vec6(2));\n    g2o::SE3Quat v(Eigen::Quaterniond(q,vec6(0),vec6(1),vec6(2)), Eigen::Vector3d(vec6(3),vec6(4),vec6(5)));\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    g2o::SE3Quat v = _measurement;\n    g2o::Vector6 vec6 = v.log();\n    os << vec6(0) << \" \" << vec6(1) << \" \" << vec6(2) << \" \" << vec6(3) << \" \" << vec6(4) << \" \" << vec6(5) << \" \";\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n};\n}  // namespace g2o\n\n#endif\n"
  },
  {
    "path": "include/g2o/edge_se3_z.hpp",
    "content": "// g2o - General Graph Optimization\n// Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard\n// All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n// * Redistributions of source code must retain the above copyright notice,\n//   this list of conditions and the following disclaimer.\n// * Redistributions in binary form must reproduce the above copyright\n//   notice, this list of conditions and the following disclaimer in the\n//   documentation and/or other materials provided with the distribution.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef EDGE_SE3_Z_HPP\n#define EDGE_SE3_Z_HPP\n\n#include <g2o/types/slam3d/types_slam3d.h>\n#include <g2o/types/slam3d_addons/types_slam3d_addons.h>\n\nnamespace Eigen {\ntypedef Eigen::Matrix<double, 1, 1> Vector1d;\n}\n\nnamespace g2o {\nclass EdgeSE3Z : public g2o::BaseBinaryEdge<1, Eigen::Vector1d, g2o::VertexSE3, g2o::VertexSE3> {\npublic:\n  \n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  EdgeSE3Z() : g2o::BaseBinaryEdge<1, Eigen::Vector1d, g2o::VertexSE3, g2o::VertexSE3>() {}\n\n  void computeError() override {\n    const g2o::VertexSE3* v1 = static_cast<const g2o::VertexSE3*>(_vertices[0]);\n    const g2o::VertexSE3* v2 = static_cast<const g2o::VertexSE3*>(_vertices[1]);\n\n    Eigen::Vector1d estimate = v2->estimate().translation().tail<1>() - v1->estimate().translation().tail<1>();\n    _error = estimate - _measurement; \n  }\n\n  void setMeasurement(const Eigen::Vector1d& m) override {\n    _measurement = m;\n  }\n\n  virtual bool read(std::istream& is) override {\n    Eigen::Vector1d v;\n    is >> v(0);\n    setMeasurement(v);\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) {\n        is >> information()(i, j);\n        if(i != j) information()(j, i) = information()(i, j);\n      }\n    return true;\n  }\n  virtual bool write(std::ostream& os) const override {\n    Eigen::Vector1d v = _measurement;\n    os << v(0) << \" \";\n    for(int i = 0; i < information().rows(); ++i)\n      for(int j = i; j < information().cols(); ++j) os << \" \" << information()(i, j);\n    return os.good();\n  }\n};\n}  // namespace g2o\n\n#endif  // EDGE_SE3_PRIORZ_HPP\n"
  },
  {
    "path": "include/g2o/robust_kernel_io.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef ROBUST_KERNEL_IO_HPP\n#define ROBUST_KERNEL_IO_HPP\n\n#include <string>\n#include <g2o/core/sparse_optimizer.h>\n\nnamespace g2o {\n\nstd::string kernel_type(g2o::RobustKernel* kernel);\n\nbool save_robust_kernels(const std::string& filename, g2o::SparseOptimizer* graph);\n\nbool load_robust_kernels(const std::string& filename, g2o::SparseOptimizer* graph);\n\n}  // namespace g2o\n\n#endif  // ROBUST_KERNEL_IO_HPP\n"
  },
  {
    "path": "include/radar_ego_velocity_estimator.h",
    "content": "// This file is part of RIO - Radar Inertial Odometry and Radar ego velocity estimation.\n// Copyright (C) 2021  Christopher Doer <christopher.doer@kit.edu>\n// (Institute of Control Systems, Karlsruhe Institute of Technology)\n\n// RIO is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n//(at your option) any later version.\n\n// RIO is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with RIO.  If not, see <https://www.gnu.org/licenses/>.\n\n#pragma once\n \n#include <sensor_msgs/PointCloud2.h>\n\n#include <rio_utils/data_types.h>\n#include <rio_utils/ros_helper.h>\n\n// #include <RadarEgoVelocityEstimatorConfig.h>\n\nnamespace rio\n{\n\nstruct RadarEgoVelocityEstimatorConfig\n{\n float min_dist = 1;\n float max_dist = 400;\n float min_db = 10;\n float elevation_thresh_deg = 22.5;\n float azimuth_thresh_deg = 56.5;\n float doppler_velocity_correction_factor = 1;\n \n float thresh_zero_velocity = 0.05; //Below this is recognized as inlier (m/s)\n float allowed_outlier_percentage = 0.30;\n float sigma_zero_velocity_x = 1.0e-03; // default Standard Deviation\n float sigma_zero_velocity_y = 3.2e-03;\n float sigma_zero_velocity_z = 1.0e-02;\n \n float sigma_offset_radar_x = 0;\n float sigma_offset_radar_y = 0;\n float sigma_offset_radar_z = 0;\n\n float max_sigma_x = 0.2;\n float max_sigma_y = 0.2;\n float max_sigma_z = 0.2;\n float max_r_cond;\n bool use_cholesky_instead_of_bdcsvd = true;\n\n bool use_ransac = true;\n float outlier_prob = 0.05; // Outlier Probability, to calculate ransac_iter_\n float success_prob = 0.995;\n float N_ransac_points = 5;\n float inlier_thresh = 0.5; // err(j) threshold, 0.1 too small, 1.0 too large\n};\n\nstruct RadarEgoVelocityEstimatorIndices\n{ \n  \n  uint x_r          = 0;\n  uint y_r          = 1;\n  uint z_r          = 2;\n  uint snr_db       = 3;\n  uint doppler      = 4;\n  uint range        = 5;\n  uint azimuth      = 6;\n  uint elevation    = 7;\n  uint normalized_x = 8;\n  uint normalized_y = 9;\n  uint normalized_z = 10;\n};\n\nclass RadarEgoVelocityEstimator\n{\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n\n  /**\n   * @brief RadarEgoVelocityEstimator constructor\n   */\n  RadarEgoVelocityEstimator() {\n    setRansacIter();\n    }\n\n  /**\n   * @brief Reconfigure callback\n   * @param config  has to contain RadarEgoVelocityEstimatorConfig\n   * @return\n   */\n  template <class ConfigContainingRadarEgoVelocityEstimatorConfig>\n  bool configure(ConfigContainingRadarEgoVelocityEstimatorConfig& config);\n\n  /**\n   * @brief Estimates the radar ego velocity based on a single radar scan\n   * @param[in] radar_scan_msg       radar scan\n   * @param[out] v_r                 estimated radar ego velocity\n   * @param[out] sigma_v_r           estimated sigmas of ego velocity\n   * @param[out] inlier_radar_scan   inlier point cloud\n   * @returns true if estimation successful\n   */\n  bool estimate(const sensor_msgs::PointCloud2& radar_scan_msg, Vector3& v_r, Vector3& sigma_v_r);\n  bool estimate(const sensor_msgs::PointCloud2& radar_scan_msg,\n                Vector3& v_r,\n                Vector3& sigma_v_r,\n                sensor_msgs::PointCloud2& inlier_radar_msg,\n                sensor_msgs::PointCloud2& outlier_radar_msg);\n\nprivate:\n  /**\n   * @brief Implementation of the ransac based estimation\n   * @param[in] radar_data          matrix of parsed radar scan --> see RadarEgoVelocityEstimatorIndices\n   * @param[out] v_r                estimated radar ego velocity\n   * @param[out] sigma_v_r          estimated sigmas of ego velocity\n   * @param[out] inlier_idx_best    idices of inlier\n   * @returns true if estimation successful\n   */\n  bool\n  solve3DFullRansac(const Matrix& radar_data, Vector3& v_r, Vector3& sigma_v_r, std::vector<uint>& inlier_idx_best, std::vector<uint>& outlier_idx_best);\n\n  /**\n   * @brief Estimates the radar ego velocity using all mesurements provided in radar_data\n   * @param[in] radar_data          matrix of parsed radar scan --> see RadarEgoVelocityEstimatorIndices\n   * @param[out] v_r                estimated radar ego velocity\n   * @param[out] sigma_v_r          estimated sigmas of ego velocity\n   * @param estimate_sigma          if true sigma will be estimated as well\n   * @returns true if estimation successful\n   */\n  bool solve3DFull(const Matrix& radar_data, Vector3& v_r, Vector3& sigma_v_r, bool estimate_sigma = true);\n\n  /**\n   * @brief Helper function which estiamtes the number of RANSAC iterations\n   */\n  void setRansacIter()\n  {\n    ransac_iter_ = uint((std::log(1.0 - config_.success_prob)) /\n                        std::log(1.0 - std::pow(1.0 - config_.outlier_prob, config_.N_ransac_points)));\n    ROS_INFO_STREAM(kPrefix << \"Number of Ransac iterations: \" << ransac_iter_);\n  }\n\n  const std::string kPrefix = \"[RadarEgoVelocityEstimator]: \";\n  const RadarEgoVelocityEstimatorIndices idx_;\n\n  RadarEgoVelocityEstimatorConfig config_;\n  uint ransac_iter_ = 0;\n};\n\ntemplate <class ConfigContainingRadarEgoVelocityEstimatorConfig>\nbool RadarEgoVelocityEstimator::configure(ConfigContainingRadarEgoVelocityEstimatorConfig& config)\n{\n  config_.min_dist                           = config.min_dist;\n  config_.max_dist                           = config.max_dist;\n  config_.min_db                             = config.min_db;\n  config_.elevation_thresh_deg               = config.elevation_thresh_deg;\n  config_.azimuth_thresh_deg                 = config.azimuth_thresh_deg;\n  config_.doppler_velocity_correction_factor = config.doppler_velocity_correction_factor;\n\n  config_.thresh_zero_velocity       = config.thresh_zero_velocity;\n  config_.allowed_outlier_percentage = config.allowed_outlier_percentage;\n  config_.sigma_zero_velocity_x      = config.sigma_zero_velocity_x;\n  config_.sigma_zero_velocity_y      = config.sigma_zero_velocity_y;\n  config_.sigma_zero_velocity_z      = config.sigma_zero_velocity_z;\n\n  config_.sigma_offset_radar_x = config.sigma_offset_radar_x;\n  config_.sigma_offset_radar_y = config.sigma_offset_radar_y;\n  config_.sigma_offset_radar_z = config.sigma_offset_radar_z;\n\n  config_.max_sigma_x                    = config.max_sigma_x;\n  config_.max_sigma_y                    = config.max_sigma_y;\n  config_.max_sigma_z                    = config.max_sigma_z;\n  config_.max_r_cond                     = config.max_r_cond;\n  config_.use_cholesky_instead_of_bdcsvd = config.use_cholesky_instead_of_bdcsvd;\n\n  config_.use_ransac      = config.use_ransac;\n  config_.outlier_prob    = config.outlier_prob;\n  config_.success_prob    = config.success_prob;\n  config_.N_ransac_points = config.N_ransac_points;\n  config_.inlier_thresh   = config.inlier_thresh;\n\n  setRansacIter();\n\n  ROS_INFO_STREAM(kPrefix << \"Number of Ransac iterations: \" << ransac_iter_);\n  return true;\n}\n}  // namespace rio\n"
  },
  {
    "path": "include/radar_graph_slam/graph_slam.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef GRAPH_SLAM_HPP\n#define GRAPH_SLAM_HPP\n\n#include <memory>\n#include <ros/time.h>\n\n#include <g2o/core/hyper_graph.h>\n\n#include <g2o/edge_se3_priorz.hpp>\n#include <g2o/edge_se3_z.hpp>\n#include <g2o/edge_se3_se3.hpp>\n#include <g2o/edge_se3_gt_utm.hpp>\n\nnamespace g2o {\nclass VertexSE3;\nclass VertexPlane;\nclass VertexPointXYZ;\nclass EdgeSE3;\nclass EdgeSE3Plane;\nclass EdgeSE3PointXYZ;\nclass EdgeSE3PriorXY;\nclass EdgeSE3PriorXYZ;\nclass EdgeSE3PriorVec;\nclass EdgeSE3PriorQuat;\nclass EdgePlane;\nclass EdgePlaneIdentity;\nclass EdgePlaneParallel;\nclass EdgePlanePerpendicular;\nclass EdgePlanePriorNormal;\nclass EdgePlanePriorDistance;\nclass RobustKernelFactory;\n}  // namespace g2o\n\nnamespace radar_graph_slam {\n\nclass GraphSLAM {\npublic:\n  GraphSLAM(const std::string& solver_type = \"lm_var\");\n  virtual ~GraphSLAM();\n\n  int num_vertices() const;\n  int num_edges() const;\n\n  void set_solver(const std::string& solver_type);\n\n  /**\n   * @brief add a SE3 node to the graph\n   * @param pose\n   * @return registered node\n   */\n  g2o::VertexSE3* add_se3_node(const Eigen::Isometry3d& pose);\n\n  /**\n   * @brief add a plane node to the graph\n   * @param plane_coeffs\n   * @return registered node\n   */\n  g2o::VertexPlane* add_plane_node(const Eigen::Vector4d& plane_coeffs);\n\n  /**\n   * @brief add a point_xyz node to the graph\n   * @param xyz\n   * @return registered node\n   */\n  g2o::VertexPointXYZ* add_point_xyz_node(const Eigen::Vector3d& xyz);\n\n  /**\n   * @brief add an edge between SE3 nodes\n   * @param v1  node1\n   * @param v2  node2\n   * @param relative_pose  relative pose between node1 and node2\n   * @param information_matrix  information matrix (it must be 6x6)\n   * @return registered edge\n   */\n  g2o::EdgeSE3* add_se3_edge(g2o::VertexSE3* v1, g2o::VertexSE3* v2, const Eigen::Isometry3d& relative_pose, const Eigen::MatrixXd& information_matrix);\n\n  g2o::EdgeSE3Prior* add_se3_prior_edge(g2o::VertexSE3* v_se3, const Eigen::Isometry3d& pose, const Eigen::MatrixXd& information_matrix);\n\n  g2o::EdgeSE3SE3* add_se3_se3_edge(g2o::VertexSE3* v1, g2o::VertexSE3* v2, const g2o::SE3Quat& relative_pose, const Eigen::MatrixXd& information_matrix);\n  /**\n   * @brief add an edge between an SE3 node and a plane node\n   * @param v_se3    SE3 node\n   * @param v_plane  plane node\n   * @param plane_coeffs  plane coefficients w.r.t. v_se3\n   * @param information_matrix  information matrix (it must be 3x3)\n   * @return registered edge\n   */\n  g2o::EdgeSE3Plane* add_se3_plane_edge(g2o::VertexSE3* v_se3, g2o::VertexPlane* v_plane, const Eigen::Vector4d& plane_coeffs, const Eigen::MatrixXd& information_matrix);\n\n  /**\n   * @brief add an edge between an SE3 node and a point_xyz node\n   * @param v_se3        SE3 node\n   * @param v_xyz        point_xyz node\n   * @param xyz          xyz coordinate\n   * @param information  information_matrix (it must be 3x3)\n   * @return registered edge\n   */\n  g2o::EdgeSE3PointXYZ* add_se3_point_xyz_edge(g2o::VertexSE3* v_se3, g2o::VertexPointXYZ* v_xyz, const Eigen::Vector3d& xyz, const Eigen::MatrixXd& information_matrix);\n\n  /**\n   * @brief add a prior edge to an SE3 node\n   * @param v_se3\n   * @param xy\n   * @param information_matrix\n   * @return\n   */\n  g2o::EdgeSE3GtUTM* add_se3_gt_utm_edge(g2o::VertexSE3* v_se3, const Eigen::Vector3d& gt_xyz, const Eigen::Vector3d& utm_xyz, const Eigen::MatrixXd& information_matrix);\n  g2o::EdgePlanePriorNormal* add_plane_normal_prior_edge(g2o::VertexPlane* v, const Eigen::Vector3d& normal, const Eigen::MatrixXd& information_matrix);\n\n  g2o::EdgePlanePriorDistance* add_plane_distance_prior_edge(g2o::VertexPlane* v, double distance, const Eigen::MatrixXd& information_matrix);\n\n  g2o::EdgeSE3PriorXY* add_se3_prior_xy_edge(g2o::VertexSE3* v_se3, const Eigen::Vector2d& xy, const Eigen::MatrixXd& information_matrix);\n\n  g2o::EdgeSE3PriorXYZ* add_se3_prior_xyz_edge(g2o::VertexSE3* v_se3, const Eigen::Vector3d& xyz, const Eigen::MatrixXd& information_matrix);\n\n  g2o::EdgeSE3PriorZ* add_se3_prior_z_edge(g2o::VertexSE3* v_se3, const Eigen::Vector1d& z, const Eigen::MatrixXd& information_matrix);\n\n  g2o::EdgeSE3Z* add_se3_z_edge(g2o::VertexSE3* v1, g2o::VertexSE3* v2, const Eigen::Vector1d& z, const Eigen::MatrixXd& information_matrix);\n\n  g2o::EdgeSE3PriorQuat* add_se3_prior_quat_edge(g2o::VertexSE3* v_se3, const Eigen::Quaterniond& quat, const Eigen::MatrixXd& information_matrix);\n\n  g2o::EdgeSE3PriorVec* add_se3_prior_vec_edge(g2o::VertexSE3* v_se3, const Eigen::Vector3d& direction, const Eigen::Vector3d& measurement, const Eigen::MatrixXd& information_matrix);\n\n  g2o::EdgePlane* add_plane_edge(g2o::VertexPlane* v_plane1, g2o::VertexPlane* v_plane2, const Eigen::Vector4d& measurement, const Eigen::Matrix4d& information);\n\n  g2o::EdgePlaneIdentity* add_plane_identity_edge(g2o::VertexPlane* v_plane1, g2o::VertexPlane* v_plane2, const Eigen::Vector4d& measurement, const Eigen::Matrix4d& information);\n\n  g2o::EdgePlaneParallel* add_plane_parallel_edge(g2o::VertexPlane* v_plane1, g2o::VertexPlane* v_plane2, const Eigen::Vector3d& measurement, const Eigen::Matrix3d& information);\n\n  g2o::EdgePlanePerpendicular* add_plane_perpendicular_edge(g2o::VertexPlane* v_plane1, g2o::VertexPlane* v_plane2, const Eigen::Vector3d& measurement, const Eigen::MatrixXd& information);\n\n  void add_robust_kernel(g2o::HyperGraph::Edge* edge, const std::string& kernel_type, double kernel_size);\n\n  /**\n   * @brief perform graph optimization\n   */\n  int optimize(int num_iterations);\n\n  /**\n   * @brief save the pose graph to a file\n   * @param filename  output filename\n   */\n  void save(const std::string& filename);\n\n  /**\n   * @brief load the pose graph from file\n   * @param filename  output filename\n   */\n  bool load(const std::string& filename);\n\npublic:\n  g2o::RobustKernelFactory* robust_kernel_factory;\n  std::unique_ptr<g2o::HyperGraph> graph;  // g2o graph\n};\n\n}  // namespace radar_graph_slam\n\n#endif  // GRAPH_SLAM_HPP\n"
  },
  {
    "path": "include/radar_graph_slam/information_matrix_calculator.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef INFORMATION_MATRIX_CALCULATOR_HPP\n#define INFORMATION_MATRIX_CALCULATOR_HPP\n\n#include <ros/ros.h>\n#include <pcl/point_types.h>\n#include <pcl/point_cloud.h>\n\nnamespace radar_graph_slam {\n\nclass InformationMatrixCalculator {\npublic:\n  using PointT = pcl::PointXYZI;\n\n  InformationMatrixCalculator() {}\n  InformationMatrixCalculator(ros::NodeHandle& nh);\n  ~InformationMatrixCalculator();\n\n  template<typename ParamServer>\n  void load(ParamServer& params) {\n    use_const_inf_matrix = params.template param<bool>(\"use_const_inf_matrix\", false);\n    const_stddev_x = params.template param<double>(\"const_stddev_x\", 0.5);\n    const_stddev_q = params.template param<double>(\"const_stddev_q\", 0.1);\n\n    var_gain_a = params.template param<double>(\"var_gain_a\", 20.0);\n    min_stddev_x = params.template param<double>(\"min_stddev_x\", 0.1);\n    max_stddev_x = params.template param<double>(\"max_stddev_x\", 5.0);\n    min_stddev_q = params.template param<double>(\"min_stddev_q\", 0.05);\n    max_stddev_q = params.template param<double>(\"max_stddev_q\", 0.2);\n    fitness_score_thresh = params.template param<double>(\"fitness_score_thresh\", 2.5);\n  }\n\n  static double calc_fitness_score(const pcl::PointCloud<PointT>::ConstPtr& cloud1, const pcl::PointCloud<PointT>::ConstPtr& cloud2, const Eigen::Isometry3d& relpose, double max_range = std::numeric_limits<double>::max());\n\n  Eigen::MatrixXd calc_information_matrix(const pcl::PointCloud<PointT>::ConstPtr& cloud1, const pcl::PointCloud<PointT>::ConstPtr& cloud2, const Eigen::Isometry3d& relpose) const;\n\nprivate:\n  double weight(double a, double max_x, double min_y, double max_y, double x) const {\n    double y = (1.0 - std::exp(-a * x)) / (1.0 - std::exp(-a * max_x));\n    return min_y + (max_y - min_y) * y;\n  }\n\nprivate:\n  bool use_const_inf_matrix;\n  double const_stddev_x;\n  double const_stddev_q;\n\n  double var_gain_a;\n  double min_stddev_x;\n  double max_stddev_x;\n  double min_stddev_q;\n  double max_stddev_q;\n  double fitness_score_thresh;\n};\n\n}  // namespace radar_graph_slam\n\n#endif  // INFORMATION_MATRIX_CALCULATOR_HPP\n"
  },
  {
    "path": "include/radar_graph_slam/keyframe.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef KEYFRAME_HPP\n#define KEYFRAME_HPP\n\n#include <ros/ros.h>\n#include <pcl/point_types.h>\n#include <pcl/point_cloud.h>\n#include <boost/optional.hpp>\n\n#include <geometry_msgs/Transform.h>\n#include <sensor_msgs/Imu.h>\n\n#include <g2o/edge_se3_priorz.hpp>\n\nnamespace g2o {\nclass VertexSE3;\nclass HyperGraph;\nclass SparseOptimizer;\n}  // namespace g2o\n\nnamespace radar_graph_slam {\n\n/**\n * @brief KeyFrame (pose node)\n */\nstruct KeyFrame {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  using PointT = pcl::PointXYZI;\n  using Ptr = std::shared_ptr<KeyFrame>;\n\n  KeyFrame(const size_t index, const ros::Time& stamp, const Eigen::Isometry3d& odom_scan2scan, double accum_distance, const pcl::PointCloud<PointT>::ConstPtr& cloud);\n  KeyFrame(const std::string& directory, g2o::HyperGraph* graph);\n  virtual ~KeyFrame();\n\n  void save(const std::string& directory);\n  bool load(const std::string& directory, g2o::HyperGraph* graph);\n\n  long id() const;\n  Eigen::Isometry3d estimate() const;\n\npublic:\n  size_t index;\n  ros::Time stamp;                                // timestamp\n  Eigen::Isometry3d odom_scan2scan;               // odometry (estimated by scan_matching_odometry)\n  Eigen::Isometry3d odom_scan2map;\n  double accum_distance;                          // accumulated distance from the first node (by scan_matching_odometry)\n  pcl::PointCloud<PointT>::ConstPtr cloud;        // point cloud\n  boost::optional<Eigen::Vector4d> floor_coeffs;  // detected floor's coefficients\n  boost::optional<Eigen::Vector3d> utm_coord;     // UTM coord obtained by GPS\n  boost::optional<Eigen::Vector1d> altitude;      // Altitude (Filtered) obtained by Barometer\n\n  boost::optional<Eigen::Vector3d> acceleration;    //\n  boost::optional<Eigen::Quaterniond> orientation;  //\n\n  geometry_msgs::Transform trans_integrated; // relative transform obtained by imu preintegration\n\n  boost::optional<sensor_msgs::Imu> imu; // the IMU message close to keyframe_\n\n  g2o::VertexSE3* node;  // node instance\n};\n\n/**\n * @brief KeyFramesnapshot for map cloud generation\n */\nstruct KeyFrameSnapshot {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n\n  using PointT = KeyFrame::PointT;\n  using Ptr = std::shared_ptr<KeyFrameSnapshot>;\n\n  KeyFrameSnapshot(const KeyFrame::Ptr& key);\n  KeyFrameSnapshot(const Eigen::Isometry3d& pose, const pcl::PointCloud<PointT>::ConstPtr& cloud);\n\n  ~KeyFrameSnapshot();\n\npublic:\n  Eigen::Isometry3d pose;                   // pose estimated by graph optimization\n  pcl::PointCloud<PointT>::ConstPtr cloud;  // point cloud\n};\n\n}  // namespace radar_graph_slam\n\n#endif  // KEYFRAME_HPP\n"
  },
  {
    "path": "include/radar_graph_slam/keyframe_updater.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef KEYFRAME_UPDATER_HPP\n#define KEYFRAME_UPDATER_HPP\n\n#include <ros/ros.h>\n#include <Eigen/Dense>\n\n#include <sensor_msgs/PointCloud2.h>\n\nnamespace radar_graph_slam {\n\n/**\n * @brief this class decides if a new frame should be registered to the pose graph as a keyframe\n */\nclass KeyframeUpdater {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n\n  /**\n   * @brief constructor\n   * @param pnh\n   */\n  KeyframeUpdater(ros::NodeHandle& pnh) : is_first(true), prev_keypose(Eigen::Isometry3d::Identity()) {\n    keyframe_delta_trans = pnh.param<double>(\"keyframe_delta_trans\", 2.0);\n    keyframe_delta_angle = pnh.param<double>(\"keyframe_delta_angle\", 2.0);\n    // keyframe_delta_time = pnh.param<double>(\"keyframe_delta_time\", 2.0);\n    keyframe_min_size = pnh.param<int>(\"keyframe_min_size\", 1000);\n\n    accum_distance = 0.0;\n  }\n\n  /**\n   * @brief decide if a new frame should be registered to the graph\n   * @param pose  pose of the frame\n   * @return  if true, the frame should be registered\n   */\n  bool decide(const Eigen::Isometry3d& pose, const ros::Time& stamp) {\n    // first frame is always registered to the graph\n    if(is_first) {\n      is_first = false;\n      prev_keypose = pose;\n      prev_keytime = stamp.toSec();\n      return true;\n    }\n    \n    // calculate the delta transformation from the previous keyframe\n    Eigen::Isometry3d delta = prev_keypose.inverse() * pose;\n    double dx = delta.translation().norm();\n    double da = Eigen::AngleAxisd(delta.linear()).angle();\n    // double dt = stamp.toSec() - prev_keytime;\n\n    // too close to the previous frame\n    if((dx < keyframe_delta_trans && da < keyframe_delta_angle)) {\n      return false;\n    }\n\n    accum_distance += dx;\n    prev_keypose = pose;\n    prev_keytime = stamp.toSec();\n\n    return true;\n  }\n\n  /**\n   * @brief the last keyframe's accumulated distance from the first keyframe\n   * @return accumulated distance\n   */\n  double get_accum_distance() const {\n    return accum_distance;\n  }\n\nprivate:\n  // parameters\n  double keyframe_delta_trans;  //\n  double keyframe_delta_angle;  //\n  std::size_t keyframe_min_size;  //\n\n  bool is_first;\n  double accum_distance;\n  Eigen::Isometry3d prev_keypose;\n  double prev_keytime;\n};\n\n}  // namespace radar_graph_slam\n\n#endif  // KEYFRAME_UPDATOR_HPP\n"
  },
  {
    "path": "include/radar_graph_slam/loop_detector.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef LOOP_DETECTOR_HPP\n#define LOOP_DETECTOR_HPP\n\n#include <math.h>\n\n#include <boost/format.hpp>\n\n#include <radar_graph_slam/keyframe.hpp>\n#include <radar_graph_slam/registrations.hpp>\n#include <radar_graph_slam/graph_slam.hpp>\n#include <radar_graph_slam/information_matrix_calculator.hpp>\n#include <scan_context/Scancontext.h>\n\n#include <g2o/types/slam3d/vertex_se3.h>\n\n#include <image_transport/image_transport.h>\n#include <opencv2/highgui/highgui.hpp>\n#include <cv_bridge/cv_bridge.h>\n\nusing namespace std;\n\nnamespace radar_graph_slam {\n\nstruct Loop {\npublic:\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n  using Ptr = std::shared_ptr<Loop>;\n\n  Loop(const KeyFrame::Ptr& key1, const KeyFrame::Ptr& key2, const Eigen::Matrix4f& relpose) : key1(key1), key2(key2), relative_pose(relpose) {}\n\npublic:\n  KeyFrame::Ptr key1;\n  KeyFrame::Ptr key2;\n  Eigen::Matrix4f relative_pose;\n};\n\n/**\n * @brief this class finds loops by scam matching and adds them to the pose graph\n */\nclass LoopDetector {\npublic:\n  typedef pcl::PointXYZI PointT;\n\n  /**\n   * @brief constructor\n   * @param pnh\n   */\n  LoopDetector() {}\n  LoopDetector(ros::NodeHandle& pnh);\n  ~LoopDetector();\n\n  std::vector<Loop::Ptr> detect(const std::vector<KeyFrame::Ptr>& keyframes, const std::deque<KeyFrame::Ptr>& new_keyframes, radar_graph_slam::GraphSLAM& graph_slam);\n\n  double get_distance_thresh() const;\n\n  Loop::Ptr performScanContextLoopClosure(const std::vector<KeyFrame::Ptr>& keyframes, const std::vector<KeyFrame::Ptr>& candidate_keyframes, const KeyFrame::Ptr& new_keyframe);\n\nprivate:\n  std::vector<KeyFrame::Ptr> find_candidates(const std::vector<KeyFrame::Ptr>& keyframes, const KeyFrame::Ptr& new_keyframe) const;\n  cv::Mat getColorImage(cv::Mat &desc);\n  typedef std::tuple<u_char, u_char, u_char> Color;\n  std::map<uint32_t, Color> _argmax_to_rgb;\n  cv::Mat makeSCImage(Eigen::MatrixXd src_mat);\n\nprivate:\n  double distance_thresh;                 // estimated distance between keyframes consisting a loop must be less than this distance\n  double accum_distance_thresh;           // Minimum distance beteen two edges of the loop\n  double min_loop_interval_dist;  // Minimum distance between a new loop edge and the last one\n\n  double odom_drift_xy;     // Odometry drift along X and Y axis\n  double odom_drift_z;      // Odometry drift along Z axis\n  double drift_scale_xy;       // Odometry drift scale\n  double drift_scale_z;\n\n  double max_baro_difference;\n  double max_yaw_difference;\n  \n  double fitness_score_max_range;  // maximum allowable distance between corresponding points\n  double fitness_score_thresh;     // threshold for scan matching\n\n  double last_loop_edge_accum_distance = 0.0;\n  size_t last_loop_edge_index = 0;\n\n  pcl::Registration<PointT, PointT>::Ptr registration;\n\npublic:\n  // Loop closure\n  std::unique_ptr<SCManager> scManager;  // loop detector\n  std::unique_ptr<InformationMatrixCalculator> inf_calclator;\n  int historyKeyframeSearchNum;\n  float historyKeyframeFitnessScore;\n  std::multimap<int, int> loopIndexContainer; // from new to old // giseop \n  std::vector<pair<int, int>> loopIndexQueue;\n  std::vector<Eigen::Isometry3d> loopPoseQueue;\n  std::vector<Eigen::MatrixXd> loopInfoQueue;\n  std::mutex mtx;\n  double odom_check_trans_thresh;\n  double odom_check_rot_thresh;\n  double pairwise_check_trans_thresh;\n  double pairwise_check_rot_thresh;\n\n  bool enable_pf;\n  bool enable_odom_check;\n\n  std::vector<double> pf_time;\n  std::vector<double> sc_time;\n  std::vector<double> oc_time;\n  clock_t start_ms_pf;\n  clock_t end_ms_pf;\n  clock_t start_ms_sc;\n  clock_t end_ms_sc;\n  clock_t start_ms_oc;\n  clock_t end_ms_oc;\n  std::unique_ptr<image_transport::ImageTransport> image_transport;\n  image_transport::Publisher pub_cur_sc;\n  image_transport::Publisher pub_pre_sc;\n};\n\n}  // namespace radar_graph_slam\n\n#endif  // LOOP_DETECTOR_HPP\n"
  },
  {
    "path": "include/radar_graph_slam/map_cloud_generator.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef MAP_CLOUD_GENERATOR_HPP\n#define MAP_CLOUD_GENERATOR_HPP\n\n#include <vector>\n#include <pcl/point_types.h>\n#include <pcl/point_cloud.h>\n#include <radar_graph_slam/keyframe.hpp>\n\nnamespace radar_graph_slam {\n\n/**\n * @brief this class generates a map point cloud from registered keyframes\n */\nclass MapCloudGenerator {\npublic:\n  using PointT = pcl::PointXYZI;\n\n  MapCloudGenerator();\n  ~MapCloudGenerator();\n\n  /**\n   * @brief generates a map point cloud\n   * @param keyframes   snapshots of keyframes\n   * @param resolution  resolution of generated map\n   * @return generated map point cloud\n   */\n  pcl::PointCloud<PointT>::Ptr generate(const std::vector<KeyFrameSnapshot::Ptr>& keyframes, double resolution) const;\n};\n\n}  // namespace radar_graph_slam\n\n#endif  // MAP_POINTCLOUD_GENERATOR_HPP\n"
  },
  {
    "path": "include/radar_graph_slam/nmea_sentence_parser.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef NMEA_SENTENCE_PARSER_HPP\n#define NMEA_SENTENCE_PARSER_HPP\n\n#include <cmath>\n#include <string>\n#include <vector>\n#include <numeric>\n#include <boost/algorithm/string.hpp>\n\nnamespace radar_graph_slam {\n\nstruct GPRMC {\npublic:\n  GPRMC() {\n    status = 'V';\n  }\n\n  GPRMC(const std::vector<std::string>& tokens) {\n    if(tokens[0] != \"$GPRMC\" || tokens.size() < 12) {\n      status = 'V';\n      return;\n    }\n\n    long time = std::stol(tokens[1]);\n    hour = time / 10000;\n    minute = (time % 10000) / 100;\n    second = time % 100;\n\n    status = tokens[2][0];\n\n    latitude = degmin2deg(std::stod(tokens[3]));\n    latitude = tokens[4] == \"N\" ? latitude : -latitude;\n\n    longitude = degmin2deg(std::stod(tokens[5]));\n    longitude = tokens[6] == \"E\" ? longitude : -longitude;\n\n    speed_knots = std::stod(tokens[7]);\n    track_angle_degree = std::stod(tokens[8]);\n\n    long date = std::stol(tokens[9]);\n    year = date % 100;\n    month = (date / 100) % 100;\n    day = (date / 10000) % 100;\n\n    magnetic_variation = std::stod(tokens[10]);\n    magnetic_variation = tokens[11][0] == 'E' ? magnetic_variation : -magnetic_variation;\n  }\n\n  double degmin2deg(double degmin) {\n    double d = std::floor(degmin / 100.0);\n    double m = (degmin - d * 100.0) / 60.0;\n    return d + m;\n  }\n\npublic:\n  char status;  // Status A=active or V=Void.\n\n  int hour;  // Fix taken at 12:35:19 UTC\n  int minute;\n  int second;\n\n  double latitude;  //\n  double longitude;\n\n  double speed_knots;         // Speed over the ground in knots\n  double track_angle_degree;  // Track angle in degrees True\n\n  int year;\n  int month;\n  int day;\n\n  double magnetic_variation;\n};\n\nclass NmeaSentenceParser {\npublic:\n  NmeaSentenceParser() {}\n  ~NmeaSentenceParser() {}\n\n  GPRMC parse(const std::string& sentence) const {\n    int checksum_loc = sentence.find('*');\n    if(checksum_loc == std::string::npos) {\n      return GPRMC();\n    }\n\n    int checksum = std::stoul(sentence.substr(checksum_loc + 1), nullptr, 16);\n\n    std::string substr = sentence.substr(1, checksum_loc - 1);\n    int sum = std::accumulate(substr.begin(), substr.end(), static_cast<unsigned char>(0), [=](unsigned char n, unsigned char c) { return n ^ c; });\n\n    if(checksum != (sum & 0xf)) {\n      std::cerr << \"checksum doesn't match!!\" << std::endl;\n      std::cerr << sentence << \" \" << sum << std::endl;\n      return GPRMC();\n    }\n\n    std::vector<std::string> tokens;\n    boost::split(tokens, sentence, boost::is_any_of(\",\"));\n\n    return GPRMC(tokens);\n  }\n};\n\n}  // namespace radar_graph_slam\n\n#endif  // NMEA_SENTENCE_PARSER_HPP\n"
  },
  {
    "path": "include/radar_graph_slam/polynomial_interpolation.hpp",
    "content": "\n#include <ros/ros.h>\n\n#include <Eigen/Dense>\n\nclass CubicInterpolation {\n    public:\n    Eigen::MatrixXd q_via;\n    Eigen::VectorXd t_via;\n    CubicInterpolation(Eigen::MatrixXd q_via_, Eigen::VectorXd t_via_){\n        /*\n        :param: name: string\n            name of objective\n        :param: q_via: N x 3 array\n            given q array\n        :param: t_via: N x 1 array\n            given t array\n        */\n        q_via = q_via_;\n        t_via = t_via_;\n        if(q_via.rows() != t_via.rows())\n            ROS_ERROR(\"%s\",\"The q_via and t_via must have a same length\");\n    }\n    virtual ~CubicInterpolation() {}\n    private:\n    int cnt = 0;\n    // void resetIntegration() {\n\n    // }\n    public:\n    Eigen::Vector4d cubic(double q0, double q1, double v0, double v1, double t0, double t1){\n        /*\n        :param: q0: float\n            the first data point\n        :param: q1: float\n            the second data point\n        :param: v0: float\n            the velocity of the first data point\n        :param: v1: float\n            the velocity of the second data point\n        :param: t0: float\n            the time of the first data point\n        :param: t1: float\n            the time of the second data point\n        */\n        if(abs(t0 - t1) < 1e-6)\n            ROS_ERROR(\"%s\",\"t0 and t1 must be different\");\n\n        double T = t1 - t0;\n        double h = q1 - q0;\n\n        double a0 = q0;\n        double a1 = v0;\n        double a2 = (3*h - (2*v0 + v1)*T) / (T*T);\n        double a3 = (-2*h + (v0 + v1)*T) / (T*T*T);\n        return Eigen::Vector4d(a0, a1, a2, a3);\n    }\n\n    double getPosition(double t){\n        /*\n        :param: t: float\n            specified time\n        :return: q: float\n            output of the interpolation at time t\n        */\n        if(t < t_via(0) || t > t_via(t_via.size()-1))\n            ROS_ERROR(\"%s\",\"The specific time error, time ranges error\");\n\n        // j_array = np.where(self.t_via >= t); // find the index of t1\n        int j;// = j_array[0][0];\n        for (int i = 0; i < t_via.rows(); i++){\n            if (t > t_via(i));\n            else {j=i; break;}\n        }\n        int i;\n        if (j == 0) {i = 0; j = 1;}\n        else i = j-1;\n\n        // get given position\n        double q0 = q_via(i,0);\n        double v0 = q_via(i,1);\n        double t0 = t_via(i);\n\n        double q1 = q_via(j,0);\n        double v1 = q_via(j,1);\n        double t1 = t_via(j);\n\n        Eigen::Vector4d a = cubic(q0, q1, v0, v1, t0, t1);\n\n        Eigen::Vector3d q;\n        q(0) = a(0) + a(1)*(t - t0) + a(2)*pow(t-t0, 2) + a(3)*pow(t-t0, 3); // position\n        // q(1) = a(1) + 2*a(2)*(t - t0) + 3*a(3)*pow(t-t0, 2); // velocity\n        // q(2) = 2*a(2) + 6*a(3)*(t - t0); // acceleration\n\n        return q(0);\n    }\n};\n\n"
  },
  {
    "path": "include/radar_graph_slam/registrations.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef HDL_GRAPH_SLAM_REGISTRATIONS_HPP\n#define HDL_GRAPH_SLAM_REGISTRATIONS_HPP\n\n#include <ros/ros.h>\n\n#include <pcl/registration/registration.h>\n\nnamespace radar_graph_slam {\n\n/**\n * @brief select a scan matching algorithm according to rosparams\n * @param pnh\n * @return selected scan matching\n */\npcl::Registration<pcl::PointXYZI, pcl::PointXYZI>::Ptr select_registration_method(ros::NodeHandle& pnh);\n\n}  // namespace radar_graph_slam\n\n#endif  //\n"
  },
  {
    "path": "include/radar_graph_slam/ros_time_hash.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef ROS_TIME_HASH_HPP\n#define ROS_TIME_HASH_HPP\n\n#include <unordered_map>\n#include <boost/functional/hash.hpp>\n\n#include <ros/time.h>\n\n/**\n * @brief Hash calculation for ros::Time\n */\nclass RosTimeHash {\npublic:\n  size_t operator()(const ros::Time& val) const {\n    size_t seed = 0;\n    boost::hash_combine(seed, val.sec);\n    boost::hash_combine(seed, val.nsec);\n    return seed;\n  }\n};\n\n#endif  // ROS_TIME_HASHER_HPP\n"
  },
  {
    "path": "include/radar_graph_slam/ros_utils.hpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#ifndef ROS_UTILS_HPP\n#define ROS_UTILS_HPP\n\n#include <Eigen/Dense>\n\n#include <ros/ros.h>\n#include <nav_msgs/Odometry.h>\n#include <geometry_msgs/Pose.h>\n#include <geometry_msgs/TransformStamped.h>\n#include <tf_conversions/tf_eigen.h>\n\nnamespace radar_graph_slam {\n\nextern const int imuQueLength = 200;\nextern const float scanPeriod = 0.083333;\n\ndouble restrict_rad(double rad){\n  double out;\n  if (rad < -M_PI/2)\n    out = rad + M_PI;\n  else if (rad > M_PI/2)\n    out = rad - M_PI;\n  else out = rad;\n  return out;\n}\n\nEigen::Vector3d R2ypr(const Eigen::Matrix3d& R) {\n  Eigen::Vector3d n = R.col(0);\n  Eigen::Vector3d o = R.col(1);\n  Eigen::Vector3d a = R.col(2);\n\n  Eigen::Vector3d ypr(3);\n  double y = atan2(n(1), n(0));\n  double p = atan2(-n(2), n(0) * cos(y) + n(1) * sin(y));\n  double r = atan2(a(0) * sin(y) - a(1) * cos(y), -o(0) * sin(y) + o(1) * cos(y));\n  ypr(0) = y;\n  ypr(1) = p;\n  ypr(2) = r;\n\n  return ypr;\n}\n\n\n/**\n * @brief convert Eigen::Matrix to geometry_msgs::TransformStamped\n * @param stamp            timestamp\n * @param pose             Eigen::Matrix to be converted\n * @param frame_id         tf frame_id\n * @param child_frame_id   tf child frame_id\n * @return converted TransformStamped\n */\nstatic geometry_msgs::TransformStamped matrix2transform(const ros::Time& stamp, const Eigen::Matrix4d& pose, const std::string& frame_id, const std::string& child_frame_id) {\n  Eigen::Quaterniond quat(pose.block<3, 3>(0, 0));\n  quat.normalize();\n  geometry_msgs::Quaternion odom_quat;\n  odom_quat.w = quat.w();\n  odom_quat.x = quat.x();\n  odom_quat.y = quat.y();\n  odom_quat.z = quat.z();\n\n  geometry_msgs::TransformStamped odom_trans;\n  odom_trans.header.stamp = stamp;\n  odom_trans.header.frame_id = frame_id;\n  odom_trans.child_frame_id = child_frame_id;\n\n  odom_trans.transform.translation.x = pose(0, 3);\n  odom_trans.transform.translation.y = pose(1, 3);\n  odom_trans.transform.translation.z = pose(2, 3);\n  odom_trans.transform.rotation = odom_quat;\n\n  return odom_trans;\n}\n\nstatic nav_msgs::Odometry matrix2odom(const ros::Time& stamp, const Eigen::Matrix4d& pose, const std::string& frame_id, const std::string& child_frame_id){\n  Eigen::Quaterniond quat(pose.block<3, 3>(0, 0));\n  quat.normalize();\n  geometry_msgs::Quaternion odom_quat;\n  odom_quat.w = quat.w();\n  odom_quat.x = quat.x();\n  odom_quat.y = quat.y();\n  odom_quat.z = quat.z();\n  \n  nav_msgs::Odometry odom;\n  odom.header.stamp = stamp;\n  odom.header.frame_id = frame_id;\n  odom.child_frame_id = child_frame_id;\n\n  odom.pose.pose.position.x = pose(0, 3);\n  odom.pose.pose.position.y = pose(1, 3);\n  odom.pose.pose.position.z = pose(2, 3);\n  odom.pose.pose.orientation = odom_quat;\n  \n  return odom;\n}\n\nstatic Eigen::Isometry3d pose2isometry(const geometry_msgs::Pose& pose) {\n  Eigen::Isometry3d mat = Eigen::Isometry3d::Identity();\n  mat.translation() = Eigen::Vector3d(pose.position.x, pose.position.y, pose.position.z);\n  mat.linear() = Eigen::Quaterniond(pose.orientation.w, pose.orientation.x, pose.orientation.y, pose.orientation.z).toRotationMatrix();\n  return mat;\n}\n\nstatic Eigen::Isometry3d transform2isometry(const geometry_msgs::Transform& transf) {\n  Eigen::Isometry3d mat = Eigen::Isometry3d::Identity();\n  mat.translation() = Eigen::Vector3d(transf.translation.x, transf.translation.y, transf.translation.z);\n  mat.linear() = Eigen::Quaterniond(transf.rotation.w, transf.rotation.x, transf.rotation.y, transf.rotation.z).toRotationMatrix();\n  return mat;\n}\n\nstatic Eigen::Isometry3d tf2isometry(const tf::StampedTransform& trans) {\n  Eigen::Isometry3d mat = Eigen::Isometry3d::Identity();\n  mat.translation() = Eigen::Vector3d(trans.getOrigin().x(), trans.getOrigin().y(), trans.getOrigin().z());\n  mat.linear() = Eigen::Quaterniond(trans.getRotation().w(), trans.getRotation().x(), trans.getRotation().y(), trans.getRotation().z()).toRotationMatrix();\n  return mat;\n}\n\nstatic Eigen::Isometry3d quaternion2isometry(const Eigen::Quaterniond& orient) {\n  Eigen::Isometry3d mat = Eigen::Isometry3d::Identity();\n  mat.linear() = Eigen::Quaterniond(orient.w(), orient.x(), orient.y(), orient.z()).toRotationMatrix();\n  return mat;\n}\n\nstatic geometry_msgs::Pose isometry2pose(const Eigen::Isometry3d& mat) {\n  Eigen::Quaterniond quat(mat.linear());\n  Eigen::Vector3d trans = mat.translation();\n\n  geometry_msgs::Pose pose;\n  pose.position.x = trans.x();\n  pose.position.y = trans.y();\n  pose.position.z = trans.z();\n  pose.orientation.w = quat.w();\n  pose.orientation.x = quat.x();\n  pose.orientation.y = quat.y();\n  pose.orientation.z = quat.z();\n\n  return pose;\n}\n\nstatic nav_msgs::Odometry isometry2odom(const ros::Time& stamp, const Eigen::Isometry3d& mat, const std::string& frame_id, const std::string& child_frame_id) {\n  Eigen::Quaterniond quat(mat.linear());\n  Eigen::Vector3d trans = mat.translation();\n  nav_msgs::Odometry odom;\n  odom.header.stamp = stamp;\n  odom.header.frame_id = frame_id;\n  odom.child_frame_id = child_frame_id;\n  odom.pose.pose.position.x = trans.x();\n  odom.pose.pose.position.y = trans.y();\n  odom.pose.pose.position.z = trans.z();\n  odom.pose.pose.orientation.w = quat.w();\n  odom.pose.pose.orientation.x = quat.x();\n  odom.pose.pose.orientation.y = quat.y();\n  odom.pose.pose.orientation.z = quat.z();\n\n  return odom;\n}\n\nstatic Eigen::Isometry3d odom2isometry(const nav_msgs::OdometryConstPtr& odom_msg) {\n  const auto& orientation = odom_msg->pose.pose.orientation;\n  const auto& position = odom_msg->pose.pose.position;\n\n  Eigen::Quaterniond quat;\n  quat.w() = orientation.w;\n  quat.x() = orientation.x;\n  quat.y() = orientation.y;\n  quat.z() = orientation.z;\n\n  Eigen::Isometry3d isometry = Eigen::Isometry3d::Identity();\n  isometry.linear() = quat.toRotationMatrix();\n  isometry.translation() = Eigen::Vector3d(position.x, position.y, position.z);\n  return isometry;\n}\n\n}  // namespace radar_graph_slam\n\n#endif  // ROS_UTILS_HPP\n"
  },
  {
    "path": "include/rio_utils/data_types.h",
    "content": "// This file is part of RIO - Radar Inertial Odometry and Radar ego velocity estimation.\n// Copyright (C) 2021  Christopher Doer <christopher.doer@kit.edu>\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n//(at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n#pragma once\n\n#include <angles/angles.h>\n#include <eigen3/Eigen/Dense>\n#include <ros/time.h>\n#include <sensor_msgs/Imu.h>\n\n#include <tf2/LinearMath/Quaternion.h>\n#include <tf2/LinearMath/Matrix3x3.h>\n#include <tf2_eigen/tf2_eigen.h>\n\nnamespace rio\n{\ntypedef double Real;\n\ntypedef Eigen::Vector2d Vector2;\ntypedef Eigen::Vector3d Vector3;\ntypedef Eigen::Vector4d Vector4;\ntypedef Eigen::Matrix<double, 7, 1> Vector7;\ntypedef Eigen::Matrix<double, 11, 1> Vector11;\ntypedef Eigen::VectorXd Vector;\ntypedef Eigen::Matrix2d Matrix2;\ntypedef Eigen::Matrix3d Matrix3;\ntypedef Eigen::Matrix4d Matrix4;\ntypedef Eigen::MatrixXd Matrix;\ntypedef Eigen::Quaterniond Quaternion;\ntypedef Eigen::Isometry3d Isometry;\ntypedef Eigen::AngleAxisd AngleAxis;\n\nstruct EulerAngles : public Vector3\n{\n  EulerAngles() : Vector3(0, 0, 0) {}\n\n  EulerAngles(const Real roll, const Real pitch, const Real yaw) : Vector3(roll, pitch, yaw) {}\n\n  EulerAngles(const Vector3& eul_n_b) : Vector3(eul_n_b) {}\n\n  EulerAngles from_degrees(const Vector3& eul_rad)\n  {\n    x() = angles::from_degrees(eul_rad.x());\n    y() = angles::from_degrees(eul_rad.y());\n    z() = angles::from_degrees(eul_rad.z());\n    return EulerAngles(x(), y(), z());\n  }\n\n  Vector3 to_degrees() { return Vector3(angles::to_degrees(x()), angles::to_degrees(y()), angles::to_degrees(z())); }\n\n  Real& roll() { return Vector3::x(); }\n  Real roll() const { return Vector3::x(); }\n\n  Real& pitch() { return Vector3::y(); }\n  Real pitch() const { return Vector3::y(); }\n\n  Real& yaw() { return Vector3::z(); }\n  Real yaw() const { return Vector3::z(); }\n};\n\nstruct NavigationSolution\n{\n  NavigationSolution()\n  {\n    pose_n_b.linear().setIdentity();\n    pose_n_b.translation().setZero();\n    v_n_b.setZero();\n  }\n  NavigationSolution(const Isometry& pose_n_b, const Vector3& v_n_b) : pose_n_b{pose_n_b}, v_n_b{v_n_b} {}\n  NavigationSolution(const Vector3& p_n_b, const Quaternion& q_n_b, const Vector3 v_n_b) : v_n_b{v_n_b}\n  {\n    setQuaternion(q_n_b);\n    setPosition_n_b(p_n_b);\n  }\n\n  Vector3 getPosition_n_b() const { return pose_n_b.translation(); }\n\n  void setPosition_n_b(const Vector3& p_n_b) { pose_n_b.translation() = p_n_b; }\n\n  Quaternion getQuaternion_n_b() const { return Quaternion(pose_n_b.linear()); }\n\n  void setQuaternion(const Quaternion& q_n_b) { pose_n_b.linear() = q_n_b.normalized().toRotationMatrix(); }\n\n  EulerAngles getEuler_n_b() const\n  {\n    const Quaternion q = getQuaternion_n_b();\n    tf2::Quaternion q_msg(q.x(), q.y(), q.z(), q.w());\n    tf2::Matrix3x3 R(q_msg);\n    EulerAngles eul;\n    R.getEulerYPR(eul.yaw(), eul.pitch(), eul.roll());\n\n    return eul;\n  }\n\n  void setEuler_n_b(const EulerAngles& eul_n_b)\n  {\n    tf2::Quaternion q_n_b;\n    q_n_b.setRPY(eul_n_b.roll(), eul_n_b.pitch(), eul_n_b.yaw());\n    pose_n_b.linear() = Matrix3(Quaternion(q_n_b.w(), q_n_b.x(), q_n_b.y(), q_n_b.z()));\n  }\n\n  Matrix3 getC_n_b() const { return pose_n_b.linear(); }\n\n  Isometry getPoseRos() const\n  {\n    tf2::Quaternion q_n_b;\n    q_n_b.setRPY(M_PI, 0, 0);\n    return Matrix3(Quaternion(q_n_b.w(), q_n_b.x(), q_n_b.y(), q_n_b.z())) * pose_n_b;\n  }\n\n  Vector3 getVelocityRos() const { return Vector3(v_n_b.x(), -v_n_b.y(), -v_n_b.z()); }\n\n  Isometry pose_n_b;  // position and attitude in navigation frame\n  Vector3 v_n_b;      // [m/s]\n\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n};\n\nstruct ImuData\n{\n  ImuData() : dt{0} {}\n  ImuData(const double dt, const Vector3& a_b_ib, const Vector3& w_b_ib) : dt{dt}, a_b_ib{a_b_ib}, w_b_ib{w_b_ib} {}\n\n  Real dt;         // [s]\n  Vector3 a_b_ib;  // [m/s^2]\n  Vector3 w_b_ib;  // [rad/s]\n\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n};\n\nstruct ImuDataStamped\n{\n  ImuDataStamped() : dt{0} {}\n  ImuDataStamped(const ros::Time& time_stamp,\n                 const std::string frame_id,\n                 const double dt,\n                 const Vector3& a_b_ib,\n                 const Vector3& w_b_ib) :\n    time_stamp{time_stamp},\n    frame_id{frame_id},\n    dt{dt},\n    a_b_ib{a_b_ib},\n    w_b_ib{w_b_ib}\n  {\n  }\n\n  ImuDataStamped(const sensor_msgs::ImuConstPtr& imu_msg, const Real dt) :\n    time_stamp{imu_msg->header.stamp},\n    frame_id{imu_msg->header.frame_id},\n    dt{dt},\n    a_b_ib{Vector3(imu_msg->linear_acceleration.x, imu_msg->linear_acceleration.y, imu_msg->linear_acceleration.z)},\n    w_b_ib{Vector3(imu_msg->angular_velocity.x, imu_msg->angular_velocity.y, imu_msg->angular_velocity.z)}\n  {\n  }\n\n  sensor_msgs::Imu toImuMsg()\n  {\n    sensor_msgs::Imu imu_msg;\n    imu_msg.header.stamp          = time_stamp;\n    imu_msg.angular_velocity.x    = w_b_ib.x();\n    imu_msg.angular_velocity.y    = w_b_ib.y();\n    imu_msg.angular_velocity.z    = w_b_ib.z();\n    imu_msg.linear_acceleration.x = a_b_ib.x();\n    imu_msg.linear_acceleration.y = a_b_ib.y();\n    imu_msg.linear_acceleration.z = a_b_ib.z();\n    return imu_msg;\n  }\n\n  ros::Time time_stamp;  // ros::Time\n  std::string frame_id;  // frame id\n  Real dt;               // [s]\n  Vector3 a_b_ib;        // [m/s^2]\n  Vector3 w_b_ib;        // [rad/s]\n\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW\n};\n\n}  // namespace rio\n"
  },
  {
    "path": "include/rio_utils/math_helper.h",
    "content": "// This file is part of RIO - Radar Inertial Odometry and Radar ego velocity estimation.\n// Copyright (C) 2021  Christopher Doer <christopher.doer@kit.edu>\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n//(at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n#pragma once\n\n#include <Eigen/Dense>\n#include <rio_utils/data_types.h>\n\nnamespace rio\n{\nnamespace math_helper\n{\n/**\n * @brief Calculates the skew matrix from a 3D vector\n */\nstatic Matrix3 skewVec(const Vector3& v)\n{\n  Matrix3 S;\n  S << 0, -v.z(), v.y(), v.z(), 0, -v.x(), -v.y(), v.x(), 0;\n  return S;\n}\n\n/**\n * @brief Performs a quaternion multiplication following the Hamilton convention\n */\nstatic Quaternion quaternionMultiplicationHamilton(const Quaternion& q, const Quaternion& p)\n{\n  const Vector3 q_v(q.x(), q.y(), q.z());\n  const Vector3 p_v(p.x(), p.y(), p.z());\n\n  const Real q_p_w   = p.w() * q.w() - q_v.transpose() * p_v;\n  const Vector3 q_p_ = q.w() * p_v + p.w() * q_v + skewVec(q_v) * p_v;\n  return Quaternion(q_p_w, q_p_.x(), q_p_.y(), q_p_.z());\n}\n\n/**\n * @brief Wraps a scalar (e.g. an angle) to the intervall [0, max]\n */\nstatic Real wrapToPositive(const Real d, const Real max = 360)\n{\n  const auto m = std::fmod(d, max);\n  if (m < 0)\n    return m + max;\n  else\n    return m;\n}\n\n/**\n * @brief Wraps a scalar (e.g. an angle) to the intervall [-abs_max, abs_max]\n */\nstatic Real wrapToCentered(const Real d, const Real abs_max = 180)\n{\n  return wrapToPositive(d + abs_max, abs_max * 2) - abs_max;\n}\n\n/**\n * @brief Converts from Cartesian to Spherical coordinates\n * @param[in] v      Cartesian vector\n * @param [out] r    range\n * @param [out] azi  azimuth\n * @param [out] ele  elevation\n */\nstatic void cartesianToSpherical(const Vector3& v, Real& r, Real& azi, Real& ele)\n{\n  r   = v.norm();\n  azi = std::atan2(v.y(), v.x());\n  ele = std::atan2(std::sqrt(v.x() * v.x() + v.y() * v.y()), v.z());\n}\n\n/**\n * @brief The ConvolveType enum\n */\nenum class ConvolveType\n{\n  VALID,  // consider only where both vectors overlap completely\n  FULL    // consider each point of overlap\n};\n\n/**\n * @brief Convolution of v with k\n * @param[in] v     vector to be convolved\n * @param[in] k     convolution kernel\n * @param[in] type  convolution type --> see ConvolveType\n * @param[out] c    convolution result\n * @returns true, if result valid\n */\nstatic bool convolve(const Vector v, const Vector k, const ConvolveType type, Vector& c)\n{\n  if (k.size() < v.size())\n  {\n    if (type == ConvolveType::FULL)\n    {\n      c = Vector::Zero(v.size() + k.size() - 1, 1);\n      for (uint i = 0; i < c.size(); ++i)\n      {\n        if (i < k.size() - 1)\n          c[i] = v.head(i + 1).transpose() * k.head(i + 1).reverse();\n        else if (i > v.size() - 1)\n          c[i] = v.tail(c.size() - i).transpose() * k.tail(c.size() - i).reverse();\n        else\n          c[i] = v.segment(i - k.size() + 1, k.size()).transpose() * k.reverse();\n      }\n      return true;\n    }\n    else\n    {\n      c = Vector::Zero(v.size() - k.size() + 1, 1);\n      for (uint i = 0; i < c.size(); ++i) c[i] = v.segment(i, k.size()).transpose() * k.reverse();\n\n      return true;\n    }\n  }\n  return false;\n}\n\n}  // namespace math_helper\n}  // namespace rio\n"
  },
  {
    "path": "include/rio_utils/radar_point_cloud.h",
    "content": "// This file is part of RIO - Radar Inertial Odometry and Radar ego velocity estimation.\n// Copyright (C) 2021  Christopher Doer <christopher.doer@kit.edu>\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n//(at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n#pragma once\n\n\n#include <pcl_conversions/pcl_conversions.h>\n#include <pcl/PCLPointCloud2.h>\n#include <pcl/pcl_macros.h>\n\n#include <sensor_msgs/PointCloud2.h>\n\n#include <pcl/point_types.h>\n#include <pcl/point_cloud.h>\n#include <pcl/io/pcd_io.h>\n\nnamespace rio\n{\n\n// bool pcl2msgToPcl(const sensor_msgs::PointCloud2& pcl_msg, pcl::PointCloud<RadarPointCloudType>& scan);\n\n}  // namespace rio\n\nstruct RadarPointCloudType\n{\n  PCL_ADD_POINT4D      // x,y,z position in [m]\n  PCL_ADD_INTENSITY;\n  union\n    {\n      struct\n      {\n        float doppler;\n      };\n      float data_c[4];\n    };\n  EIGEN_MAKE_ALIGNED_OPERATOR_NEW // 确保定义新类型点云内存与SSE对齐\n} EIGEN_ALIGN16; // 强制SSE填充以正确对齐内存\nPOINT_CLOUD_REGISTER_POINT_STRUCT\n(\n    RadarPointCloudType,\n    (float, x, x)\n    (float, y, y)\n    (float, z, z)\n    (float, intensity, intensity)\n    (float, doppler, doppler)\n)\n\nstruct EaglePointXYZIVRAB\n{\n    float x;\n    float y;\n    float z;\n    float snr_db;\n    float doppler;\n    float range;\n    float alpha;\n    float beta;\n};\nPOINT_CLOUD_REGISTER_POINT_STRUCT\n(\n    EaglePointXYZIVRAB,\n    (float, x, x)\n    (float, y, y)\n    (float, z, z)\n    (float, snr_db, snr_db)\n    (float, doppler, doppler)\n    (float, range, range)\n    (float, alpha, alpha)\n    (float, beta, beta)\n)"
  },
  {
    "path": "include/rio_utils/ros_helper.h",
    "content": "// This file is part of RIO - Radar Inertial Odometry and Radar ego velocity estimation.\n// Copyright (C) 2021  Christopher Doer <christopher.doer@kit.edu>\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n//(at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n#pragma once\n\n#include <ros/node_handle.h>\n\nnamespace rio\n{\nenum class RosParameterType\n{\n  Required,\n  Recommended,\n  Optional\n};\n\ntemplate <typename T>\nstatic bool getRosParameter(const ros::NodeHandle& nh,\n                            const std::string kPrefix,\n                            const RosParameterType& param_type,\n                            const std::string& param_name,\n                            T& param)\n{\n  if (!nh.getParam(param_name, param))\n  {\n    if (param_type == RosParameterType::Optional)\n    {\n      ROS_INFO_STREAM(kPrefix << \"<\" << param_name\n                              << \"> is optional but not configured. Using default value: \" << param);\n      nh.setParam(param_name, param);\n    }\n    else if (param_type == RosParameterType::Recommended)\n    {\n      ROS_WARN_STREAM(kPrefix << \"<\" << param_name\n                              << \"> is recommeded but not configured. Using default value: \" << param);\n      nh.setParam(param_name, param);\n    }\n    else\n    {\n      ROS_ERROR_STREAM(kPrefix << \"<\" << param_name << \"> is required but not configured. Exiting!\");\n      return false;\n    }\n  }\n\n  return true;\n}\n\n}  // namespace rio\n"
  },
  {
    "path": "include/rio_utils/simple_profiler.h",
    "content": "// This file is part of RIO - Radar Inertial Odometry and Radar ego velocity estimation.\n// Copyright (C) 2021  Christopher Doer <christopher.doer@kit.edu>\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n//(at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n#pragma once\n\n#include <string>\n#include <vector>\n#include <unordered_map>\n#include <map>\n#include <chrono>\n\n#include <yaml-cpp/yaml.h>\n\n#include <ros/console.h>\n\nnamespace rio\n{\nstruct RuntimeStatistics\n{\n  float min_ms   = 0.0f;\n  float max_ms   = 0.0f;\n  float mean_ms  = 0.0f;\n  float total_ms = 0.0f;\n\n  std::string toStringMs() const\n  {\n    std::stringstream ss;\n    ss << std::fixed << std::setprecision(2) << \"mean_ms=\" << mean_ms << \", max_ms=\" << max_ms << \", min_ms=\" << min_ms\n       << \", total_s=\" << total_ms;\n    return ss.str();\n  }\n\n  std::string toStringUs() const\n  {\n    std::stringstream ss;\n    ss << std::fixed << std::setprecision(2) << \"mean_us=\" << mean_ms * 1.0e3f << \", max_us=\" << max_ms * 1.0e3f\n       << \", min_us=\" << min_ms * 1.0e3f << \", total_us=\" << total_ms * 1.0e3f;\n    return ss.str();\n  }\n};\n\nstruct ProfileData\n{\n  int id = -1;\n  RuntimeStatistics statistics;\n  std::vector<float> execution_ms = {};\n};\n\n/**\n * @brief The SimpleProfiler class provides a lighweight tool for runtime evaluation\n */\nclass SimpleProfiler\n{\npublic:\n  /**\n   * @brief SimpleProfiler constructor\n   * @param is_on   enables runtime measurements\n   */\n  SimpleProfiler(const bool is_on = true);\n\n  /**\n   * @brief Starts a runtime measurement of the provided key\n   */\n  void start(const std::string& key);\n\n  /**\n   * @brief Stops a runtime measurement of the provided key\n   * @returns true if successful (=the measurement for the given key was started)\n   */\n  bool stop(const std::string& key);\n\n  /**\n   * @brief Stops a runtime measurement of the provided key returning the runtime in [ms]\n   */\n  float stopWithRuntimeMs(const std::string& key);\n\n  /**\n   * @brief Gets the runtime statistics (=mean, max, min) runtimes for this key\n   */\n  RuntimeStatistics getStatistics(const std::string& key);\n\n  /**\n   * @brief Returns a summary of all runtime measurements as string\n   * @param indent   size of whitespace indet\n   */\n  std::string toString(const uint indent = 0);\n\n  /**\n   * @brief Returns a summary of all runtime measurements as string formatted to a markdown table\n   */\n  std::string toMarkdownTable();\n\n  /**\n   * @brief Returns the sum of all stopped runtimes\n   */\n  float getTotalRuntime();\n\nprivate:\n  /**\n   * @brief Helper function to calculate the runtime statistics for a given key\n   */\n  RuntimeStatistics calculateProfileStatistics(const std::string& key);\n\n  const std::string kPrefix;\n\n  bool is_on_;\n  int next_id_;\n  std::map<std::string, std::chrono::system_clock::time_point> start_times_;\n  std::unordered_map<std::string, ProfileData> profile_data_;\n};\n\n}  // namespace rio\n"
  },
  {
    "path": "include/rio_utils/strapdown.h",
    "content": "// This file is part of RIO - Radar Inertial Odometry and Radar ego velocity estimation.\n// Copyright (C) 2021  Christopher Doer <christopher.doer@kit.edu>\n\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n//(at your option) any later version.\n\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n#pragma once\n\n#include <eigen3/Eigen/Dense>\n#include \"rio_utils/data_types.h\"\n\nnamespace rio\n{\n/**\n * @brief The Strapdown class implements a strapdown which propagates a kinematic state using an IMU measurement\n * @note Uses the North East Down (NED) convention\n */\nclass Strapdown\n{\npublic:\n  /**\n   * @brief Strapdown constructor\n   * @param local_gravity  local gravity using the NED convention\n   */\n  Strapdown(const double local_gravity = 9.80665);\n\n  /**\n   * @brief Propagates the given navigation solution using an IMU measurement\n   * @param nav_sol_prev   previous navigation solution\n   * @param a_b_ib         measured body frame acceleration\n   * @param w_b_ib         measured body frame angular velocity\n   * @param dt             propagation time\n   * @return\n   */\n  NavigationSolution\n  propagate(const NavigationSolution nav_sol_prev, const Vector3 a_b_ib, const Vector3 w_b_ib, const Real dt);\n\nprivate:\n  Eigen::Matrix4d getQLeftMatrix(const Vector4& v);\n\n  Vector3 local_gravity_;\n};\n}  // namespace rio\n"
  },
  {
    "path": "include/scan_context/KDTreeVectorOfVectorsAdaptor.h",
    "content": "/***********************************************************************\n * Software License Agreement (BSD License)\n *\n * Copyright 2011-16 Jose Luis Blanco (joseluisblancoc@gmail.com).\n *   All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\n * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *************************************************************************/\n\n#pragma once\n\n#include \"nanoflann.hpp\"\n\n#include <vector>\n\n// ===== This example shows how to use nanoflann with these types of containers: =======\n//typedef std::vector<std::vector<double> > my_vector_of_vectors_t;\n//typedef std::vector<Eigen::VectorXd> my_vector_of_vectors_t;   // This requires #include <Eigen/Dense>\n// =====================================================================================\n\n\n/** A simple vector-of-vectors adaptor for nanoflann, without duplicating the storage.\n  *  The i'th vector represents a point in the state space.\n  *\n  *  \\tparam DIM If set to >0, it specifies a compile-time fixed dimensionality for the points in the data set, allowing more compiler optimizations.\n  *  \\tparam num_t The type of the point coordinates (typically, double or float).\n  *  \\tparam Distance The distance metric to use: nanoflann::metric_L1, nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc.\n  *  \\tparam IndexType The type for indices in the KD-tree index (typically, size_t of int)\n  */\ntemplate <class VectorOfVectorsType, typename num_t = double, int DIM = -1, class Distance = nanoflann::metric_L2, typename IndexType = size_t>\nstruct KDTreeVectorOfVectorsAdaptor\n{\n\ttypedef KDTreeVectorOfVectorsAdaptor<VectorOfVectorsType,num_t,DIM,Distance> self_t;\n\ttypedef typename Distance::template traits<num_t,self_t>::distance_t metric_t;\n\ttypedef nanoflann::KDTreeSingleIndexAdaptor< metric_t,self_t,DIM,IndexType>  index_t;\n\n\tindex_t* index; //! The kd-tree index for the user to call its methods as usual with any other FLANN index.\n\n\t/// Constructor: takes a const ref to the vector of vectors object with the data points\n\tKDTreeVectorOfVectorsAdaptor(const size_t /* dimensionality */, const VectorOfVectorsType &mat, const int leaf_max_size = 10) : m_data(mat)\n\t{\n\t\tassert(mat.size() != 0 && mat[0].size() != 0);\n\t\tconst size_t dims = mat[0].size();\n\t\tif (DIM>0 && static_cast<int>(dims) != DIM)\n\t\t\tthrow std::runtime_error(\"Data set dimensionality does not match the 'DIM' template argument\");\n\t\tindex = new index_t( static_cast<int>(dims), *this /* adaptor */, nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size ) );\n\t\tindex->buildIndex();\n\t}\n\n\t~KDTreeVectorOfVectorsAdaptor() {\n\t\tdelete index;\n\t}\n\n\tconst VectorOfVectorsType &m_data;\n\n\t/** Query for the \\a num_closest closest points to a given point (entered as query_point[0:dim-1]).\n\t  *  Note that this is a short-cut method for index->findNeighbors().\n\t  *  The user can also call index->... methods as desired.\n\t  * \\note nChecks_IGNORED is ignored but kept for compatibility with the original FLANN interface.\n\t  */\n\tinline void query(const num_t *query_point, const size_t num_closest, IndexType *out_indices, num_t *out_distances_sq, const int nChecks_IGNORED = 10) const\n\t{\n\t\tnanoflann::KNNResultSet<num_t,IndexType> resultSet(num_closest);\n\t\tresultSet.init(out_indices, out_distances_sq);\n\t\tindex->findNeighbors(resultSet, query_point, nanoflann::SearchParams());\n\t}\n\n\t/** @name Interface expected by KDTreeSingleIndexAdaptor\n\t  * @{ */\n\n\tconst self_t & derived() const {\n\t\treturn *this;\n\t}\n\tself_t & derived()       {\n\t\treturn *this;\n\t}\n\n\t// Must return the number of data points\n\tinline size_t kdtree_get_point_count() const {\n\t\treturn m_data.size();\n\t}\n\n\t// Returns the dim'th component of the idx'th point in the class:\n\tinline num_t kdtree_get_pt(const size_t idx, const size_t dim) const {\n\t\treturn m_data[idx][dim];\n\t}\n\n\t// Optional bounding-box computation: return false to default to a standard bbox computation loop.\n\t//   Return true if the BBOX was already computed by the class and returned in \"bb\" so it can be avoided to redo it again.\n\t//   Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds)\n\ttemplate <class BBOX>\n\tbool kdtree_get_bbox(BBOX & /*bb*/) const {\n\t\treturn false;\n\t}\n\n\t/** @} */\n\n}; // end of KDTreeVectorOfVectorsAdaptor\n"
  },
  {
    "path": "include/scan_context/Scancontext.h",
    "content": "#pragma once\n\n#include <ctime>\n#include <cassert>\n#include <cmath>\n#include <utility>\n#include <vector>\n#include <algorithm> \n#include <cstdlib>\n#include <memory>\n#include <iostream>\n\n#include <Eigen/Dense>\n\n#include <opencv2/opencv.hpp>\n#include <opencv2/core/eigen.hpp>\n#include <opencv2/highgui/highgui.hpp>\n#include <cv_bridge/cv_bridge.h>\n\n#include <pcl/point_cloud.h>\n#include <pcl/point_types.h>\n#include <pcl/filters/voxel_grid.h>\n#include <pcl_conversions/pcl_conversions.h>\n\n#include \"nanoflann.hpp\"\n#include \"KDTreeVectorOfVectorsAdaptor.h\"\n\n#include \"tictoc.h\"\n\n#include <radar_graph_slam/keyframe.hpp>\n\nusing namespace Eigen;\nusing namespace nanoflann;\n\nusing std::cout;\nusing std::endl;\nusing std::make_pair;\n\nusing std::atan2;\nusing std::cos;\nusing std::sin;\n\nusing SCPointType = pcl::PointXYZI; // using xyz only. but a user can exchange the original bin encoding function (i.e., max hegiht) to max intensity (for detail, refer 20 ICRA Intensity Scan Context)\nusing KeyMat = std::vector<std::vector<float> >;\nusing InvKeyTree = KDTreeVectorOfVectorsAdaptor< KeyMat, float >;\n\nstruct PointXYZIRPYT\n{\n    PCL_ADD_POINT4D\n    PCL_ADD_INTENSITY;                  // preferred way of adding a XYZ+padding\n    float roll;\n    float pitch;\n    float yaw;\n    double time;\n    EIGEN_MAKE_ALIGNED_OPERATOR_NEW   // make sure our new allocators are aligned\n} EIGEN_ALIGN16;                    // enforce SSE padding for correct memory alignment\n\nPOINT_CLOUD_REGISTER_POINT_STRUCT (PointXYZIRPYT,\n                                   (float, x, x) (float, y, y)\n                                   (float, z, z) (float, intensity, intensity)\n                                   (float, roll, roll) (float, pitch, pitch) (float, yaw, yaw)\n                                   (double, time, time))\n\n// giseop\nenum class SCInputType { \n    SINGLE_SCAN_FULL, \n    SINGLE_SCAN_FEAT, \n    MULTI_SCAN_FEAT \n}; \n\n// namespace SC2\n// {\n\nvoid coreImportTest ( void );\n\n\n// sc param-independent helper functions \nfloat xy2theta( const float & _x, const float & _y );\nMatrixXd circshift( MatrixXd &_mat, int _num_shift );\nstd::vector<float> eig2stdvec( MatrixXd _eigmat );\n\n\nclass SCManager\n{\npublic: \n    SCManager( ) = default; // reserving data space (of std::vector) could be considered. but the descriptor is lightweight so don't care.\n\n    void setScDistThresh(double thresh);\n    void setAzimuthRange(double range);\n\n    Eigen::MatrixXd makeScancontext( pcl::PointCloud<SCPointType> & _scan_down );\n    Eigen::MatrixXd makeRingkeyFromScancontext( Eigen::MatrixXd &_desc );\n    Eigen::MatrixXd makeSectorkeyFromScancontext( Eigen::MatrixXd &_desc );\n\n    int fastAlignUsingVkey ( MatrixXd & _vkey1, MatrixXd & _vkey2 ); \n    double distDirectSC ( MatrixXd &_sc1, MatrixXd &_sc2 ); // \"d\" (eq 5) in the original paper (IROS 18)\n    std::pair<double, int> distanceBtnScanContext ( MatrixXd &_sc1, MatrixXd &_sc2 ); // \"D\" (eq 6) in the original paper (IROS 18)\n\n    // User-side API\n    void makeAndSaveScancontextAndKeys( pcl::PointCloud<SCPointType> & _scan_down );\n    std::pair<int, float> detectLoopClosureID( const std::vector<radar_graph_slam::KeyFrame::Ptr>& candidate_keyframes, const radar_graph_slam::KeyFrame::Ptr& new_keyframe ); // int: nearest node index, float: relative yaw  \n\n    // for ltmapper \n    const Eigen::MatrixXd& getConstRefRecentSCD(void);\n\npublic:\n    // hyper parameters ()\n    const double LIDAR_HEIGHT = 1.2; // lidar height : add this for simply directly using lidar scan in the lidar local coord (not robot base coord) / if you use robot-coord-transformed lidar scans, just set this as 0.\n\n    double       PC_AZIMUTH_ANGLE_MAX =  56.5;\n    double       PC_AZIMUTH_ANGLE_MIN = -56.6;\n    const int    PC_NUM_RING = 40; // 20 in the original paper (IROS 18)\n    const int    PC_NUM_SECTOR = 20; // 60 in the original paper (IROS 18)\n    const double PC_MAX_RADIUS = 80.0; // 80 meter max in the original paper (IROS 18)\n    double       PC_UNIT_SECTOR_ANGLE = (PC_AZIMUTH_ANGLE_MAX - PC_AZIMUTH_ANGLE_MIN) / double(PC_NUM_SECTOR); // 360.0 / double(PC_NUM_SECTOR);\n    const double PC_UNIT_RINGGAP = PC_MAX_RADIUS / double(PC_NUM_RING);\n\n    // tree\n    const int    NUM_EXCLUDE_RECENT = 10;//30; // simply just keyframe gap (related with loopClosureFrequency in yaml), but node position distance-based exclusion is ok. \n    const int    NUM_CANDIDATES_FROM_TREE = 3; // 10 is enough. (refer the IROS 18 paper)\n\n    // loop thres\n    const double SEARCH_RATIO = 0.1; // for fast comparison, no Brute-force, but search 10 % is okay. // not was in the original conf paper, but improved ver.\n    // const double SC_DIST_THRES = 0.13; // empirically 0.1-0.2 is fine (rare false-alarms) for 20x60 polar context (but for 0.15 <, DCS or ICP fit score check (e.g., in LeGO-LOAM) should be required for robustness)\n    double SC_DIST_THRES = 0.5; // 0.4-0.6 is good choice for using with robust kernel (e.g., Cauchy, DCS) + icp fitness threshold / if not, recommend 0.1-0.15\n    // const double SC_DIST_THRES = 0.7; // 0.4-0.6 is good choice for using with robust kernel (e.g., Cauchy, DCS) + icp fitness threshold / if not, recommend 0.1-0.15\n\n    // config \n    const int    TREE_MAKING_PERIOD_ = 10; // i.e., remaking tree frequency, to avoid non-mandatory every remaking, to save time cost / in the LeGO-LOAM integration, it is synchronized with the loop detection callback (which is 1Hz) so it means the tree is updated evrey 10 sec. But you can use the smaller value because it is enough fast ~ 5-50ms wrt N.\n    int          tree_making_period_conter = 0;\n\n    // data \n    std::vector<double> polarcontexts_timestamp_; // optional.\n    std::vector<Eigen::MatrixXd> polarcontexts_;\n    std::vector<Eigen::MatrixXd> polarcontext_invkeys_;\n    std::vector<Eigen::MatrixXd> polarcontext_vkeys_;\n\n    KeyMat polarcontext_invkeys_mat_;\n    KeyMat polarcontext_invkeys_to_search_;\n    std::unique_ptr<InvKeyTree> polarcontext_tree_;\n\n}; // SCManager\n\n// } // namespace SC2\n"
  },
  {
    "path": "include/scan_context/nanoflann.hpp",
    "content": "/***********************************************************************\n * Software License Agreement (BSD License)\n *\n * Copyright 2008-2009  Marius Muja (mariusm@cs.ubc.ca). All rights reserved.\n * Copyright 2008-2009  David G. Lowe (lowe@cs.ubc.ca). All rights reserved.\n * Copyright 2011-2016  Jose Luis Blanco (joseluisblancoc@gmail.com).\n *   All rights reserved.\n *\n * THE BSD LICENSE\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\n * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *************************************************************************/\n\n/** \\mainpage nanoflann C++ API documentation\n *  nanoflann is a C++ header-only library for building KD-Trees, mostly\n *  optimized for 2D or 3D point clouds.\n *\n *  nanoflann does not require compiling or installing, just an\n *  #include <nanoflann.hpp> in your code.\n *\n *  See:\n *   - <a href=\"modules.html\" >C++ API organized by modules</a>\n *   - <a href=\"https://github.com/jlblancoc/nanoflann\" >Online README</a>\n *   - <a href=\"http://jlblancoc.github.io/nanoflann/\" >Doxygen\n * documentation</a>\n */\n\n#ifndef NANOFLANN_HPP_\n#define NANOFLANN_HPP_\n\n#include <algorithm>\n#include <array>\n#include <cassert>\n#include <cmath>   // for abs()\n#include <cstdio>  // for fwrite()\n#include <cstdlib> // for abs()\n#include <functional>\n#include <limits> // std::reference_wrapper\n#include <stdexcept>\n#include <vector>\n\n/** Library version: 0xMmP (M=Major,m=minor,P=patch) */\n#define NANOFLANN_VERSION 0x132\n\n// Avoid conflicting declaration of min/max macros in windows headers\n#if !defined(NOMINMAX) &&                                                      \\\n    (defined(_WIN32) || defined(_WIN32_) || defined(WIN32) || defined(_WIN64))\n#define NOMINMAX\n#ifdef max\n#undef max\n#undef min\n#endif\n#endif\n\nnamespace nanoflann {\n/** @addtogroup nanoflann_grp nanoflann C++ library for ANN\n *  @{ */\n\n/** the PI constant (required to avoid MSVC missing symbols) */\ntemplate <typename T> T pi_const() {\n  return static_cast<T>(3.14159265358979323846);\n}\n\n/**\n * Traits if object is resizable and assignable (typically has a resize | assign\n * method)\n */\ntemplate <typename T, typename = int> struct has_resize : std::false_type {};\n\ntemplate <typename T>\nstruct has_resize<T, decltype((void)std::declval<T>().resize(1), 0)>\n    : std::true_type {};\n\ntemplate <typename T, typename = int> struct has_assign : std::false_type {};\n\ntemplate <typename T>\nstruct has_assign<T, decltype((void)std::declval<T>().assign(1, 0), 0)>\n    : std::true_type {};\n\n/**\n * Free function to resize a resizable object\n */\ntemplate <typename Container>\ninline typename std::enable_if<has_resize<Container>::value, void>::type\nresize(Container &c, const size_t nElements) {\n  c.resize(nElements);\n}\n\n/**\n * Free function that has no effects on non resizable containers (e.g.\n * std::array) It raises an exception if the expected size does not match\n */\ntemplate <typename Container>\ninline typename std::enable_if<!has_resize<Container>::value, void>::type\nresize(Container &c, const size_t nElements) {\n  if (nElements != c.size())\n    throw std::logic_error(\"Try to change the size of a std::array.\");\n}\n\n/**\n * Free function to assign to a container\n */\ntemplate <typename Container, typename T>\ninline typename std::enable_if<has_assign<Container>::value, void>::type\nassign(Container &c, const size_t nElements, const T &value) {\n  c.assign(nElements, value);\n}\n\n/**\n * Free function to assign to a std::array\n */\ntemplate <typename Container, typename T>\ninline typename std::enable_if<!has_assign<Container>::value, void>::type\nassign(Container &c, const size_t nElements, const T &value) {\n  for (size_t i = 0; i < nElements; i++)\n    c[i] = value;\n}\n\n/** @addtogroup result_sets_grp Result set classes\n *  @{ */\ntemplate <typename _DistanceType, typename _IndexType = size_t,\n          typename _CountType = size_t>\nclass KNNResultSet {\npublic:\n  typedef _DistanceType DistanceType;\n  typedef _IndexType IndexType;\n  typedef _CountType CountType;\n\nprivate:\n  IndexType *indices;\n  DistanceType *dists;\n  CountType capacity;\n  CountType count;\n\npublic:\n  inline KNNResultSet(CountType capacity_)\n      : indices(0), dists(0), capacity(capacity_), count(0) {}\n\n  inline void init(IndexType *indices_, DistanceType *dists_) {\n    indices = indices_;\n    dists = dists_;\n    count = 0;\n    if (capacity)\n      dists[capacity - 1] = (std::numeric_limits<DistanceType>::max)();\n  }\n\n  inline CountType size() const { return count; }\n\n  inline bool full() const { return count == capacity; }\n\n  /**\n   * Called during search to add an element matching the criteria.\n   * @return true if the search should be continued, false if the results are\n   * sufficient\n   */\n  inline bool addPoint(DistanceType dist, IndexType index) {\n    CountType i;\n    for (i = count; i > 0; --i) {\n#ifdef NANOFLANN_FIRST_MATCH // If defined and two points have the same\n                             // distance, the one with the lowest-index will be\n                             // returned first.\n      if ((dists[i - 1] > dist) ||\n          ((dist == dists[i - 1]) && (indices[i - 1] > index))) {\n#else\n      if (dists[i - 1] > dist) {\n#endif\n        if (i < capacity) {\n          dists[i] = dists[i - 1];\n          indices[i] = indices[i - 1];\n        }\n      } else\n        break;\n    }\n    if (i < capacity) {\n      dists[i] = dist;\n      indices[i] = index;\n    }\n    if (count < capacity)\n      count++;\n\n    // tell caller that the search shall continue\n    return true;\n  }\n\n  inline DistanceType worstDist() const { return dists[capacity - 1]; }\n};\n\n/** operator \"<\" for std::sort() */\nstruct IndexDist_Sorter {\n  /** PairType will be typically: std::pair<IndexType,DistanceType> */\n  template <typename PairType>\n  inline bool operator()(const PairType &p1, const PairType &p2) const {\n    return p1.second < p2.second;\n  }\n};\n\n/**\n * A result-set class used when performing a radius based search.\n */\ntemplate <typename _DistanceType, typename _IndexType = size_t>\nclass RadiusResultSet {\npublic:\n  typedef _DistanceType DistanceType;\n  typedef _IndexType IndexType;\n\npublic:\n  const DistanceType radius;\n\n  std::vector<std::pair<IndexType, DistanceType>> &m_indices_dists;\n\n  inline RadiusResultSet(\n      DistanceType radius_,\n      std::vector<std::pair<IndexType, DistanceType>> &indices_dists)\n      : radius(radius_), m_indices_dists(indices_dists) {\n    init();\n  }\n\n  inline void init() { clear(); }\n  inline void clear() { m_indices_dists.clear(); }\n\n  inline size_t size() const { return m_indices_dists.size(); }\n\n  inline bool full() const { return true; }\n\n  /**\n   * Called during search to add an element matching the criteria.\n   * @return true if the search should be continued, false if the results are\n   * sufficient\n   */\n  inline bool addPoint(DistanceType dist, IndexType index) {\n    if (dist < radius)\n      m_indices_dists.push_back(std::make_pair(index, dist));\n    return true;\n  }\n\n  inline DistanceType worstDist() const { return radius; }\n\n  /**\n   * Find the worst result (furtherest neighbor) without copying or sorting\n   * Pre-conditions: size() > 0\n   */\n  std::pair<IndexType, DistanceType> worst_item() const {\n    if (m_indices_dists.empty())\n      throw std::runtime_error(\"Cannot invoke RadiusResultSet::worst_item() on \"\n                               \"an empty list of results.\");\n    typedef\n        typename std::vector<std::pair<IndexType, DistanceType>>::const_iterator\n            DistIt;\n    DistIt it = std::max_element(m_indices_dists.begin(), m_indices_dists.end(),\n                                 IndexDist_Sorter());\n    return *it;\n  }\n};\n\n/** @} */\n\n/** @addtogroup loadsave_grp Load/save auxiliary functions\n * @{ */\ntemplate <typename T>\nvoid save_value(FILE *stream, const T &value, size_t count = 1) {\n  fwrite(&value, sizeof(value), count, stream);\n}\n\ntemplate <typename T>\nvoid save_value(FILE *stream, const std::vector<T> &value) {\n  size_t size = value.size();\n  fwrite(&size, sizeof(size_t), 1, stream);\n  fwrite(&value[0], sizeof(T), size, stream);\n}\n\ntemplate <typename T>\nvoid load_value(FILE *stream, T &value, size_t count = 1) {\n  size_t read_cnt = fread(&value, sizeof(value), count, stream);\n  if (read_cnt != count) {\n    throw std::runtime_error(\"Cannot read from file\");\n  }\n}\n\ntemplate <typename T> void load_value(FILE *stream, std::vector<T> &value) {\n  size_t size;\n  size_t read_cnt = fread(&size, sizeof(size_t), 1, stream);\n  if (read_cnt != 1) {\n    throw std::runtime_error(\"Cannot read from file\");\n  }\n  value.resize(size);\n  read_cnt = fread(&value[0], sizeof(T), size, stream);\n  if (read_cnt != size) {\n    throw std::runtime_error(\"Cannot read from file\");\n  }\n}\n/** @} */\n\n/** @addtogroup metric_grp Metric (distance) classes\n * @{ */\n\nstruct Metric {};\n\n/** Manhattan distance functor (generic version, optimized for\n * high-dimensionality data sets). Corresponding distance traits:\n * nanoflann::metric_L1 \\tparam T Type of the elements (e.g. double, float,\n * uint8_t) \\tparam _DistanceType Type of distance variables (must be signed)\n * (e.g. float, double, int64_t)\n */\ntemplate <class T, class DataSource, typename _DistanceType = T>\nstruct L1_Adaptor {\n  typedef T ElementType;\n  typedef _DistanceType DistanceType;\n\n  const DataSource &data_source;\n\n  L1_Adaptor(const DataSource &_data_source) : data_source(_data_source) {}\n\n  inline DistanceType evalMetric(const T *a, const size_t b_idx, size_t size,\n                                 DistanceType worst_dist = -1) const {\n    DistanceType result = DistanceType();\n    const T *last = a + size;\n    const T *lastgroup = last - 3;\n    size_t d = 0;\n\n    /* Process 4 items with each loop for efficiency. */\n    while (a < lastgroup) {\n      const DistanceType diff0 =\n          std::abs(a[0] - data_source.kdtree_get_pt(b_idx, d++));\n      const DistanceType diff1 =\n          std::abs(a[1] - data_source.kdtree_get_pt(b_idx, d++));\n      const DistanceType diff2 =\n          std::abs(a[2] - data_source.kdtree_get_pt(b_idx, d++));\n      const DistanceType diff3 =\n          std::abs(a[3] - data_source.kdtree_get_pt(b_idx, d++));\n      result += diff0 + diff1 + diff2 + diff3;\n      a += 4;\n      if ((worst_dist > 0) && (result > worst_dist)) {\n        return result;\n      }\n    }\n    /* Process last 0-3 components.  Not needed for standard vector lengths. */\n    while (a < last) {\n      result += std::abs(*a++ - data_source.kdtree_get_pt(b_idx, d++));\n    }\n    return result;\n  }\n\n  template <typename U, typename V>\n  inline DistanceType accum_dist(const U a, const V b, const size_t) const {\n    return std::abs(a - b);\n  }\n};\n\n/** Squared Euclidean distance functor (generic version, optimized for\n * high-dimensionality data sets). Corresponding distance traits:\n * nanoflann::metric_L2 \\tparam T Type of the elements (e.g. double, float,\n * uint8_t) \\tparam _DistanceType Type of distance variables (must be signed)\n * (e.g. float, double, int64_t)\n */\ntemplate <class T, class DataSource, typename _DistanceType = T>\nstruct L2_Adaptor {\n  typedef T ElementType;\n  typedef _DistanceType DistanceType;\n\n  const DataSource &data_source;\n\n  L2_Adaptor(const DataSource &_data_source) : data_source(_data_source) {}\n\n  inline DistanceType evalMetric(const T *a, const size_t b_idx, size_t size,\n                                 DistanceType worst_dist = -1) const {\n    DistanceType result = DistanceType();\n    const T *last = a + size;\n    const T *lastgroup = last - 3;\n    size_t d = 0;\n\n    /* Process 4 items with each loop for efficiency. */\n    while (a < lastgroup) {\n      const DistanceType diff0 = a[0] - data_source.kdtree_get_pt(b_idx, d++);\n      const DistanceType diff1 = a[1] - data_source.kdtree_get_pt(b_idx, d++);\n      const DistanceType diff2 = a[2] - data_source.kdtree_get_pt(b_idx, d++);\n      const DistanceType diff3 = a[3] - data_source.kdtree_get_pt(b_idx, d++);\n      result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;\n      a += 4;\n      if ((worst_dist > 0) && (result > worst_dist)) {\n        return result;\n      }\n    }\n    /* Process last 0-3 components.  Not needed for standard vector lengths. */\n    while (a < last) {\n      const DistanceType diff0 = *a++ - data_source.kdtree_get_pt(b_idx, d++);\n      result += diff0 * diff0;\n    }\n    return result;\n  }\n\n  template <typename U, typename V>\n  inline DistanceType accum_dist(const U a, const V b, const size_t) const {\n    return (a - b) * (a - b);\n  }\n};\n\n/** Squared Euclidean (L2) distance functor (suitable for low-dimensionality\n * datasets, like 2D or 3D point clouds) Corresponding distance traits:\n * nanoflann::metric_L2_Simple \\tparam T Type of the elements (e.g. double,\n * float, uint8_t) \\tparam _DistanceType Type of distance variables (must be\n * signed) (e.g. float, double, int64_t)\n */\ntemplate <class T, class DataSource, typename _DistanceType = T>\nstruct L2_Simple_Adaptor {\n  typedef T ElementType;\n  typedef _DistanceType DistanceType;\n\n  const DataSource &data_source;\n\n  L2_Simple_Adaptor(const DataSource &_data_source)\n      : data_source(_data_source) {}\n\n  inline DistanceType evalMetric(const T *a, const size_t b_idx,\n                                 size_t size) const {\n    DistanceType result = DistanceType();\n    for (size_t i = 0; i < size; ++i) {\n      const DistanceType diff = a[i] - data_source.kdtree_get_pt(b_idx, i);\n      result += diff * diff;\n    }\n    return result;\n  }\n\n  template <typename U, typename V>\n  inline DistanceType accum_dist(const U a, const V b, const size_t) const {\n    return (a - b) * (a - b);\n  }\n};\n\n/** SO2 distance functor\n *  Corresponding distance traits: nanoflann::metric_SO2\n * \\tparam T Type of the elements (e.g. double, float)\n * \\tparam _DistanceType Type of distance variables (must be signed) (e.g.\n * float, double) orientation is constrained to be in [-pi, pi]\n */\ntemplate <class T, class DataSource, typename _DistanceType = T>\nstruct SO2_Adaptor {\n  typedef T ElementType;\n  typedef _DistanceType DistanceType;\n\n  const DataSource &data_source;\n\n  SO2_Adaptor(const DataSource &_data_source) : data_source(_data_source) {}\n\n  inline DistanceType evalMetric(const T *a, const size_t b_idx,\n                                 size_t size) const {\n    return accum_dist(a[size - 1], data_source.kdtree_get_pt(b_idx, size - 1),\n                      size - 1);\n  }\n\n  /** Note: this assumes that input angles are already in the range [-pi,pi] */\n  template <typename U, typename V>\n  inline DistanceType accum_dist(const U a, const V b, const size_t) const {\n    DistanceType result = DistanceType();\n    DistanceType PI = pi_const<DistanceType>();\n    result = b - a;\n    if (result > PI)\n      result -= 2 * PI;\n    else if (result < -PI)\n      result += 2 * PI;\n    return result;\n  }\n};\n\n/** SO3 distance functor (Uses L2_Simple)\n *  Corresponding distance traits: nanoflann::metric_SO3\n * \\tparam T Type of the elements (e.g. double, float)\n * \\tparam _DistanceType Type of distance variables (must be signed) (e.g.\n * float, double)\n */\ntemplate <class T, class DataSource, typename _DistanceType = T>\nstruct SO3_Adaptor {\n  typedef T ElementType;\n  typedef _DistanceType DistanceType;\n\n  L2_Simple_Adaptor<T, DataSource> distance_L2_Simple;\n\n  SO3_Adaptor(const DataSource &_data_source)\n      : distance_L2_Simple(_data_source) {}\n\n  inline DistanceType evalMetric(const T *a, const size_t b_idx,\n                                 size_t size) const {\n    return distance_L2_Simple.evalMetric(a, b_idx, size);\n  }\n\n  template <typename U, typename V>\n  inline DistanceType accum_dist(const U a, const V b, const size_t idx) const {\n    return distance_L2_Simple.accum_dist(a, b, idx);\n  }\n};\n\n/** Metaprogramming helper traits class for the L1 (Manhattan) metric */\nstruct metric_L1 : public Metric {\n  template <class T, class DataSource> struct traits {\n    typedef L1_Adaptor<T, DataSource> distance_t;\n  };\n};\n/** Metaprogramming helper traits class for the L2 (Euclidean) metric */\nstruct metric_L2 : public Metric {\n  template <class T, class DataSource> struct traits {\n    typedef L2_Adaptor<T, DataSource> distance_t;\n  };\n};\n/** Metaprogramming helper traits class for the L2_simple (Euclidean) metric */\nstruct metric_L2_Simple : public Metric {\n  template <class T, class DataSource> struct traits {\n    typedef L2_Simple_Adaptor<T, DataSource> distance_t;\n  };\n};\n/** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */\nstruct metric_SO2 : public Metric {\n  template <class T, class DataSource> struct traits {\n    typedef SO2_Adaptor<T, DataSource> distance_t;\n  };\n};\n/** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */\nstruct metric_SO3 : public Metric {\n  template <class T, class DataSource> struct traits {\n    typedef SO3_Adaptor<T, DataSource> distance_t;\n  };\n};\n\n/** @} */\n\n/** @addtogroup param_grp Parameter structs\n * @{ */\n\n/**  Parameters (see README.md) */\nstruct KDTreeSingleIndexAdaptorParams {\n  KDTreeSingleIndexAdaptorParams(size_t _leaf_max_size = 10)\n      : leaf_max_size(_leaf_max_size) {}\n\n  size_t leaf_max_size;\n};\n\n/** Search options for KDTreeSingleIndexAdaptor::findNeighbors() */\nstruct SearchParams {\n  /** Note: The first argument (checks_IGNORED_) is ignored, but kept for\n   * compatibility with the FLANN interface */\n  SearchParams(int checks_IGNORED_ = 32, float eps_ = 0, bool sorted_ = true)\n      : checks(checks_IGNORED_), eps(eps_), sorted(sorted_) {}\n\n  int checks;  //!< Ignored parameter (Kept for compatibility with the FLANN\n               //!< interface).\n  float eps;   //!< search for eps-approximate neighbours (default: 0)\n  bool sorted; //!< only for radius search, require neighbours sorted by\n               //!< distance (default: true)\n};\n/** @} */\n\n/** @addtogroup memalloc_grp Memory allocation\n * @{ */\n\n/**\n * Allocates (using C's malloc) a generic type T.\n *\n * Params:\n *     count = number of instances to allocate.\n * Returns: pointer (of type T*) to memory buffer\n */\ntemplate <typename T> inline T *allocate(size_t count = 1) {\n  T *mem = static_cast<T *>(::malloc(sizeof(T) * count));\n  return mem;\n}\n\n/**\n * Pooled storage allocator\n *\n * The following routines allow for the efficient allocation of storage in\n * small chunks from a specified pool.  Rather than allowing each structure\n * to be freed individually, an entire pool of storage is freed at once.\n * This method has two advantages over just using malloc() and free().  First,\n * it is far more efficient for allocating small objects, as there is\n * no overhead for remembering all the information needed to free each\n * object or consolidating fragmented memory.  Second, the decision about\n * how long to keep an object is made at the time of allocation, and there\n * is no need to track down all the objects to free them.\n *\n */\n\nconst size_t WORDSIZE = 16;\nconst size_t BLOCKSIZE = 8192;\n\nclass PooledAllocator {\n  /* We maintain memory alignment to word boundaries by requiring that all\n      allocations be in multiples of the machine wordsize.  */\n  /* Size of machine word in bytes.  Must be power of 2. */\n  /* Minimum number of bytes requested at a time from\tthe system.  Must be\n   * multiple of WORDSIZE. */\n\n  size_t remaining; /* Number of bytes left in current block of storage. */\n  void *base;       /* Pointer to base of current block of storage. */\n  void *loc;        /* Current location in block to next allocate memory. */\n\n  void internal_init() {\n    remaining = 0;\n    base = NULL;\n    usedMemory = 0;\n    wastedMemory = 0;\n  }\n\npublic:\n  size_t usedMemory;\n  size_t wastedMemory;\n\n  /**\n      Default constructor. Initializes a new pool.\n   */\n  PooledAllocator() { internal_init(); }\n\n  /**\n   * Destructor. Frees all the memory allocated in this pool.\n   */\n  ~PooledAllocator() { free_all(); }\n\n  /** Frees all allocated memory chunks */\n  void free_all() {\n    while (base != NULL) {\n      void *prev =\n          *(static_cast<void **>(base)); /* Get pointer to prev block. */\n      ::free(base);\n      base = prev;\n    }\n    internal_init();\n  }\n\n  /**\n   * Returns a pointer to a piece of new memory of the given size in bytes\n   * allocated from the pool.\n   */\n  void *malloc(const size_t req_size) {\n    /* Round size up to a multiple of wordsize.  The following expression\n        only works for WORDSIZE that is a power of 2, by masking last bits of\n        incremented size to zero.\n     */\n    const size_t size = (req_size + (WORDSIZE - 1)) & ~(WORDSIZE - 1);\n\n    /* Check whether a new block must be allocated.  Note that the first word\n        of a block is reserved for a pointer to the previous block.\n     */\n    if (size > remaining) {\n\n      wastedMemory += remaining;\n\n      /* Allocate new storage. */\n      const size_t blocksize =\n          (size + sizeof(void *) + (WORDSIZE - 1) > BLOCKSIZE)\n              ? size + sizeof(void *) + (WORDSIZE - 1)\n              : BLOCKSIZE;\n\n      // use the standard C malloc to allocate memory\n      void *m = ::malloc(blocksize);\n      if (!m) {\n        fprintf(stderr, \"Failed to allocate memory.\\n\");\n        return NULL;\n      }\n\n      /* Fill first word of new block with pointer to previous block. */\n      static_cast<void **>(m)[0] = base;\n      base = m;\n\n      size_t shift = 0;\n      // int size_t = (WORDSIZE - ( (((size_t)m) + sizeof(void*)) &\n      // (WORDSIZE-1))) & (WORDSIZE-1);\n\n      remaining = blocksize - sizeof(void *) - shift;\n      loc = (static_cast<char *>(m) + sizeof(void *) + shift);\n    }\n    void *rloc = loc;\n    loc = static_cast<char *>(loc) + size;\n    remaining -= size;\n\n    usedMemory += size;\n\n    return rloc;\n  }\n\n  /**\n   * Allocates (using this pool) a generic type T.\n   *\n   * Params:\n   *     count = number of instances to allocate.\n   * Returns: pointer (of type T*) to memory buffer\n   */\n  template <typename T> T *allocate(const size_t count = 1) {\n    T *mem = static_cast<T *>(this->malloc(sizeof(T) * count));\n    return mem;\n  }\n};\n/** @} */\n\n/** @addtogroup nanoflann_metaprog_grp Auxiliary metaprogramming stuff\n * @{ */\n\n/** Used to declare fixed-size arrays when DIM>0, dynamically-allocated vectors\n * when DIM=-1. Fixed size version for a generic DIM:\n */\ntemplate <int DIM, typename T> struct array_or_vector_selector {\n  typedef std::array<T, DIM> container_t;\n};\n/** Dynamic size version */\ntemplate <typename T> struct array_or_vector_selector<-1, T> {\n  typedef std::vector<T> container_t;\n};\n\n/** @} */\n\n/** kd-tree base-class\n *\n * Contains the member functions common to the classes KDTreeSingleIndexAdaptor\n * and KDTreeSingleIndexDynamicAdaptor_.\n *\n * \\tparam Derived The name of the class which inherits this class.\n * \\tparam DatasetAdaptor The user-provided adaptor (see comments above).\n * \\tparam Distance The distance metric to use, these are all classes derived\n * from nanoflann::Metric \\tparam DIM Dimensionality of data points (e.g. 3 for\n * 3D points) \\tparam IndexType Will be typically size_t or int\n */\n\ntemplate <class Derived, typename Distance, class DatasetAdaptor, int DIM = -1,\n          typename IndexType = size_t>\nclass KDTreeBaseClass {\n\npublic:\n  /** Frees the previously-built index. Automatically called within\n   * buildIndex(). */\n  void freeIndex(Derived &obj) {\n    obj.pool.free_all();\n    obj.root_node = NULL;\n    obj.m_size_at_index_build = 0;\n  }\n\n  typedef typename Distance::ElementType ElementType;\n  typedef typename Distance::DistanceType DistanceType;\n\n  /*--------------------- Internal Data Structures --------------------------*/\n  struct Node {\n    /** Union used because a node can be either a LEAF node or a non-leaf node,\n     * so both data fields are never used simultaneously */\n    union {\n      struct leaf {\n        IndexType left, right; //!< Indices of points in leaf node\n      } lr;\n      struct nonleaf {\n        int divfeat;                  //!< Dimension used for subdivision.\n        DistanceType divlow, divhigh; //!< The values used for subdivision.\n      } sub;\n    } node_type;\n    Node *child1, *child2; //!< Child nodes (both=NULL mean its a leaf node)\n  };\n\n  typedef Node *NodePtr;\n\n  struct Interval {\n    ElementType low, high;\n  };\n\n  /**\n   *  Array of indices to vectors in the dataset.\n   */\n  std::vector<IndexType> vind;\n\n  NodePtr root_node;\n\n  size_t m_leaf_max_size;\n\n  size_t m_size;                //!< Number of current points in the dataset\n  size_t m_size_at_index_build; //!< Number of points in the dataset when the\n                                //!< index was built\n  int dim;                      //!< Dimensionality of each data point\n\n  /** Define \"BoundingBox\" as a fixed-size or variable-size container depending\n   * on \"DIM\" */\n  typedef\n      typename array_or_vector_selector<DIM, Interval>::container_t BoundingBox;\n\n  /** Define \"distance_vector_t\" as a fixed-size or variable-size container\n   * depending on \"DIM\" */\n  typedef typename array_or_vector_selector<DIM, DistanceType>::container_t\n      distance_vector_t;\n\n  /** The KD-tree used to find neighbours */\n\n  BoundingBox root_bbox;\n\n  /**\n   * Pooled memory allocator.\n   *\n   * Using a pooled memory allocator is more efficient\n   * than allocating memory directly when there is a large\n   * number small of memory allocations.\n   */\n  PooledAllocator pool;\n\n  /** Returns number of points in dataset  */\n  size_t size(const Derived &obj) const { return obj.m_size; }\n\n  /** Returns the length of each point in the dataset */\n  size_t veclen(const Derived &obj) {\n    return static_cast<size_t>(DIM > 0 ? DIM : obj.dim);\n  }\n\n  /// Helper accessor to the dataset points:\n  inline ElementType dataset_get(const Derived &obj, size_t idx,\n                                 int component) const {\n    return obj.dataset.kdtree_get_pt(idx, component);\n  }\n\n  /**\n   * Computes the inde memory usage\n   * Returns: memory used by the index\n   */\n  size_t usedMemory(Derived &obj) {\n    return obj.pool.usedMemory + obj.pool.wastedMemory +\n           obj.dataset.kdtree_get_point_count() *\n               sizeof(IndexType); // pool memory and vind array memory\n  }\n\n  void computeMinMax(const Derived &obj, IndexType *ind, IndexType count,\n                     int element, ElementType &min_elem,\n                     ElementType &max_elem) {\n    min_elem = dataset_get(obj, ind[0], element);\n    max_elem = dataset_get(obj, ind[0], element);\n    for (IndexType i = 1; i < count; ++i) {\n      ElementType val = dataset_get(obj, ind[i], element);\n      if (val < min_elem)\n        min_elem = val;\n      if (val > max_elem)\n        max_elem = val;\n    }\n  }\n\n  /**\n   * Create a tree node that subdivides the list of vecs from vind[first]\n   * to vind[last].  The routine is called recursively on each sublist.\n   *\n   * @param left index of the first vector\n   * @param right index of the last vector\n   */\n  NodePtr divideTree(Derived &obj, const IndexType left, const IndexType right,\n                     BoundingBox &bbox) {\n    NodePtr node = obj.pool.template allocate<Node>(); // allocate memory\n\n    /* If too few exemplars remain, then make this a leaf node. */\n    if ((right - left) <= static_cast<IndexType>(obj.m_leaf_max_size)) {\n      node->child1 = node->child2 = NULL; /* Mark as leaf node. */\n      node->node_type.lr.left = left;\n      node->node_type.lr.right = right;\n\n      // compute bounding-box of leaf points\n      for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) {\n        bbox[i].low = dataset_get(obj, obj.vind[left], i);\n        bbox[i].high = dataset_get(obj, obj.vind[left], i);\n      }\n      for (IndexType k = left + 1; k < right; ++k) {\n        for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) {\n          if (bbox[i].low > dataset_get(obj, obj.vind[k], i))\n            bbox[i].low = dataset_get(obj, obj.vind[k], i);\n          if (bbox[i].high < dataset_get(obj, obj.vind[k], i))\n            bbox[i].high = dataset_get(obj, obj.vind[k], i);\n        }\n      }\n    } else {\n      IndexType idx;\n      int cutfeat;\n      DistanceType cutval;\n      middleSplit_(obj, &obj.vind[0] + left, right - left, idx, cutfeat, cutval,\n                   bbox);\n\n      node->node_type.sub.divfeat = cutfeat;\n\n      BoundingBox left_bbox(bbox);\n      left_bbox[cutfeat].high = cutval;\n      node->child1 = divideTree(obj, left, left + idx, left_bbox);\n\n      BoundingBox right_bbox(bbox);\n      right_bbox[cutfeat].low = cutval;\n      node->child2 = divideTree(obj, left + idx, right, right_bbox);\n\n      node->node_type.sub.divlow = left_bbox[cutfeat].high;\n      node->node_type.sub.divhigh = right_bbox[cutfeat].low;\n\n      for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) {\n        bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low);\n        bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high);\n      }\n    }\n\n    return node;\n  }\n\n  void middleSplit_(Derived &obj, IndexType *ind, IndexType count,\n                    IndexType &index, int &cutfeat, DistanceType &cutval,\n                    const BoundingBox &bbox) {\n    const DistanceType EPS = static_cast<DistanceType>(0.00001);\n    ElementType max_span = bbox[0].high - bbox[0].low;\n    for (int i = 1; i < (DIM > 0 ? DIM : obj.dim); ++i) {\n      ElementType span = bbox[i].high - bbox[i].low;\n      if (span > max_span) {\n        max_span = span;\n      }\n    }\n    ElementType max_spread = -1;\n    cutfeat = 0;\n    for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) {\n      ElementType span = bbox[i].high - bbox[i].low;\n      if (span > (1 - EPS) * max_span) {\n        ElementType min_elem, max_elem;\n        computeMinMax(obj, ind, count, i, min_elem, max_elem);\n        ElementType spread = max_elem - min_elem;\n        ;\n        if (spread > max_spread) {\n          cutfeat = i;\n          max_spread = spread;\n        }\n      }\n    }\n    // split in the middle\n    DistanceType split_val = (bbox[cutfeat].low + bbox[cutfeat].high) / 2;\n    ElementType min_elem, max_elem;\n    computeMinMax(obj, ind, count, cutfeat, min_elem, max_elem);\n\n    if (split_val < min_elem)\n      cutval = min_elem;\n    else if (split_val > max_elem)\n      cutval = max_elem;\n    else\n      cutval = split_val;\n\n    IndexType lim1, lim2;\n    planeSplit(obj, ind, count, cutfeat, cutval, lim1, lim2);\n\n    if (lim1 > count / 2)\n      index = lim1;\n    else if (lim2 < count / 2)\n      index = lim2;\n    else\n      index = count / 2;\n  }\n\n  /**\n   *  Subdivide the list of points by a plane perpendicular on axe corresponding\n   *  to the 'cutfeat' dimension at 'cutval' position.\n   *\n   *  On return:\n   *  dataset[ind[0..lim1-1]][cutfeat]<cutval\n   *  dataset[ind[lim1..lim2-1]][cutfeat]==cutval\n   *  dataset[ind[lim2..count]][cutfeat]>cutval\n   */\n  void planeSplit(Derived &obj, IndexType *ind, const IndexType count,\n                  int cutfeat, DistanceType &cutval, IndexType &lim1,\n                  IndexType &lim2) {\n    /* Move vector indices for left subtree to front of list. */\n    IndexType left = 0;\n    IndexType right = count - 1;\n    for (;;) {\n      while (left <= right && dataset_get(obj, ind[left], cutfeat) < cutval)\n        ++left;\n      while (right && left <= right &&\n             dataset_get(obj, ind[right], cutfeat) >= cutval)\n        --right;\n      if (left > right || !right)\n        break; // \"!right\" was added to support unsigned Index types\n      std::swap(ind[left], ind[right]);\n      ++left;\n      --right;\n    }\n    /* If either list is empty, it means that all remaining features\n     * are identical. Split in the middle to maintain a balanced tree.\n     */\n    lim1 = left;\n    right = count - 1;\n    for (;;) {\n      while (left <= right && dataset_get(obj, ind[left], cutfeat) <= cutval)\n        ++left;\n      while (right && left <= right &&\n             dataset_get(obj, ind[right], cutfeat) > cutval)\n        --right;\n      if (left > right || !right)\n        break; // \"!right\" was added to support unsigned Index types\n      std::swap(ind[left], ind[right]);\n      ++left;\n      --right;\n    }\n    lim2 = left;\n  }\n\n  DistanceType computeInitialDistances(const Derived &obj,\n                                       const ElementType *vec,\n                                       distance_vector_t &dists) const {\n    assert(vec);\n    DistanceType distsq = DistanceType();\n\n    for (int i = 0; i < (DIM > 0 ? DIM : obj.dim); ++i) {\n      if (vec[i] < obj.root_bbox[i].low) {\n        dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].low, i);\n        distsq += dists[i];\n      }\n      if (vec[i] > obj.root_bbox[i].high) {\n        dists[i] = obj.distance.accum_dist(vec[i], obj.root_bbox[i].high, i);\n        distsq += dists[i];\n      }\n    }\n    return distsq;\n  }\n\n  void save_tree(Derived &obj, FILE *stream, NodePtr tree) {\n    save_value(stream, *tree);\n    if (tree->child1 != NULL) {\n      save_tree(obj, stream, tree->child1);\n    }\n    if (tree->child2 != NULL) {\n      save_tree(obj, stream, tree->child2);\n    }\n  }\n\n  void load_tree(Derived &obj, FILE *stream, NodePtr &tree) {\n    tree = obj.pool.template allocate<Node>();\n    load_value(stream, *tree);\n    if (tree->child1 != NULL) {\n      load_tree(obj, stream, tree->child1);\n    }\n    if (tree->child2 != NULL) {\n      load_tree(obj, stream, tree->child2);\n    }\n  }\n\n  /**  Stores the index in a binary file.\n   *   IMPORTANT NOTE: The set of data points is NOT stored in the file, so when\n   * loading the index object it must be constructed associated to the same\n   * source of data points used while building it. See the example:\n   * examples/saveload_example.cpp \\sa loadIndex  */\n  void saveIndex_(Derived &obj, FILE *stream) {\n    save_value(stream, obj.m_size);\n    save_value(stream, obj.dim);\n    save_value(stream, obj.root_bbox);\n    save_value(stream, obj.m_leaf_max_size);\n    save_value(stream, obj.vind);\n    save_tree(obj, stream, obj.root_node);\n  }\n\n  /**  Loads a previous index from a binary file.\n   *   IMPORTANT NOTE: The set of data points is NOT stored in the file, so the\n   * index object must be constructed associated to the same source of data\n   * points used while building the index. See the example:\n   * examples/saveload_example.cpp \\sa loadIndex  */\n  void loadIndex_(Derived &obj, FILE *stream) {\n    load_value(stream, obj.m_size);\n    load_value(stream, obj.dim);\n    load_value(stream, obj.root_bbox);\n    load_value(stream, obj.m_leaf_max_size);\n    load_value(stream, obj.vind);\n    load_tree(obj, stream, obj.root_node);\n  }\n};\n\n/** @addtogroup kdtrees_grp KD-tree classes and adaptors\n * @{ */\n\n/** kd-tree static index\n *\n * Contains the k-d trees and other information for indexing a set of points\n * for nearest-neighbor matching.\n *\n *  The class \"DatasetAdaptor\" must provide the following interface (can be\n * non-virtual, inlined methods):\n *\n *  \\code\n *   // Must return the number of data poins\n *   inline size_t kdtree_get_point_count() const { ... }\n *\n *\n *   // Must return the dim'th component of the idx'th point in the class:\n *   inline T kdtree_get_pt(const size_t idx, const size_t dim) const { ... }\n *\n *   // Optional bounding-box computation: return false to default to a standard\n * bbox computation loop.\n *   //   Return true if the BBOX was already computed by the class and returned\n * in \"bb\" so it can be avoided to redo it again.\n *   //   Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3\n * for point clouds) template <class BBOX> bool kdtree_get_bbox(BBOX &bb) const\n *   {\n *      bb[0].low = ...; bb[0].high = ...;  // 0th dimension limits\n *      bb[1].low = ...; bb[1].high = ...;  // 1st dimension limits\n *      ...\n *      return true;\n *   }\n *\n *  \\endcode\n *\n * \\tparam DatasetAdaptor The user-provided adaptor (see comments above).\n * \\tparam Distance The distance metric to use: nanoflann::metric_L1,\n * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \\tparam DIM\n * Dimensionality of data points (e.g. 3 for 3D points) \\tparam IndexType Will\n * be typically size_t or int\n */\ntemplate <typename Distance, class DatasetAdaptor, int DIM = -1,\n          typename IndexType = size_t>\nclass KDTreeSingleIndexAdaptor\n    : public KDTreeBaseClass<\n          KDTreeSingleIndexAdaptor<Distance, DatasetAdaptor, DIM, IndexType>,\n          Distance, DatasetAdaptor, DIM, IndexType> {\npublic:\n  /** Deleted copy constructor*/\n  KDTreeSingleIndexAdaptor(\n      const KDTreeSingleIndexAdaptor<Distance, DatasetAdaptor, DIM, IndexType>\n          &) = delete;\n\n  /**\n   * The dataset used by this index\n   */\n  const DatasetAdaptor &dataset; //!< The source of our data\n\n  const KDTreeSingleIndexAdaptorParams index_params;\n\n  Distance distance;\n\n  typedef typename nanoflann::KDTreeBaseClass<\n      nanoflann::KDTreeSingleIndexAdaptor<Distance, DatasetAdaptor, DIM,\n                                          IndexType>,\n      Distance, DatasetAdaptor, DIM, IndexType>\n      BaseClassRef;\n\n  typedef typename BaseClassRef::ElementType ElementType;\n  typedef typename BaseClassRef::DistanceType DistanceType;\n\n  typedef typename BaseClassRef::Node Node;\n  typedef Node *NodePtr;\n\n  typedef typename BaseClassRef::Interval Interval;\n  /** Define \"BoundingBox\" as a fixed-size or variable-size container depending\n   * on \"DIM\" */\n  typedef typename BaseClassRef::BoundingBox BoundingBox;\n\n  /** Define \"distance_vector_t\" as a fixed-size or variable-size container\n   * depending on \"DIM\" */\n  typedef typename BaseClassRef::distance_vector_t distance_vector_t;\n\n  /**\n   * KDTree constructor\n   *\n   * Refer to docs in README.md or online in\n   * https://github.com/jlblancoc/nanoflann\n   *\n   * The KD-Tree point dimension (the length of each point in the datase, e.g. 3\n   * for 3D points) is determined by means of:\n   *  - The \\a DIM template parameter if >0 (highest priority)\n   *  - Otherwise, the \\a dimensionality parameter of this constructor.\n   *\n   * @param inputData Dataset with the input features\n   * @param params Basically, the maximum leaf node size\n   */\n  KDTreeSingleIndexAdaptor(const int dimensionality,\n                           const DatasetAdaptor &inputData,\n                           const KDTreeSingleIndexAdaptorParams &params =\n                               KDTreeSingleIndexAdaptorParams())\n      : dataset(inputData), index_params(params), distance(inputData) {\n    BaseClassRef::root_node = NULL;\n    BaseClassRef::m_size = dataset.kdtree_get_point_count();\n    BaseClassRef::m_size_at_index_build = BaseClassRef::m_size;\n    BaseClassRef::dim = dimensionality;\n    if (DIM > 0)\n      BaseClassRef::dim = DIM;\n    BaseClassRef::m_leaf_max_size = params.leaf_max_size;\n\n    // Create a permutable array of indices to the input vectors.\n    init_vind();\n  }\n\n  /**\n   * Builds the index\n   */\n  void buildIndex() {\n    BaseClassRef::m_size = dataset.kdtree_get_point_count();\n    BaseClassRef::m_size_at_index_build = BaseClassRef::m_size;\n    init_vind();\n    this->freeIndex(*this);\n    BaseClassRef::m_size_at_index_build = BaseClassRef::m_size;\n    if (BaseClassRef::m_size == 0)\n      return;\n    computeBoundingBox(BaseClassRef::root_bbox);\n    BaseClassRef::root_node =\n        this->divideTree(*this, 0, BaseClassRef::m_size,\n                         BaseClassRef::root_bbox); // construct the tree\n  }\n\n  /** \\name Query methods\n   * @{ */\n\n  /**\n   * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored\n   * inside the result object.\n   *\n   * Params:\n   *     result = the result object in which the indices of the\n   * nearest-neighbors are stored vec = the vector for which to search the\n   * nearest neighbors\n   *\n   * \\tparam RESULTSET Should be any ResultSet<DistanceType>\n   * \\return  True if the requested neighbors could be found.\n   * \\sa knnSearch, radiusSearch\n   */\n  template <typename RESULTSET>\n  bool findNeighbors(RESULTSET &result, const ElementType *vec,\n                     const SearchParams &searchParams) const {\n    assert(vec);\n    if (this->size(*this) == 0)\n      return false;\n    if (!BaseClassRef::root_node)\n      throw std::runtime_error(\n          \"[nanoflann] findNeighbors() called before building the index.\");\n    float epsError = 1 + searchParams.eps;\n\n    distance_vector_t\n        dists; // fixed or variable-sized container (depending on DIM)\n    auto zero = static_cast<decltype(result.worstDist())>(0);\n    assign(dists, (DIM > 0 ? DIM : BaseClassRef::dim),\n           zero); // Fill it with zeros.\n    DistanceType distsq = this->computeInitialDistances(*this, vec, dists);\n    searchLevel(result, vec, BaseClassRef::root_node, distsq, dists,\n                epsError); // \"count_leaf\" parameter removed since was neither\n                           // used nor returned to the user.\n    return result.full();\n  }\n\n  /**\n   * Find the \"num_closest\" nearest neighbors to the \\a query_point[0:dim-1].\n   * Their indices are stored inside the result object. \\sa radiusSearch,\n   * findNeighbors \\note nChecks_IGNORED is ignored but kept for compatibility\n   * with the original FLANN interface. \\return Number `N` of valid points in\n   * the result set. Only the first `N` entries in `out_indices` and\n   * `out_distances_sq` will be valid. Return may be less than `num_closest`\n   * only if the number of elements in the tree is less than `num_closest`.\n   */\n  size_t knnSearch(const ElementType *query_point, const size_t num_closest,\n                   IndexType *out_indices, DistanceType *out_distances_sq,\n                   const int /* nChecks_IGNORED */ = 10) const {\n    nanoflann::KNNResultSet<DistanceType, IndexType> resultSet(num_closest);\n    resultSet.init(out_indices, out_distances_sq);\n    this->findNeighbors(resultSet, query_point, nanoflann::SearchParams());\n    return resultSet.size();\n  }\n\n  /**\n   * Find all the neighbors to \\a query_point[0:dim-1] within a maximum radius.\n   *  The output is given as a vector of pairs, of which the first element is a\n   * point index and the second the corresponding distance. Previous contents of\n   * \\a IndicesDists are cleared.\n   *\n   *  If searchParams.sorted==true, the output list is sorted by ascending\n   * distances.\n   *\n   *  For a better performance, it is advisable to do a .reserve() on the vector\n   * if you have any wild guess about the number of expected matches.\n   *\n   *  \\sa knnSearch, findNeighbors, radiusSearchCustomCallback\n   * \\return The number of points within the given radius (i.e. indices.size()\n   * or dists.size() )\n   */\n  size_t\n  radiusSearch(const ElementType *query_point, const DistanceType &radius,\n               std::vector<std::pair<IndexType, DistanceType>> &IndicesDists,\n               const SearchParams &searchParams) const {\n    RadiusResultSet<DistanceType, IndexType> resultSet(radius, IndicesDists);\n    const size_t nFound =\n        radiusSearchCustomCallback(query_point, resultSet, searchParams);\n    if (searchParams.sorted)\n      std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter());\n    return nFound;\n  }\n\n  /**\n   * Just like radiusSearch() but with a custom callback class for each point\n   * found in the radius of the query. See the source of RadiusResultSet<> as a\n   * start point for your own classes. \\sa radiusSearch\n   */\n  template <class SEARCH_CALLBACK>\n  size_t radiusSearchCustomCallback(\n      const ElementType *query_point, SEARCH_CALLBACK &resultSet,\n      const SearchParams &searchParams = SearchParams()) const {\n    this->findNeighbors(resultSet, query_point, searchParams);\n    return resultSet.size();\n  }\n\n  /** @} */\n\npublic:\n  /** Make sure the auxiliary list \\a vind has the same size than the current\n   * dataset, and re-generate if size has changed. */\n  void init_vind() {\n    // Create a permutable array of indices to the input vectors.\n    BaseClassRef::m_size = dataset.kdtree_get_point_count();\n    if (BaseClassRef::vind.size() != BaseClassRef::m_size)\n      BaseClassRef::vind.resize(BaseClassRef::m_size);\n    for (size_t i = 0; i < BaseClassRef::m_size; i++)\n      BaseClassRef::vind[i] = i;\n  }\n\n  void computeBoundingBox(BoundingBox &bbox) {\n    resize(bbox, (DIM > 0 ? DIM : BaseClassRef::dim));\n    if (dataset.kdtree_get_bbox(bbox)) {\n      // Done! It was implemented in derived class\n    } else {\n      const size_t N = dataset.kdtree_get_point_count();\n      if (!N)\n        throw std::runtime_error(\"[nanoflann] computeBoundingBox() called but \"\n                                 \"no data points found.\");\n      for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) {\n        bbox[i].low = bbox[i].high = this->dataset_get(*this, 0, i);\n      }\n      for (size_t k = 1; k < N; ++k) {\n        for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) {\n          if (this->dataset_get(*this, k, i) < bbox[i].low)\n            bbox[i].low = this->dataset_get(*this, k, i);\n          if (this->dataset_get(*this, k, i) > bbox[i].high)\n            bbox[i].high = this->dataset_get(*this, k, i);\n        }\n      }\n    }\n  }\n\n  /**\n   * Performs an exact search in the tree starting from a node.\n   * \\tparam RESULTSET Should be any ResultSet<DistanceType>\n   * \\return true if the search should be continued, false if the results are\n   * sufficient\n   */\n  template <class RESULTSET>\n  bool searchLevel(RESULTSET &result_set, const ElementType *vec,\n                   const NodePtr node, DistanceType mindistsq,\n                   distance_vector_t &dists, const float epsError) const {\n    /* If this is a leaf node, then do check and return. */\n    if ((node->child1 == NULL) && (node->child2 == NULL)) {\n      // count_leaf += (node->lr.right-node->lr.left);  // Removed since was\n      // neither used nor returned to the user.\n      DistanceType worst_dist = result_set.worstDist();\n      for (IndexType i = node->node_type.lr.left; i < node->node_type.lr.right;\n           ++i) {\n        const IndexType index = BaseClassRef::vind[i]; // reorder... : i;\n        DistanceType dist = distance.evalMetric(\n            vec, index, (DIM > 0 ? DIM : BaseClassRef::dim));\n        if (dist < worst_dist) {\n          if (!result_set.addPoint(dist, BaseClassRef::vind[i])) {\n            // the resultset doesn't want to receive any more points, we're done\n            // searching!\n            return false;\n          }\n        }\n      }\n      return true;\n    }\n\n    /* Which child branch should be taken first? */\n    int idx = node->node_type.sub.divfeat;\n    ElementType val = vec[idx];\n    DistanceType diff1 = val - node->node_type.sub.divlow;\n    DistanceType diff2 = val - node->node_type.sub.divhigh;\n\n    NodePtr bestChild;\n    NodePtr otherChild;\n    DistanceType cut_dist;\n    if ((diff1 + diff2) < 0) {\n      bestChild = node->child1;\n      otherChild = node->child2;\n      cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx);\n    } else {\n      bestChild = node->child2;\n      otherChild = node->child1;\n      cut_dist = distance.accum_dist(val, node->node_type.sub.divlow, idx);\n    }\n\n    /* Call recursively to search next level down. */\n    if (!searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError)) {\n      // the resultset doesn't want to receive any more points, we're done\n      // searching!\n      return false;\n    }\n\n    DistanceType dst = dists[idx];\n    mindistsq = mindistsq + cut_dist - dst;\n    dists[idx] = cut_dist;\n    if (mindistsq * epsError <= result_set.worstDist()) {\n      if (!searchLevel(result_set, vec, otherChild, mindistsq, dists,\n                       epsError)) {\n        // the resultset doesn't want to receive any more points, we're done\n        // searching!\n        return false;\n      }\n    }\n    dists[idx] = dst;\n    return true;\n  }\n\npublic:\n  /**  Stores the index in a binary file.\n   *   IMPORTANT NOTE: The set of data points is NOT stored in the file, so when\n   * loading the index object it must be constructed associated to the same\n   * source of data points used while building it. See the example:\n   * examples/saveload_example.cpp \\sa loadIndex  */\n  void saveIndex(FILE *stream) { this->saveIndex_(*this, stream); }\n\n  /**  Loads a previous index from a binary file.\n   *   IMPORTANT NOTE: The set of data points is NOT stored in the file, so the\n   * index object must be constructed associated to the same source of data\n   * points used while building the index. See the example:\n   * examples/saveload_example.cpp \\sa loadIndex  */\n  void loadIndex(FILE *stream) { this->loadIndex_(*this, stream); }\n\n}; // class KDTree\n\n/** kd-tree dynamic index\n *\n * Contains the k-d trees and other information for indexing a set of points\n * for nearest-neighbor matching.\n *\n *  The class \"DatasetAdaptor\" must provide the following interface (can be\n * non-virtual, inlined methods):\n *\n *  \\code\n *   // Must return the number of data poins\n *   inline size_t kdtree_get_point_count() const { ... }\n *\n *   // Must return the dim'th component of the idx'th point in the class:\n *   inline T kdtree_get_pt(const size_t idx, const size_t dim) const { ... }\n *\n *   // Optional bounding-box computation: return false to default to a standard\n * bbox computation loop.\n *   //   Return true if the BBOX was already computed by the class and returned\n * in \"bb\" so it can be avoided to redo it again.\n *   //   Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3\n * for point clouds) template <class BBOX> bool kdtree_get_bbox(BBOX &bb) const\n *   {\n *      bb[0].low = ...; bb[0].high = ...;  // 0th dimension limits\n *      bb[1].low = ...; bb[1].high = ...;  // 1st dimension limits\n *      ...\n *      return true;\n *   }\n *\n *  \\endcode\n *\n * \\tparam DatasetAdaptor The user-provided adaptor (see comments above).\n * \\tparam Distance The distance metric to use: nanoflann::metric_L1,\n * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \\tparam DIM\n * Dimensionality of data points (e.g. 3 for 3D points) \\tparam IndexType Will\n * be typically size_t or int\n */\ntemplate <typename Distance, class DatasetAdaptor, int DIM = -1,\n          typename IndexType = size_t>\nclass KDTreeSingleIndexDynamicAdaptor_\n    : public KDTreeBaseClass<KDTreeSingleIndexDynamicAdaptor_<\n                                 Distance, DatasetAdaptor, DIM, IndexType>,\n                             Distance, DatasetAdaptor, DIM, IndexType> {\npublic:\n  /**\n   * The dataset used by this index\n   */\n  const DatasetAdaptor &dataset; //!< The source of our data\n\n  KDTreeSingleIndexAdaptorParams index_params;\n\n  std::vector<int> &treeIndex;\n\n  Distance distance;\n\n  typedef typename nanoflann::KDTreeBaseClass<\n      nanoflann::KDTreeSingleIndexDynamicAdaptor_<Distance, DatasetAdaptor, DIM,\n                                                  IndexType>,\n      Distance, DatasetAdaptor, DIM, IndexType>\n      BaseClassRef;\n\n  typedef typename BaseClassRef::ElementType ElementType;\n  typedef typename BaseClassRef::DistanceType DistanceType;\n\n  typedef typename BaseClassRef::Node Node;\n  typedef Node *NodePtr;\n\n  typedef typename BaseClassRef::Interval Interval;\n  /** Define \"BoundingBox\" as a fixed-size or variable-size container depending\n   * on \"DIM\" */\n  typedef typename BaseClassRef::BoundingBox BoundingBox;\n\n  /** Define \"distance_vector_t\" as a fixed-size or variable-size container\n   * depending on \"DIM\" */\n  typedef typename BaseClassRef::distance_vector_t distance_vector_t;\n\n  /**\n   * KDTree constructor\n   *\n   * Refer to docs in README.md or online in\n   * https://github.com/jlblancoc/nanoflann\n   *\n   * The KD-Tree point dimension (the length of each point in the datase, e.g. 3\n   * for 3D points) is determined by means of:\n   *  - The \\a DIM template parameter if >0 (highest priority)\n   *  - Otherwise, the \\a dimensionality parameter of this constructor.\n   *\n   * @param inputData Dataset with the input features\n   * @param params Basically, the maximum leaf node size\n   */\n  KDTreeSingleIndexDynamicAdaptor_(\n      const int dimensionality, const DatasetAdaptor &inputData,\n      std::vector<int> &treeIndex_,\n      const KDTreeSingleIndexAdaptorParams &params =\n          KDTreeSingleIndexAdaptorParams())\n      : dataset(inputData), index_params(params), treeIndex(treeIndex_),\n        distance(inputData) {\n    BaseClassRef::root_node = NULL;\n    BaseClassRef::m_size = 0;\n    BaseClassRef::m_size_at_index_build = 0;\n    BaseClassRef::dim = dimensionality;\n    if (DIM > 0)\n      BaseClassRef::dim = DIM;\n    BaseClassRef::m_leaf_max_size = params.leaf_max_size;\n  }\n\n  /** Assignment operator definiton */\n  KDTreeSingleIndexDynamicAdaptor_\n  operator=(const KDTreeSingleIndexDynamicAdaptor_ &rhs) {\n    KDTreeSingleIndexDynamicAdaptor_ tmp(rhs);\n    std::swap(BaseClassRef::vind, tmp.BaseClassRef::vind);\n    std::swap(BaseClassRef::m_leaf_max_size, tmp.BaseClassRef::m_leaf_max_size);\n    std::swap(index_params, tmp.index_params);\n    std::swap(treeIndex, tmp.treeIndex);\n    std::swap(BaseClassRef::m_size, tmp.BaseClassRef::m_size);\n    std::swap(BaseClassRef::m_size_at_index_build,\n              tmp.BaseClassRef::m_size_at_index_build);\n    std::swap(BaseClassRef::root_node, tmp.BaseClassRef::root_node);\n    std::swap(BaseClassRef::root_bbox, tmp.BaseClassRef::root_bbox);\n    std::swap(BaseClassRef::pool, tmp.BaseClassRef::pool);\n    return *this;\n  }\n\n  /**\n   * Builds the index\n   */\n  void buildIndex() {\n    BaseClassRef::m_size = BaseClassRef::vind.size();\n    this->freeIndex(*this);\n    BaseClassRef::m_size_at_index_build = BaseClassRef::m_size;\n    if (BaseClassRef::m_size == 0)\n      return;\n    computeBoundingBox(BaseClassRef::root_bbox);\n    BaseClassRef::root_node =\n        this->divideTree(*this, 0, BaseClassRef::m_size,\n                         BaseClassRef::root_bbox); // construct the tree\n  }\n\n  /** \\name Query methods\n   * @{ */\n\n  /**\n   * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored\n   * inside the result object.\n   *\n   * Params:\n   *     result = the result object in which the indices of the\n   * nearest-neighbors are stored vec = the vector for which to search the\n   * nearest neighbors\n   *\n   * \\tparam RESULTSET Should be any ResultSet<DistanceType>\n   * \\return  True if the requested neighbors could be found.\n   * \\sa knnSearch, radiusSearch\n   */\n  template <typename RESULTSET>\n  bool findNeighbors(RESULTSET &result, const ElementType *vec,\n                     const SearchParams &searchParams) const {\n    assert(vec);\n    if (this->size(*this) == 0)\n      return false;\n    if (!BaseClassRef::root_node)\n      return false;\n    float epsError = 1 + searchParams.eps;\n\n    // fixed or variable-sized container (depending on DIM)\n    distance_vector_t dists;\n    // Fill it with zeros.\n    assign(dists, (DIM > 0 ? DIM : BaseClassRef::dim),\n           static_cast<typename distance_vector_t::value_type>(0));\n    DistanceType distsq = this->computeInitialDistances(*this, vec, dists);\n    searchLevel(result, vec, BaseClassRef::root_node, distsq, dists,\n                epsError); // \"count_leaf\" parameter removed since was neither\n                           // used nor returned to the user.\n    return result.full();\n  }\n\n  /**\n   * Find the \"num_closest\" nearest neighbors to the \\a query_point[0:dim-1].\n   * Their indices are stored inside the result object. \\sa radiusSearch,\n   * findNeighbors \\note nChecks_IGNORED is ignored but kept for compatibility\n   * with the original FLANN interface. \\return Number `N` of valid points in\n   * the result set. Only the first `N` entries in `out_indices` and\n   * `out_distances_sq` will be valid. Return may be less than `num_closest`\n   * only if the number of elements in the tree is less than `num_closest`.\n   */\n  size_t knnSearch(const ElementType *query_point, const size_t num_closest,\n                   IndexType *out_indices, DistanceType *out_distances_sq,\n                   const int /* nChecks_IGNORED */ = 10) const {\n    nanoflann::KNNResultSet<DistanceType, IndexType> resultSet(num_closest);\n    resultSet.init(out_indices, out_distances_sq);\n    this->findNeighbors(resultSet, query_point, nanoflann::SearchParams());\n    return resultSet.size();\n  }\n\n  /**\n   * Find all the neighbors to \\a query_point[0:dim-1] within a maximum radius.\n   *  The output is given as a vector of pairs, of which the first element is a\n   * point index and the second the corresponding distance. Previous contents of\n   * \\a IndicesDists are cleared.\n   *\n   *  If searchParams.sorted==true, the output list is sorted by ascending\n   * distances.\n   *\n   *  For a better performance, it is advisable to do a .reserve() on the vector\n   * if you have any wild guess about the number of expected matches.\n   *\n   *  \\sa knnSearch, findNeighbors, radiusSearchCustomCallback\n   * \\return The number of points within the given radius (i.e. indices.size()\n   * or dists.size() )\n   */\n  size_t\n  radiusSearch(const ElementType *query_point, const DistanceType &radius,\n               std::vector<std::pair<IndexType, DistanceType>> &IndicesDists,\n               const SearchParams &searchParams) const {\n    RadiusResultSet<DistanceType, IndexType> resultSet(radius, IndicesDists);\n    const size_t nFound =\n        radiusSearchCustomCallback(query_point, resultSet, searchParams);\n    if (searchParams.sorted)\n      std::sort(IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter());\n    return nFound;\n  }\n\n  /**\n   * Just like radiusSearch() but with a custom callback class for each point\n   * found in the radius of the query. See the source of RadiusResultSet<> as a\n   * start point for your own classes. \\sa radiusSearch\n   */\n  template <class SEARCH_CALLBACK>\n  size_t radiusSearchCustomCallback(\n      const ElementType *query_point, SEARCH_CALLBACK &resultSet,\n      const SearchParams &searchParams = SearchParams()) const {\n    this->findNeighbors(resultSet, query_point, searchParams);\n    return resultSet.size();\n  }\n\n  /** @} */\n\npublic:\n  void computeBoundingBox(BoundingBox &bbox) {\n    resize(bbox, (DIM > 0 ? DIM : BaseClassRef::dim));\n\n    if (dataset.kdtree_get_bbox(bbox)) {\n      // Done! It was implemented in derived class\n    } else {\n      const size_t N = BaseClassRef::m_size;\n      if (!N)\n        throw std::runtime_error(\"[nanoflann] computeBoundingBox() called but \"\n                                 \"no data points found.\");\n      for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) {\n        bbox[i].low = bbox[i].high =\n            this->dataset_get(*this, BaseClassRef::vind[0], i);\n      }\n      for (size_t k = 1; k < N; ++k) {\n        for (int i = 0; i < (DIM > 0 ? DIM : BaseClassRef::dim); ++i) {\n          if (this->dataset_get(*this, BaseClassRef::vind[k], i) < bbox[i].low)\n            bbox[i].low = this->dataset_get(*this, BaseClassRef::vind[k], i);\n          if (this->dataset_get(*this, BaseClassRef::vind[k], i) > bbox[i].high)\n            bbox[i].high = this->dataset_get(*this, BaseClassRef::vind[k], i);\n        }\n      }\n    }\n  }\n\n  /**\n   * Performs an exact search in the tree starting from a node.\n   * \\tparam RESULTSET Should be any ResultSet<DistanceType>\n   */\n  template <class RESULTSET>\n  void searchLevel(RESULTSET &result_set, const ElementType *vec,\n                   const NodePtr node, DistanceType mindistsq,\n                   distance_vector_t &dists, const float epsError) const {\n    /* If this is a leaf node, then do check and return. */\n    if ((node->child1 == NULL) && (node->child2 == NULL)) {\n      // count_leaf += (node->lr.right-node->lr.left);  // Removed since was\n      // neither used nor returned to the user.\n      DistanceType worst_dist = result_set.worstDist();\n      for (IndexType i = node->node_type.lr.left; i < node->node_type.lr.right;\n           ++i) {\n        const IndexType index = BaseClassRef::vind[i]; // reorder... : i;\n        if (treeIndex[index] == -1)\n          continue;\n        DistanceType dist = distance.evalMetric(\n            vec, index, (DIM > 0 ? DIM : BaseClassRef::dim));\n        if (dist < worst_dist) {\n          if (!result_set.addPoint(\n                  static_cast<typename RESULTSET::DistanceType>(dist),\n                  static_cast<typename RESULTSET::IndexType>(\n                      BaseClassRef::vind[i]))) {\n            // the resultset doesn't want to receive any more points, we're done\n            // searching!\n            return; // false;\n          }\n        }\n      }\n      return;\n    }\n\n    /* Which child branch should be taken first? */\n    int idx = node->node_type.sub.divfeat;\n    ElementType val = vec[idx];\n    DistanceType diff1 = val - node->node_type.sub.divlow;\n    DistanceType diff2 = val - node->node_type.sub.divhigh;\n\n    NodePtr bestChild;\n    NodePtr otherChild;\n    DistanceType cut_dist;\n    if ((diff1 + diff2) < 0) {\n      bestChild = node->child1;\n      otherChild = node->child2;\n      cut_dist = distance.accum_dist(val, node->node_type.sub.divhigh, idx);\n    } else {\n      bestChild = node->child2;\n      otherChild = node->child1;\n      cut_dist = distance.accum_dist(val, node->node_type.sub.divlow, idx);\n    }\n\n    /* Call recursively to search next level down. */\n    searchLevel(result_set, vec, bestChild, mindistsq, dists, epsError);\n\n    DistanceType dst = dists[idx];\n    mindistsq = mindistsq + cut_dist - dst;\n    dists[idx] = cut_dist;\n    if (mindistsq * epsError <= result_set.worstDist()) {\n      searchLevel(result_set, vec, otherChild, mindistsq, dists, epsError);\n    }\n    dists[idx] = dst;\n  }\n\npublic:\n  /**  Stores the index in a binary file.\n   *   IMPORTANT NOTE: The set of data points is NOT stored in the file, so when\n   * loading the index object it must be constructed associated to the same\n   * source of data points used while building it. See the example:\n   * examples/saveload_example.cpp \\sa loadIndex  */\n  void saveIndex(FILE *stream) { this->saveIndex_(*this, stream); }\n\n  /**  Loads a previous index from a binary file.\n   *   IMPORTANT NOTE: The set of data points is NOT stored in the file, so the\n   * index object must be constructed associated to the same source of data\n   * points used while building the index. See the example:\n   * examples/saveload_example.cpp \\sa loadIndex  */\n  void loadIndex(FILE *stream) { this->loadIndex_(*this, stream); }\n};\n\n/** kd-tree dynaimic index\n *\n * class to create multiple static index and merge their results to behave as\n * single dynamic index as proposed in Logarithmic Approach.\n *\n *  Example of usage:\n *  examples/dynamic_pointcloud_example.cpp\n *\n * \\tparam DatasetAdaptor The user-provided adaptor (see comments above).\n * \\tparam Distance The distance metric to use: nanoflann::metric_L1,\n * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \\tparam DIM\n * Dimensionality of data points (e.g. 3 for 3D points) \\tparam IndexType Will\n * be typically size_t or int\n */\ntemplate <typename Distance, class DatasetAdaptor, int DIM = -1,\n          typename IndexType = size_t>\nclass KDTreeSingleIndexDynamicAdaptor {\npublic:\n  typedef typename Distance::ElementType ElementType;\n  typedef typename Distance::DistanceType DistanceType;\n\nprotected:\n  size_t m_leaf_max_size;\n  size_t treeCount;\n  size_t pointCount;\n\n  /**\n   * The dataset used by this index\n   */\n  const DatasetAdaptor &dataset; //!< The source of our data\n\n  std::vector<int> treeIndex; //!< treeIndex[idx] is the index of tree in which\n                              //!< point at idx is stored. treeIndex[idx]=-1\n                              //!< means that point has been removed.\n\n  KDTreeSingleIndexAdaptorParams index_params;\n\n  int dim; //!< Dimensionality of each data point\n\n  typedef KDTreeSingleIndexDynamicAdaptor_<Distance, DatasetAdaptor, DIM>\n      index_container_t;\n  std::vector<index_container_t> index;\n\npublic:\n  /** Get a const ref to the internal list of indices; the number of indices is\n   * adapted dynamically as the dataset grows in size. */\n  const std::vector<index_container_t> &getAllIndices() const { return index; }\n\nprivate:\n  /** finds position of least significant unset bit */\n  int First0Bit(IndexType num) {\n    int pos = 0;\n    while (num & 1) {\n      num = num >> 1;\n      pos++;\n    }\n    return pos;\n  }\n\n  /** Creates multiple empty trees to handle dynamic support */\n  void init() {\n    typedef KDTreeSingleIndexDynamicAdaptor_<Distance, DatasetAdaptor, DIM>\n        my_kd_tree_t;\n    std::vector<my_kd_tree_t> index_(\n        treeCount, my_kd_tree_t(dim /*dim*/, dataset, treeIndex, index_params));\n    index = index_;\n  }\n\npublic:\n  Distance distance;\n\n  /**\n   * KDTree constructor\n   *\n   * Refer to docs in README.md or online in\n   * https://github.com/jlblancoc/nanoflann\n   *\n   * The KD-Tree point dimension (the length of each point in the datase, e.g. 3\n   * for 3D points) is determined by means of:\n   *  - The \\a DIM template parameter if >0 (highest priority)\n   *  - Otherwise, the \\a dimensionality parameter of this constructor.\n   *\n   * @param inputData Dataset with the input features\n   * @param params Basically, the maximum leaf node size\n   */\n  KDTreeSingleIndexDynamicAdaptor(const int dimensionality,\n                                  const DatasetAdaptor &inputData,\n                                  const KDTreeSingleIndexAdaptorParams &params =\n                                      KDTreeSingleIndexAdaptorParams(),\n                                  const size_t maximumPointCount = 1000000000U)\n      : dataset(inputData), index_params(params), distance(inputData) {\n    treeCount = static_cast<size_t>(std::log2(maximumPointCount));\n    pointCount = 0U;\n    dim = dimensionality;\n    treeIndex.clear();\n    if (DIM > 0)\n      dim = DIM;\n    m_leaf_max_size = params.leaf_max_size;\n    init();\n    const size_t num_initial_points = dataset.kdtree_get_point_count();\n    if (num_initial_points > 0) {\n      addPoints(0, num_initial_points - 1);\n    }\n  }\n\n  /** Deleted copy constructor*/\n  KDTreeSingleIndexDynamicAdaptor(\n      const KDTreeSingleIndexDynamicAdaptor<Distance, DatasetAdaptor, DIM,\n                                            IndexType> &) = delete;\n\n  /** Add points to the set, Inserts all points from [start, end] */\n  void addPoints(IndexType start, IndexType end) {\n    size_t count = end - start + 1;\n    treeIndex.resize(treeIndex.size() + count);\n    for (IndexType idx = start; idx <= end; idx++) {\n      int pos = First0Bit(pointCount);\n      index[pos].vind.clear();\n      treeIndex[pointCount] = pos;\n      for (int i = 0; i < pos; i++) {\n        for (int j = 0; j < static_cast<int>(index[i].vind.size()); j++) {\n          index[pos].vind.push_back(index[i].vind[j]);\n          if (treeIndex[index[i].vind[j]] != -1)\n            treeIndex[index[i].vind[j]] = pos;\n        }\n        index[i].vind.clear();\n        index[i].freeIndex(index[i]);\n      }\n      index[pos].vind.push_back(idx);\n      index[pos].buildIndex();\n      pointCount++;\n    }\n  }\n\n  /** Remove a point from the set (Lazy Deletion) */\n  void removePoint(size_t idx) {\n    if (idx >= pointCount)\n      return;\n    treeIndex[idx] = -1;\n  }\n\n  /**\n   * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored\n   * inside the result object.\n   *\n   * Params:\n   *     result = the result object in which the indices of the\n   * nearest-neighbors are stored vec = the vector for which to search the\n   * nearest neighbors\n   *\n   * \\tparam RESULTSET Should be any ResultSet<DistanceType>\n   * \\return  True if the requested neighbors could be found.\n   * \\sa knnSearch, radiusSearch\n   */\n  template <typename RESULTSET>\n  bool findNeighbors(RESULTSET &result, const ElementType *vec,\n                     const SearchParams &searchParams) const {\n    for (size_t i = 0; i < treeCount; i++) {\n      index[i].findNeighbors(result, &vec[0], searchParams);\n    }\n    return result.full();\n  }\n};\n\n/** An L2-metric KD-tree adaptor for working with data directly stored in an\n * Eigen Matrix, without duplicating the data storage. Each row in the matrix\n * represents a point in the state space.\n *\n *  Example of usage:\n * \\code\n * \tEigen::Matrix<num_t,Dynamic,Dynamic>  mat;\n * \t// Fill out \"mat\"...\n *\n * \ttypedef KDTreeEigenMatrixAdaptor< Eigen::Matrix<num_t,Dynamic,Dynamic> >\n * my_kd_tree_t; const int max_leaf = 10; my_kd_tree_t   mat_index(mat, max_leaf\n * ); mat_index.index->buildIndex(); mat_index.index->... \\endcode\n *\n *  \\tparam DIM If set to >0, it specifies a compile-time fixed dimensionality\n * for the points in the data set, allowing more compiler optimizations. \\tparam\n * Distance The distance metric to use: nanoflann::metric_L1,\n * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc.\n */\ntemplate <class MatrixType, int DIM = -1, class Distance = nanoflann::metric_L2>\nstruct KDTreeEigenMatrixAdaptor {\n  typedef KDTreeEigenMatrixAdaptor<MatrixType, DIM, Distance> self_t;\n  typedef typename MatrixType::Scalar num_t;\n  typedef typename MatrixType::Index IndexType;\n  typedef\n      typename Distance::template traits<num_t, self_t>::distance_t metric_t;\n  typedef KDTreeSingleIndexAdaptor<metric_t, self_t,\n                                   MatrixType::ColsAtCompileTime, IndexType>\n      index_t;\n\n  index_t *index; //! The kd-tree index for the user to call its methods as\n                  //! usual with any other FLANN index.\n\n  /// Constructor: takes a const ref to the matrix object with the data points\n  KDTreeEigenMatrixAdaptor(const size_t dimensionality,\n                           const std::reference_wrapper<const MatrixType> &mat,\n                           const int leaf_max_size = 10)\n      : m_data_matrix(mat) {\n    const auto dims = mat.get().cols();\n    if (size_t(dims) != dimensionality)\n      throw std::runtime_error(\n          \"Error: 'dimensionality' must match column count in data matrix\");\n    if (DIM > 0 && int(dims) != DIM)\n      throw std::runtime_error(\n          \"Data set dimensionality does not match the 'DIM' template argument\");\n    index =\n        new index_t(static_cast<int>(dims), *this /* adaptor */,\n                    nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size));\n    index->buildIndex();\n  }\n\npublic:\n  /** Deleted copy constructor */\n  KDTreeEigenMatrixAdaptor(const self_t &) = delete;\n\n  ~KDTreeEigenMatrixAdaptor() { delete index; }\n\n  const std::reference_wrapper<const MatrixType> m_data_matrix;\n\n  /** Query for the \\a num_closest closest points to a given point (entered as\n   * query_point[0:dim-1]). Note that this is a short-cut method for\n   * index->findNeighbors(). The user can also call index->... methods as\n   * desired. \\note nChecks_IGNORED is ignored but kept for compatibility with\n   * the original FLANN interface.\n   */\n  inline void query(const num_t *query_point, const size_t num_closest,\n                    IndexType *out_indices, num_t *out_distances_sq,\n                    const int /* nChecks_IGNORED */ = 10) const {\n    nanoflann::KNNResultSet<num_t, IndexType> resultSet(num_closest);\n    resultSet.init(out_indices, out_distances_sq);\n    index->findNeighbors(resultSet, query_point, nanoflann::SearchParams());\n  }\n\n  /** @name Interface expected by KDTreeSingleIndexAdaptor\n   * @{ */\n\n  const self_t &derived() const { return *this; }\n  self_t &derived() { return *this; }\n\n  // Must return the number of data points\n  inline size_t kdtree_get_point_count() const {\n    return m_data_matrix.get().rows();\n  }\n\n  // Returns the dim'th component of the idx'th point in the class:\n  inline num_t kdtree_get_pt(const IndexType idx, size_t dim) const {\n    return m_data_matrix.get().coeff(idx, IndexType(dim));\n  }\n\n  // Optional bounding-box computation: return false to default to a standard\n  // bbox computation loop.\n  //   Return true if the BBOX was already computed by the class and returned in\n  //   \"bb\" so it can be avoided to redo it again. Look at bb.size() to find out\n  //   the expected dimensionality (e.g. 2 or 3 for point clouds)\n  template <class BBOX> bool kdtree_get_bbox(BBOX & /*bb*/) const {\n    return false;\n  }\n\n  /** @} */\n\n}; // end of KDTreeEigenMatrixAdaptor\n   /** @} */\n\n/** @} */ // end of grouping\n} // namespace nanoflann\n\n#endif /* NANOFLANN_HPP_ */\n"
  },
  {
    "path": "include/scan_context/tictoc.h",
    "content": "// Author:   Tong Qin               qintonguav@gmail.com\n// \t         Shaozu Cao \t\t    saozu.cao@connect.ust.hk\n\n#pragma once\n\n#include <ctime>\n#include <iostream>\n#include <string>\n#include <cstdlib>\n#include <chrono>\n\nclass TicToc\n{\npublic:\n    TicToc()\n    {\n        tic();\n    }\n\n    TicToc( bool _disp )\n    {\n        disp_ = _disp;\n        tic();\n    }\n\n    void tic()\n    {\n        start = std::chrono::system_clock::now();\n    }\n\n    void toc( std::string _about_task )\n    {\n        end = std::chrono::system_clock::now();\n        std::chrono::duration<double> elapsed_seconds = end - start;\n        double elapsed_ms = elapsed_seconds.count() * 1000;\n\n        if( disp_ )\n        {\n          std::cout.precision(3); // 10 for sec, 3 for ms \n          std::cout << _about_task << \": \" << elapsed_ms << \" msec.\" << std::endl;\n        }\n    }\n\nprivate:  \n    std::chrono::time_point<std::chrono::system_clock> start, end;\n    bool disp_ = false;\n};\n"
  },
  {
    "path": "include/utility_radar.h",
    "content": "#pragma once\n#ifndef _UTILITY_RADAR_ODOMETRY_H_\n#define _UTILITY_RADAR_ODOMETRY_H_\n\n#include <ros/ros.h>\n\n#include <std_msgs/Header.h>\n#include <std_msgs/Float64MultiArray.h>\n#include <sensor_msgs/Imu.h>\n#include <sensor_msgs/PointCloud2.h>\n#include <sensor_msgs/NavSatFix.h>\n#include <nav_msgs/Odometry.h>\n#include <nav_msgs/Path.h>\n#include <visualization_msgs/Marker.h>\n#include <visualization_msgs/MarkerArray.h>\n\n// #include <opencv/cv.h>\n// #include <opencv2/imgproc.hpp>\n\n#include <pcl/point_cloud.h>\n#include <pcl/point_types.h>\n#include <pcl/search/impl/search.hpp>\n#include <pcl/range_image/range_image.h>\n#include <pcl/kdtree/kdtree_flann.h>\n#include <pcl/common/common.h>\n#include <pcl/common/transforms.h>\n#include <pcl/registration/icp.h>\n#include <pcl/io/pcd_io.h>\n#include <pcl/filters/filter.h>\n#include <pcl/filters/voxel_grid.h>\n#include <pcl/filters/crop_box.h> \n#include <pcl_conversions/pcl_conversions.h>\n\n#include <tf/LinearMath/Quaternion.h>\n#include <tf/transform_listener.h>\n#include <tf/transform_datatypes.h>\n#include <tf/transform_broadcaster.h>\n \n#include <vector>\n#include <cmath>\n#include <algorithm>\n#include <queue>\n#include <deque>\n#include <iostream>\n#include <fstream>\n#include <ctime>\n#include <cfloat>\n#include <iterator>\n#include <sstream>\n#include <string>\n#include <limits>\n#include <iomanip>\n#include <array>\n#include <thread>\n#include <mutex>\n\nusing namespace std;\n\ntypedef pcl::PointXYZI PointType;\n\nclass ParamServer\n{\npublic:\n\n    ros::NodeHandle nh;\n\n    std::string robot_id;\n\n    //Topics\n    string pointCloudTopic;\n    string imuTopic;\n    string odomTopic;\n    string gpsTopic;\n\n    //Frames\n    string lidarFrame;\n    string baselinkFrame;\n    string odometryFrame;\n    string mapFrame;\n\n    // GPS Settings\n    bool useGpsElevation;\n    float gpsCovThreshold;\n    float poseCovThreshold;\n\n    // Save pcd\n    bool savePCD;\n    string savePCDDirectory;\n\n    // Sensor Configuration:\n    int downsampleRate;\n    float lidarMinRange;\n    float lidarMaxRange;\n\n    // IMU\n    float imuAccNoise;\n    float imuGyrNoise;\n    float imuAccBiasN;\n    float imuGyrBiasN;\n    float imuGravity;\n    float imuRPYWeight;\n    vector<double> extRotV;\n    vector<double> extRPYV;\n    vector<double> extTransV;\n    Eigen::Matrix3d extRot;\n    Eigen::Matrix3d extRPY;\n    Eigen::Vector3d extTrans;\n    Eigen::Quaterniond extQRPY;\n\n    // voxel filter paprams\n    float odometrySurfLeafSize;\n    float mappingCornerLeafSize;\n    float mappingSurfLeafSize ;\n\n    // CPU Params\n    int numberOfCores;\n    double mappingProcessInterval;\n\n    // Surrounding map\n    float surroundingkeyframeAddingDistThreshold; \n    float surroundingkeyframeAddingAngleThreshold; \n    float surroundingKeyframeDensity;\n    float surroundingKeyframeSearchRadius;\n    \n    // Loop closure\n    bool  loopClosureEnableFlag;\n    float loopClosureFrequency;\n    int   surroundingKeyframeSize;\n    float historyKeyframeSearchRadius;\n    float historyKeyframeSearchTimeDiff;\n    int   historyKeyframeSearchNum;\n    float historyKeyframeFitnessScore;\n\n    // global map visualization radius\n    float globalMapVisualizationSearchRadius;\n    float globalMapVisualizationPoseDensity;\n    float globalMapVisualizationLeafSize;\n\n    ParamServer()\n    {\n        nh.param<std::string>(\"/robot_id\", robot_id, \"nuc_handcart\");\n\n        nh.param<std::string>(\"radar_slam/pointCloudTopic\", pointCloudTopic, \"/radar_enhanced_pcl\");\n        nh.param<std::string>(\"radar_slam/imuTopic\", imuTopic, \"/vectornav/imu\");\n        nh.param<std::string>(\"radar_slam/odomTopic\", odomTopic, \"/odom\");\n        nh.param<std::string>(\"radar_slam/gpsTopic\", gpsTopic, \"/ublox/fix\");\n\n        nh.param<std::string>(\"radar_slam/lidarFrame\", lidarFrame, \"livox\");\n        nh.param<std::string>(\"radar_slam/baselinkFrame\", baselinkFrame, \"base_link\");\n        nh.param<std::string>(\"radar_slam/odometryFrame\", odometryFrame, \"odom_imu\"); \n        nh.param<std::string>(\"radar_slam/mapFrame\", mapFrame, \"map\");\n\n        nh.param<bool>(\"radar_slam/useGpsElevation\", useGpsElevation, false);\n        nh.param<float>(\"radar_slam/gpsCovThreshold\", gpsCovThreshold, 2.0);\n        nh.param<float>(\"radar_slam/poseCovThreshold\", poseCovThreshold, 25.0);\n\n        nh.param<bool>(\"radar_slam/savePCD\", savePCD, false);\n        nh.param<std::string>(\"radar_slam/savePCDDirectory\", savePCDDirectory, \"/Downloads/LOAM/\");\n\n        nh.param<int>(\"radar_slam/downsampleRate\", downsampleRate, 1);\n        nh.param<float>(\"radar_slam/lidarMinRange\", lidarMinRange, 1.0);\n        nh.param<float>(\"radar_slam/lidarMaxRange\", lidarMaxRange, 1000.0);\n\n        nh.param<float>(\"radar_slam/imuAccNoise\", imuAccNoise, 0.01);\n        nh.param<float>(\"radar_slam/imuGyrNoise\", imuGyrNoise, 0.001);\n        nh.param<float>(\"radar_slam/imuAccBiasN\", imuAccBiasN, 0.0002);\n        nh.param<float>(\"radar_slam/imuGyrBiasN\", imuGyrBiasN, 0.00003);\n        nh.param<float>(\"radar_slam/imuGravity\", imuGravity, 9.80511);\n        nh.param<float>(\"radar_slam/imuRPYWeight\", imuRPYWeight, 0.01);\n        nh.param<vector<double>>(\"radar_slam/extrinsicRot\", extRotV, vector<double>());\n        nh.param<vector<double>>(\"radar_slam/extrinsicRPY\", extRPYV, vector<double>());\n        nh.param<vector<double>>(\"radar_slam/extrinsicTrans\", extTransV, vector<double>());\n        extRot = Eigen::Map<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>>(extRotV.data(), 3, 3);\n        extRPY = Eigen::Map<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>>(extRPYV.data(), 3, 3);\n        extTrans = Eigen::Map<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>>(extTransV.data(), 3, 1);\n        extQRPY = Eigen::Quaterniond(extRPY);\n\n        nh.param<float>(\"radar_slam/odometrySurfLeafSize\", odometrySurfLeafSize, 0.2);\n        nh.param<float>(\"radar_slam/mappingCornerLeafSize\", mappingCornerLeafSize, 0.2);\n        nh.param<float>(\"radar_slam/mappingSurfLeafSize\", mappingSurfLeafSize, 0.2);\n\n        nh.param<int>(\"radar_slam/numberOfCores\", numberOfCores, 2);\n        nh.param<double>(\"radar_slam/mappingProcessInterval\", mappingProcessInterval, 0.15);\n\n        nh.param<float>(\"radar_slam/surroundingkeyframeAddingDistThreshold\", surroundingkeyframeAddingDistThreshold, 1.0);\n        nh.param<float>(\"radar_slam/surroundingkeyframeAddingAngleThreshold\", surroundingkeyframeAddingAngleThreshold, 0.2);\n        nh.param<float>(\"radar_slam/surroundingKeyframeDensity\", surroundingKeyframeDensity, 1.0);\n        nh.param<float>(\"radar_slam/surroundingKeyframeSearchRadius\", surroundingKeyframeSearchRadius, 50.0);\n\n        nh.param<bool>(\"radar_slam/loopClosureEnableFlag\", loopClosureEnableFlag, false);\n        nh.param<float>(\"radar_slam/loopClosureFrequency\", loopClosureFrequency, 1.0);\n        nh.param<int>(\"radar_slam/surroundingKeyframeSize\", surroundingKeyframeSize, 50);\n        nh.param<float>(\"radar_slam/historyKeyframeSearchRadius\", historyKeyframeSearchRadius, 10.0);\n        nh.param<float>(\"radar_slam/historyKeyframeSearchTimeDiff\", historyKeyframeSearchTimeDiff, 30.0);\n        nh.param<int>(\"radar_slam/historyKeyframeSearchNum\", historyKeyframeSearchNum, 25);\n        nh.param<float>(\"radar_slam/historyKeyframeFitnessScore\", historyKeyframeFitnessScore, 0.3);\n\n        nh.param<float>(\"radar_slam/globalMapVisualizationSearchRadius\", globalMapVisualizationSearchRadius, 1e3);\n        nh.param<float>(\"radar_slam/globalMapVisualizationPoseDensity\", globalMapVisualizationPoseDensity, 10.0);\n        nh.param<float>(\"radar_slam/globalMapVisualizationLeafSize\", globalMapVisualizationLeafSize, 1.0);\n\n        usleep(100);\n    }\n\n    sensor_msgs::Imu imuConverter(const sensor_msgs::Imu& imu_in)\n    {\n        sensor_msgs::Imu imu_out = imu_in;\n        // rotate acceleration\n        Eigen::Vector3d acc(imu_in.linear_acceleration.x, imu_in.linear_acceleration.y, imu_in.linear_acceleration.z);\n        acc = extRot * acc;\n        imu_out.linear_acceleration.x = acc.x();\n        imu_out.linear_acceleration.y = acc.y();\n        imu_out.linear_acceleration.z = acc.z();\n        // rotate gyroscope\n        Eigen::Vector3d gyr(imu_in.angular_velocity.x, imu_in.angular_velocity.y, imu_in.angular_velocity.z);\n        gyr = extRot * gyr;\n        imu_out.angular_velocity.x = gyr.x();\n        imu_out.angular_velocity.y = gyr.y();\n        imu_out.angular_velocity.z = gyr.z();\n        // rotate roll pitch yaw\n        Eigen::Quaterniond q_from(imu_in.orientation.w, imu_in.orientation.x, imu_in.orientation.y, imu_in.orientation.z);\n        Eigen::Quaterniond q_final = q_from * extQRPY;\n        imu_out.orientation.x = q_final.x();\n        imu_out.orientation.y = q_final.y();\n        imu_out.orientation.z = q_final.z();\n        imu_out.orientation.w = q_final.w();\n\n        if (sqrt(q_final.x()*q_final.x() + q_final.y()*q_final.y() + q_final.z()*q_final.z() + q_final.w()*q_final.w()) < 0.1)\n        {\n            ROS_ERROR(\"Invalid quaternion, please use a 9-axis IMU!\");\n            ros::shutdown();\n        }\n\n        return imu_out;\n    }\n\n};\n\n\n\n\n\nsensor_msgs::PointCloud2 publishCloud(ros::Publisher *thisPub, pcl::PointCloud<PointType>::Ptr thisCloud, ros::Time thisStamp, std::string thisFrame)\n{\n    sensor_msgs::PointCloud2 tempCloud;\n    pcl::toROSMsg(*thisCloud, tempCloud);\n    tempCloud.header.stamp = thisStamp;\n    tempCloud.header.frame_id = thisFrame;\n    if (thisPub->getNumSubscribers() != 0)\n        thisPub->publish(tempCloud);\n    return tempCloud;\n}\n\ntemplate<typename T>\ndouble ROS_TIME(T msg)\n{\n    return msg->header.stamp.toSec();\n}\n\n\ntemplate<typename T>\nvoid imuAngular2rosAngular(sensor_msgs::Imu *thisImuMsg, T *angular_x, T *angular_y, T *angular_z)\n{\n    *angular_x = thisImuMsg->angular_velocity.x;\n    *angular_y = thisImuMsg->angular_velocity.y;\n    *angular_z = thisImuMsg->angular_velocity.z;\n}\n\n\ntemplate<typename T>\nvoid imuAccel2rosAccel(sensor_msgs::Imu *thisImuMsg, T *acc_x, T *acc_y, T *acc_z)\n{\n    *acc_x = thisImuMsg->linear_acceleration.x;\n    *acc_y = thisImuMsg->linear_acceleration.y;\n    *acc_z = thisImuMsg->linear_acceleration.z;\n}\n\n\ntemplate<typename T>\nvoid imuRPY2rosRPY(sensor_msgs::Imu *thisImuMsg, T *rosRoll, T *rosPitch, T *rosYaw)\n{\n    double imuRoll, imuPitch, imuYaw;\n    tf::Quaternion orientation;\n    tf::quaternionMsgToTF(thisImuMsg->orientation, orientation);\n    tf::Matrix3x3(orientation).getRPY(imuRoll, imuPitch, imuYaw);\n\n    *rosRoll = imuRoll;\n    *rosPitch = imuPitch;\n    *rosYaw = imuYaw;\n}\n\n\nfloat pointDistance(PointType p)\n{\n    return sqrt(p.x*p.x + p.y*p.y + p.z*p.z);\n}\n\n\nfloat pointDistance(PointType p1, PointType p2)\n{\n    return sqrt((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y) + (p1.z-p2.z)*(p1.z-p2.z));\n}\n\n#endif"
  },
  {
    "path": "launch/radar_graph_slam.launch",
    "content": "<?xml version=\"1.0\"?>\n<launch>\n  <!-- Parameters -->\n  <rosparam file=\"$(find radar_graph_slam)/config/params.yaml\" command=\"load\" />\n  <param name=\"use_sim_time\" value=\"true\"/>\n  \n  <!-- arguments -->\n  <arg name=\"nodelet_manager\" default=\"radarslam_nodelet_manager\" />\n  <arg name=\"enable_floor_detection\" default=\"false\" />\n  <arg name=\"enable_barometer\" default=\"false\" /> <!-- barometer altitude constraint, not used -->\n  <arg name=\"barometer_edge_type\" default=\"1\" /> <!-- not used -->\n  <arg name=\"enable_gps\" default=\"false\" />\n  <arg name=\"enable_dynamic_object_removal\" default=\"false\" />\n  <arg name=\"enable_frontend_ego_vel\" default=\"false\" />\n  <arg name=\"enable_preintegration\" default=\"false\" /> <!-- not used -->\n\n  <arg name=\"keyframe_delta_trans_front_end\" default=\"0.5\" />\n  <arg name=\"keyframe_delta_trans_back_end\" default=\"2\" /><!-- 1 2 -->\n  <arg name=\"keyframe_delta_angle\" default=\"0.2612\" /><!-- 10°: 0.1745 15°: 0.2612 -->\n\n  <arg name=\"enable_transform_thresholding\" default=\"true\" />\n  <arg name=\"enable_loop_closure\" default=\"false\" />\n  \n  <!-- ICP NDT_OMP FAST_GICP FAST_APDGICP FAST_VGICP   -->\n  <arg name=\"registration_method\" default=\"FAST_APDGICP\" />\n  <arg name=\"reg_resolution\" default=\"1.0\" />\n  \n  <arg name=\"dist_var\" default=\"0.86\" />\n  <arg name=\"azimuth_var\" default=\"0.5\" />\n  <arg name=\"elevation_var\" default=\"1.0\" /><!-- 1.0 -->\n\n  <!-- optional arguments -->\n  <arg name=\"enable_robot_odometry_init_guess\" default=\"false\" /> <!-- not used -->\n\n  <!-- transformation between lidar and base_link -->\n  <!-- <node pkg=\"tf\" type=\"static_transform_publisher\" name=\"lidar2base_publisher\" args=\"0 0 0 0 0 0 base_link velodyne 10\" /> -->\n  \n  <node pkg=\"nodelet\" type=\"nodelet\" name=\"$(arg nodelet_manager)\" args=\"manager\" output=\"screen\"/>\n\n  <!-- radar_preprocessing_nodelet -->\n  <node pkg=\"nodelet\" type=\"nodelet\" name=\"radar_preprocessing_nodelet\" args=\"load radar_graph_slam/PreprocessingNodelet $(arg nodelet_manager)\">\n    <!-- distance filter -->\n    <param name=\"use_distance_filter\" value=\"true\" />\n    <param name=\"distance_near_thresh\" value=\"2\" />\n    <param name=\"distance_far_thresh\" value=\"100.0\" />\n    <param name=\"z_low_thresh\" value=\"-100.0\" />\n    <param name=\"z_high_thresh\" value=\"100.0\" />\n    <!-- NONE, VOXELGRID(0.1), or APPROX_VOXELGRID -->\n    <param name=\"downsample_method\" value=\"VOXELGRID\" />\n    <param name=\"downsample_resolution\" value=\"0.1\" />\n    <!-- NONE, RADIUS 2(initial 0.5 2), STATISTICAL, or BILATERAL -->\n    <param name=\"outlier_removal_method\" value=\"RADIUS\" />\n    <param name=\"statistical_mean_k\" value=\"30\" />\n    <param name=\"statistical_stddev\" value=\"1.2\" />\n    <param name=\"radius_radius\" value=\"0.5\" />\n    <param name=\"radius_min_neighbors\" value=\"1\" />\n    <param name=\"bilateral_sigma_s\" value=\"5\" />\n    <param name=\"bilateral_sigma_r\" value=\"0.03\" />\n    <!-- Power Filterring -->\n    <param name=\"power_threshold\" value=\"0.0\" />\n    <!-- Ego Velocity Estimation -->\n    <param name=\"enable_dynamic_object_removal\" value=\"$(arg enable_dynamic_object_removal)\" />\n    <!-- ground truth publication -->\n    <param name=\"gt_file_location\" value=\"/home/zhuge/datasets/Radar/gt_carpark1.txt\" />\n  </node>\n\n\n  <!-- scan_matching_odometry_nodelet -->\n  <node pkg=\"nodelet\" type=\"nodelet\" name=\"scan_matching_odometry_nodelet\" args=\"load radar_graph_slam/ScanMatchingOdometryNodelet $(arg nodelet_manager)\">\n    <param name=\"keyframe_delta_trans\" value=\"$(arg keyframe_delta_trans_front_end)\" />\n    <param name=\"keyframe_delta_angle\" value=\"$(arg keyframe_delta_angle)\" /> \n    <param name=\"keyframe_min_size\" value=\"100\" />\n    <param name=\"enable_robot_odometry_init_guess\" value=\"$(arg enable_robot_odometry_init_guess)\" />\n    <param name=\"enable_transform_thresholding\" value=\"$(arg enable_transform_thresholding)\" />\n    <param name=\"enable_imu_thresholding\" value=\"false\" /> <!-- bad effect, not used -->\n    <param name=\"max_acceptable_trans\" value=\"1.0\" />\n    <param name=\"max_acceptable_angle\" value=\"3\" /><!-- degree -->\n    <param name=\"max_diff_trans\" value=\"0.3\" />\n    <param name=\"max_diff_angle\" value=\"0.8\" />\n    <param name=\"max_egovel_cum\" value=\"1\" /><!-- 12m/s -->\n    <param name=\"downsample_method\" value=\"NONE\" />\n    <param name=\"downsample_resolution\" value=\"0.1\" />\n    <!-- ICP, GICP, NDT, GICP_OMP, NDT_OMP, FAST_GICP(recommended), or FAST_VGICP -->\n    <param name=\"registration_method\" value=\"$(arg registration_method)\" />\n    <param name=\"dist_var\" value=\"$(arg dist_var)\" /><!--  -->\n    <param name=\"azimuth_var\" value=\"$(arg azimuth_var)\" /><!--  -->\n    <param name=\"elevation_var\" value=\"$(arg elevation_var)\" /><!--  -->\n    <param name=\"reg_num_threads\" value=\"0\" />\n    <param name=\"reg_transformation_epsilon\" value=\"0.1\"/>\n    <param name=\"reg_maximum_iterations\" value=\"64\"/>\n    <param name=\"reg_max_correspondence_distance\" value=\"2.0\"/>\n    <param name=\"reg_max_optimizer_iterations\" value=\"20\"/>\n    <param name=\"reg_use_reciprocal_correspondences\" value=\"false\"/>\n    <param name=\"reg_correspondence_randomness\" value=\"20\"/>\n    <param name=\"reg_resolution\" value=\"$(arg reg_resolution)\" />\n    <param name=\"reg_nn_search_method\" value=\"DIRECT7\" />\n    <param name=\"use_ego_vel\" value=\"$(arg enable_frontend_ego_vel)\"/>\n    <param name=\"max_submap_frames\" value=\"5\"/>\n    <param name=\"enable_scan_to_map\" value=\"false\"/>\n    <!-- IMU --> <!-- bad effect, not used -->\n    <param name=\"enable_imu_fusion\" value=\"false\" />\n    <param name=\"imu_debug_out\" value=\"true\" />\n    <param name=\"imu_fusion_ratio\" value=\"0.1\" />\n  </node>\n\n\n  <!-- radar_graph_slam_nodelet -->\n  <node pkg=\"nodelet\" type=\"nodelet\" name=\"radar_graph_slam_nodelet\" args=\"load radar_graph_slam/RadarGraphSlamNodelet $(arg nodelet_manager)\" output=\"screen\">\n    <!-- optimization params -->\n    <!-- typical solvers: gn_var, gn_fix6_3, gn_var_cholmod, lm_var, lm_fix6_3, lm_var_cholmod, ... -->\n    <param name=\"g2o_solver_type\" value=\"lm_var_cholmod\" />\n    <param name=\"g2o_solver_num_iterations\" value=\"512\" />\n    <!-- constraint switches -->\n    <param name=\"enable_barometer\" value=\"$(arg enable_barometer)\" /> <!-- not used -->\n    <param name=\"enable_gps\" value=\"$(arg enable_gps)\" />\n    <!-- keyframe registration params --> \n    <param name=\"max_keyframes_per_update\" value=\"30\" />\n    <param name=\"keyframe_delta_trans\" value=\"$(arg keyframe_delta_trans_back_end)\" />\n    <param name=\"keyframe_delta_angle\" value=\"$(arg keyframe_delta_angle)\" />\n    <param name=\"keyframe_min_size\" value=\"500\" />\n    <!-- fix first node for optimization stability -->\n    <param name=\"fix_first_node\" value=\"true\"/>\n    <param name=\"fix_first_node_stddev\" value=\"10 10 10 1 1 1\"/>\n    <param name=\"fix_first_node_adaptive\" value=\"true\"/>\n    <!-- Scan Context Loop Closure params 15 25 15 2.5 -->\n    <param name=\"enable_loop_closure\" value=\"$(arg enable_loop_closure)\"/>\n    <param name=\"enable_pf\" value=\"true\"/> <!-- loop prefiltering -->\n    <param name=\"enable_odom_check\" value=\"true\"/>\n    <param name=\"distance_thresh\" value=\"10.0\" />\n    <param name=\"accum_distance_thresh\" value=\"50.0\" /><!-- Minimum distance beteen two edges of the loop -->\n    <param name=\"min_loop_interval_dist\" value=\"10.0\" /><!-- Minimum distance between a new loop edge and the last one -->\n    <param name=\"distance_from_last_edge_thresh\" value=\"10\" />\n    <param name=\"max_baro_difference\" value=\"2.0\" /><!-- Maximum altitude difference beteen two edges' odometry -->\n    <param name=\"max_yaw_difference\" value=\"20\" /><!-- Maximum yaw difference beteen two edges' odometry -->\n    \n    <param name=\"sc_dist_thresh\" value=\"0.5\" /><!-- Matching score threshold of Scan Context 0.4-0.6 will be good -->\n    <param name=\"sc_azimuth_range\" value=\"56.5\" />\n    <param name=\"historyKeyframeFitnessScore\" value=\"6\" />\n    <param name=\"odom_check_trans_thresh\" value=\"0.3\" />\n    <param name=\"odom_check_rot_thresh\" value=\"0.05\" />\n    <param name=\"pairwise_check_trans_thresh\" value=\"1.5\" />\n    <param name=\"pairwise_check_rot_thresh\" value=\"0.2\" />\n    <!-- scan matching params -->\n    <param name=\"registration_method\" value=\"$(arg registration_method)\" />\n    <param name=\"reg_num_threads\" value=\"0\" />\n    <param name=\"reg_transformation_epsilon\" value=\"0.1\"/>\n    <param name=\"reg_maximum_iterations\" value=\"64\"/>\n    <param name=\"reg_max_correspondence_distance\" value=\"2.0\"/>\n    <param name=\"reg_max_optimizer_iterations\" value=\"20\"/>\n    <param name=\"reg_use_reciprocal_correspondences\" value=\"false\"/>\n    <param name=\"reg_correspondence_randomness\" value=\"20\"/>\n    <param name=\"reg_resolution\" value=\"$(arg reg_resolution)\" />\n    <param name=\"reg_nn_search_method\" value=\"DIRECT7\" />\n    <!-- edge params -->\n    <!-- Barometer not used -->\n    <param name=\"barometer_edge_type\" value=\"$(arg barometer_edge_type)\" />\n    <param name=\"barometer_edge_robust_kernel\" value=\"Huber\" />\n    <param name=\"barometer_edge_robust_kernel_size\" value=\"1.0\" />\n    <param name=\"barometer_edge_stddev\" value=\"0.47\" /> <!-- 0.47 -->\n    <!-- GPS -->\n    <param name=\"gps_edge_robust_kernel\" value=\"Huber\" />\n    <param name=\"gps_edge_robust_kernel_size\" value=\"1.0\" />\n    <param name=\"gps_edge_stddev_xy\" value=\"5.0\" />\n    <param name=\"gps_edge_stddev_z\" value=\"5.0\" />\n    <param name=\"max_gps_edge_stddev_xy\" value=\"1.5\" />\n    <param name=\"max_gps_edge_stddev_z\" value=\"3.0\" />\n    <param name=\"gps_edge_intervals\" value=\"15\" />\n    <param name=\"dataset_name\" value=\"loop2\" />\n    <!-- Preintegration --> <!-- bad effect, not used -->\n    <param name=\"enable_preintegration\" value=\"$(arg enable_preintegration)\" />\n      <param name=\"use_egovel_preinteg_trans\" value=\"false\" /><!-- Ego Velocity Integration - Translation -->\n      <param name=\"preinteg_orient_stddev\" value=\"1\" />\n      <param name=\"preinteg_trans_stddev\" value=\"5\" />\n    <!-- scan matching -->\n    <!-- robust kernels: NONE, Cauchy, DCS, Fair, GemanMcClure, Huber, PseudoHuber, Saturated, Tukey, Welsch -->\n    <param name=\"odometry_edge_robust_kernel\" value=\"NONE\" />\n    <param name=\"odometry_edge_robust_kernel_size\" value=\"1.0\" />\n    <param name=\"loop_closure_edge_robust_kernel\" value=\"Huber\" />\n    <param name=\"loop_closure_edge_robust_kernel_size\" value=\"1.0\" />\n    <param name=\"use_const_inf_matrix\" value=\"false\" />\n    <param name=\"const_stddev_x\" value=\"0.5\" />\n    <param name=\"const_stddev_q\" value=\"0.1\" />\n    <param name=\"var_gain_a\" value=\"20.0\" />\n    <param name=\"min_stddev_x\" value=\"0.1\" />\n    <param name=\"max_stddev_x\" value=\"5.0\" />\n    <param name=\"min_stddev_q\" value=\"0.05\" />\n    <param name=\"max_stddev_q\" value=\"0.2\" />\n    <!-- update params -->\n    <param name=\"graph_update_interval\" value=\"2.0\" />\n    <param name=\"map_cloud_update_interval\" value=\"6.0\" />\n    <param name=\"map_cloud_resolution\" value=\"0.05\" />\n    <!-- marker params -->\n    <param name=\"show_sphere\" value=\"false\" />\n  </node>\n\n  <node pkg=\"rviz\" type=\"rviz\" name=\"rviz_slam\" args=\"-d $(find radar_graph_slam)/rviz/radar_graph_slam.rviz\" respawn=\"true\"/>\n\n  <!--- Rosbag Play -->\n  <include file=\"$(find radar_graph_slam)/launch/rosbag_play_radar_carpark1.launch\" />\n  <!-- <include file=\"$(find radar_graph_slam)/launch/rosbag_play_radar_carpark3.launch\" /> -->\n  <!-- <include file=\"$(find radar_graph_slam)/launch/rosbag_play_radar_garden.launch\" /> -->\n  <!-- <include file=\"$(find radar_graph_slam)/launch/rosbag_play_radar_nanyanglink.launch\" /> -->\n  <!-- <include file=\"$(find radar_graph_slam)/launch/rosbag_play_radar_ntuloop1.launch\" /> -->\n  <!-- <include file=\"$(find radar_graph_slam)/launch/rosbag_play_radar_ntuloop2.launch\" /> -->\n  <!-- <include file=\"$(find radar_graph_slam)/launch/rosbag_play_radar_ntuloop3.launch\" /> -->\n  <!-- <include file=\"$(find radar_graph_slam)/launch/rosbag_play_radar_smoke.launch\" /> -->\n\n</launch>\n"
  },
  {
    "path": "launch/rosbag_play_radar_carpark1.launch",
    "content": "<!-- This launch file loads rosbags and makes an octomap file -->\n\n<launch>\n\n<!-- <param name=\"/use_sim_time\" value=\"true\"/> -->\n\n<!-- paths to the rosbag files -->\n<arg name=\"path\" default=\"/home/zhuge/datasets/Radar/\"/>\n\n<arg name = \"file_0\" default = \"carpark_400/carpark1_2022-02-26.bag\"/>\n<arg name = \"file_1\" default = \"carpark_400/carpark1_2023-02-01.bag\"/>\n<arg name = \"file_2\" default = \"carpark_400/carpark8_normal_2023-01-14.bag\"/>\n<arg name = \"file_3\" default = \"carpark_400/carpark0_normal_2023-01-14.bag\"/>\n<arg name = \"file_4\" default = \"carpark_400/carpark0_hard_2023-01-14.bag\"/>\n<arg name = \"file_5\" default = \"carpark_400/carpark0_2023-01-27.bag\"/>\n<arg name = \"file_6\" default = \"carpark_400/carpark8_2023-01-27.bag\"/>\n\n<!-- Plays the dataset. WARNING: changing 'rate' will cause interactions with the demo.  -->\n<!--  /radar_pcl /radar_trk -->\n<node pkg=\"rosbag\" type=\"play\" name=\"player\"\n    args = \"-s 0.5 --clock --rate=3 --duration=10000\n    $(arg path)$(arg file_0)\n    --topic /radar_enhanced_pcl /rgb_cam/image_raw/compressed /barometer/filtered /vectornav/imu\n    \">\n</node>\n\n</launch>\n"
  },
  {
    "path": "launch/rosbag_play_radar_carpark3.launch",
    "content": "<!-- This launch file loads rosbags and makes an octomap file -->\n\n<launch>\n\n<!-- <param name=\"/use_sim_time\" value=\"true\"/> -->\n\n<!-- paths to the rosbag files -->\n<arg name=\"path\" default=\"/home/zhuge/datasets/Radar/\"/>\n\n<arg name = \"file_8\" default = \"carpark_400/carpark1_2022-02-26.bag\"/>\n<arg name = \"file_9\" default = \"carpark_400/carpark3_2022-02-26_0.bag\"/>\n<arg name = \"file_10\" default = \"carpark_400/carpark3_2022-02-26_1.bag\"/>\n\n<!-- Plays the dataset. WARNING: changing 'rate' will cause interactions with the demo.  -->\n<!--  /radar_pcl /radar_trk -->\n<node pkg=\"rosbag\" type=\"play\" name=\"player\" \n    args = \"--clock --rate=3 --duration=10000\n    $(arg path)$(arg file_9)\n    $(arg path)$(arg file_10)\n    --topic /radar_enhanced_pcl /rgb_cam/image_raw/compressed /barometer/filtered /vectornav/imu\n    \">\n</node>\n\n</launch>\n"
  },
  {
    "path": "launch/rosbag_play_radar_garden.launch",
    "content": "<!-- This launch file loads rosbags and makes an octomap file -->\n\n<launch>\n\n<!-- <param name=\"/use_sim_time\" value=\"true\"/> -->\n\n<!-- paths to the rosbag files -->\n<arg name=\"path\" default=\"/home/zhuge/datasets/Radar/\"/>\n\n<arg name = \"file_1\" default = \"Yunnan_garden/garden_2022-02-03_0.bag\"/>\n<arg name = \"file_2\" default = \"Yunnan_garden/garden_2022-02-03_1.bag\"/>\n<arg name = \"file_3\" default = \"Yunnan_garden/garden_2022-02-03_2.bag\"/>\n\n<arg name = \"file_4\" default = \"yunnangarden_day/5-13/yunnangarden_2022-05-13_0.bag\"/>\n<arg name = \"file_5\" default = \"yunnangarden_day/5-13/yunnangarden_2022-05-13_1.bag\"/>\n\n<arg name = \"file_6\" default = \"yunnangarden_day/5-14/yunnangarden_2022-05-14_0.bag\"/>\n<arg name = \"file_7\" default = \"yunnangarden_day/5-14/yunnangarden_2022-05-14_1.bag\"/>\n\n<!-- Plays the dataset. WARNING: changing 'rate' will cause interactions with the demo.  -->\n<!--  /radar_pcl /radar_trk -->\n<node pkg=\"rosbag\" type=\"play\" name=\"player\"\n    args = \"--clock --rate=2 --duration=10000\n    $(arg path)$(arg file_4)\n    $(arg path)$(arg file_5)\n    --topic /radar_enhanced_pcl /rgb_cam/image_raw/compressed /barometer/filtered /vectornav/imu\n    \">\n</node>\n\n</launch>\n"
  },
  {
    "path": "launch/rosbag_play_radar_nanyanglink.launch",
    "content": "<!-- This launch file loads rosbags and makes an octomap file -->\n\n<launch>\n\n<!-- <param name=\"/use_sim_time\" value=\"true\"/> -->\n\n<!-- paths to the rosbag files -->\n<arg name=\"path\" default=\"/home/zhuge/datasets/Radar/\"/>\n\n<arg name = \"file_0\" default = \"nanyang_link/nanyanglink_2022-02-03.bag\"/>\n\n<arg name = \"file_1\" default = \"nanyang_link/nanyanglink_2022-02-26_0.bag\"/>\n<arg name = \"file_2\" default = \"nanyang_link/nanyanglink_2022-02-26_1.bag\"/>\n\n<!-- Plays the dataset. WARNING: changing 'rate' will cause interactions with the demo.  -->\n<!--  /radar_pcl /radar_trk -->\n<node pkg=\"rosbag\" type=\"play\" name=\"player\" \n    args = \"--clock --rate=2 --duration=10000\n    $(arg path)$(arg file_1)\n    $(arg path)$(arg file_2)\n    --topic /radar_enhanced_pcl /rgb_cam/image_raw/compressed /barometer/filtered /vectornav/imu\n    \">\n</node>\n\n</launch>\n"
  },
  {
    "path": "launch/rosbag_play_radar_ntuloop1.launch",
    "content": "<!-- This launch file loads rosbags and makes an octomap file -->\n\n<launch>\n\n<!-- <param name=\"/use_sim_time\" value=\"true\"/> -->\n\n<!-- paths to the rosbag files -->\n<arg name=\"path\" default=\"/home/zhuge/datasets/Radar/\"/>\n\n<arg name = \"file_0\" default = \"ntu_loop1_car/ntu-car-day-loop1_2022-06-03_0.bag\"/>\n<arg name = \"file_1\" default = \"ntu_loop1_car/ntu-car-day-loop1_2022-06-03_1.bag\"/>\n<arg name = \"file_2\" default = \"ntu_loop1_car/ntu-car-day-loop1_2022-06-03_2.bag\"/>\n<arg name = \"file_3\" default = \"ntu_loop1_car/ntu-car-day-loop1_2022-06-03_3.bag\"/>\n<arg name = \"file_4\" default = \"ntu_loop1_car/ntu-car-day-loop1_2022-06-03_4.bag\"/>\n\n<!-- Plays the dataset. WARNING: changing 'rate' will cause interactions with the demo.  -->\n<!--  /radar_pcl /radar_trk -->\n<node pkg=\"rosbag\" type=\"play\" name=\"player\" \n    args = \"--clock --rate=3 --duration=10000 --pause\n    $(arg path)$(arg file_0)\n    $(arg path)$(arg file_1)\n    $(arg path)$(arg file_2)\n    $(arg path)$(arg file_3)\n    $(arg path)$(arg file_4)\n    --topic /radar_enhanced_pcl /rgb_cam/image_raw/compressed /barometer/filtered /vectornav/imu\n    \n    \">\n</node>\n\n</launch>\n"
  },
  {
    "path": "launch/rosbag_play_radar_ntuloop2.launch",
    "content": "<!-- This launch file loads rosbags and makes an octomap file -->\n\n<launch>\n\n<!-- <param name=\"/use_sim_time\" value=\"true\"/> -->\n\n<!-- paths to the rosbag files -->\n<arg name=\"path\" default=\"/home/zhuge/datasets/Radar/\"/>\n\n<arg name = \"file_0\" default = \"ntu_loop2_car/ntu-car-day-loop2_2022-06-03_0.bag\"/>\n<arg name = \"file_1\" default = \"ntu_loop2_car/ntu-car-day-loop2_2022-06-03_1.bag\"/>\n<arg name = \"file_2\" default = \"ntu_loop2_car/ntu-car-day-loop2_2022-06-03_2.bag\"/>\n<arg name = \"file_3\" default = \"ntu_loop2_car/ntu-car-day-loop2_2022-06-03_3.bag\"/>\n\n<!-- Plays the dataset. WARNING: changing 'rate' will cause interactions with the demo.  -->\n<!--  /radar_pcl /radar_trk /ublox/fix -->\n<node pkg=\"rosbag\" type=\"play\" name=\"player\"\n    args = \"--clock --rate=2 --duration=10000\n    $(arg path)$(arg file_0)\n    $(arg path)$(arg file_1)\n    $(arg path)$(arg file_2)\n    $(arg path)$(arg file_3)\n    --topic /radar_enhanced_pcl /rgb_cam/image_raw/compressed /barometer/filtered /vectornav/imu\n    \">\n</node>\n\n</launch>\n"
  },
  {
    "path": "launch/rosbag_play_radar_ntuloop3.launch",
    "content": "<!-- This launch file loads rosbags and makes an octomap file -->\n\n<launch>\n\n<!-- <param name=\"/use_sim_time\" value=\"true\"/> -->\n\n<!-- paths to the rosbag files -->\n<arg name=\"path\" default=\"/home/zhuge/datasets/Radar/\"/>\n\n<arg name = \"file_0\" default = \"ntu_loop3_car/ntu-car-day-loop3_2022-06-03_0.bag\"/>\n<arg name = \"file_1\" default = \"ntu_loop3_car/ntu-car-day-loop3_2022-06-03_1.bag\"/>\n<arg name = \"file_2\" default = \"ntu_loop3_car/ntu-car-day-loop3_2022-06-03_2.bag\"/>\n\n<!-- Plays the dataset. WARNING: changing 'rate' will cause interactions with the demo.  -->\n<!--  /radar_pcl /radar_trk pause     /ublox/fix-->\n<node pkg=\"rosbag\" type=\"play\" name=\"player\" \n    args = \"--clock --rate=2 --duration=10000\n    $(arg path)$(arg file_0)\n    $(arg path)$(arg file_1)\n    $(arg path)$(arg file_2)\n    --topic /radar_enhanced_pcl /rgb_cam/image_raw/compressed /barometer/filtered /vectornav/imu\n    \">\n</node>\n\n</launch>\n"
  },
  {
    "path": "launch/rosbag_play_radar_smoke.launch",
    "content": "<!-- This launch file loads rosbags and makes an octomap file -->\n\n<launch>\n\n<!-- <param name=\"/use_sim_time\" value=\"true\"/> -->\n\n<!-- paths to the rosbag files -->\n<arg name=\"path\" default=\"/home/zhuge/datasets/Radar/\"/>\n\n  <arg name = \"file_0\" default = \"carpark_smoke/smoke_2022-05-21_0.bag\"/>\n  <arg name = \"file_1\" default = \"carpark_smoke/smoke_2022-05-21_1.bag\"/>\n  <node pkg=\"rosbag\" type=\"play\" name=\"player\" \n    args = \"--clock --rate=3 --duration=10000\n    $(arg path)$(arg file_0) $(arg path)$(arg file_1)\n    --topic /radar_enhanced_pcl /rgb_cam/image_raw/compressed /barometer/filtered /vectornav/imu\n    \">\n  </node>\n\n</launch>\n"
  },
  {
    "path": "msg/FloorCoeffs.msg",
    "content": "Header header\n\nfloat32[] coeffs\n"
  },
  {
    "path": "msg/ScanMatchingStatus.msg",
    "content": "Header header\n\nbool has_converged\nfloat32 matching_error\nfloat32 inlier_fraction\ngeometry_msgs/Pose relative_pose\n\nstd_msgs/String[] prediction_labels\ngeometry_msgs/Pose[] prediction_errors"
  },
  {
    "path": "nodelet_plugins.xml",
    "content": "<library path=\"lib/libpreprocessing_nodelet\">\n  <class name=\"radar_graph_slam/PreprocessingNodelet\" type=\"radar_graph_slam::PreprocessingNodelet\" base_class_type=\"nodelet::Nodelet\" />\n</library>\n\n<library path=\"lib/libscan_matching_odometry_nodelet\">\n  <class name=\"radar_graph_slam/ScanMatchingOdometryNodelet\" type=\"radar_graph_slam::ScanMatchingOdometryNodelet\" base_class_type=\"nodelet::Nodelet\" />\n</library>\n\n<library path=\"lib/libradar_graph_slam_nodelet\">\n  <class name=\"radar_graph_slam/RadarGraphSlamNodelet\" type=\"radar_graph_slam::RadarGraphSlamNodelet\" base_class_type=\"nodelet::Nodelet\" />\n</library>\n"
  },
  {
    "path": "package.xml",
    "content": "<?xml version=\"1.0\"?>\n<package format=\"3\">\n  <name>radar_graph_slam</name>\n  <version>0.0.0</version>\n  <description>The radar_graph_slam package</description>\n\n  <maintainer email=\"koide@aisl.cs.tut.ac.jp\">koide</maintainer>\n\n  <license>BSD</license>\n\n  <buildtool_depend>catkin</buildtool_depend>\n  <build_depend>ndt_omp</build_depend>\n  <build_depend>fast_gicp</build_depend>\n  <build_depend>pcl_ros</build_depend>\n  <build_depend>roscpp</build_depend>\n  <build_depend>rospy</build_depend>\n  <build_depend>nodelet</build_depend>\n  <build_depend>geodesy</build_depend>\n  <build_depend>nmea_msgs</build_depend>\n  <build_depend>sensor_msgs</build_depend>\n  <build_depend>geometry_msgs</build_depend>\n  <build_depend>message_generation</build_depend>\n  <build_depend>libg2o</build_depend>\n  <!-- <build_depend>rosopencv</build_depend> -->\n  <build_depend>cv_bridge</build_depend>\n  <build_depend>image_transport</build_depend>\n  \n  <exec_depend>ndt_omp</exec_depend>\n  <exec_depend>fast_gicp</exec_depend>\n  <exec_depend>pcl_ros</exec_depend>\n  <exec_depend>roscpp</exec_depend>\n  <exec_depend>rospy</exec_depend>\n  <exec_depend>geodesy</exec_depend>\n  <exec_depend>nodelet</exec_depend>\n  <exec_depend>nmea_msgs</exec_depend>\n  <exec_depend>sensor_msgs</exec_depend>\n  <exec_depend>message_generation</exec_depend>\n  <!-- <exec_depend>rosopencv</exec_depend> -->\n  <exec_depend>cv_bridge</exec_depend>\n  <exec_depend>image_transport</exec_depend>\n\n  <exec_depend condition=\"$ROS_PYTHON_VERSION == 2\">python-scipy</exec_depend>\n  <exec_depend condition=\"$ROS_PYTHON_VERSION == 3\">python3-scipy</exec_depend>\n  <exec_depend condition=\"$ROS_PYTHON_VERSION == 2\">python-progressbar</exec_depend>\n  <exec_depend condition=\"$ROS_PYTHON_VERSION == 3\">python3-progressbar</exec_depend>\n\n  <export>\n    <nodelet plugin=\"${prefix}/nodelet_plugins.xml\" />\n  </export>\n</package>\n"
  },
  {
    "path": "rviz/radar_graph_slam.rviz",
    "content": "Panels:\n  - Class: rviz/Displays\n    Help Height: 0\n    Name: Displays\n    Property Tree Widget:\n      Expanded:\n        - /Global Options1\n        - /Status1\n        - /Grid1\n        - /TF1/Frames1\n        - /MarkerArray1/Namespaces1\n        - //odom1/Shape1\n        - /imu_odom1/Shape1\n        - /imu_odom_incre1/Shape1\n        - /frame2frame1/Shape1\n        - /ground_truth1/Shape1\n        - /orient_incre1/Shape1\n      Splitter Ratio: 0.5778490900993347\n    Tree Height: 539\n  - Class: rviz/Selection\n    Name: Selection\n  - Class: rviz/Tool Properties\n    Expanded:\n      - /2D Pose Estimate1\n      - /2D Nav Goal1\n      - /Publish Point1\n    Name: Tool Properties\n    Splitter Ratio: 0.5886790156364441\n  - Class: rviz/Views\n    Expanded:\n      - /Current View1\n    Name: Views\n    Splitter Ratio: 0.5\n  - Class: rviz/Time\n    Name: Time\n    SyncMode: 0\n    SyncSource: /filtered_points\nPreferences:\n  PromptSaveOnExit: true\nToolbars:\n  toolButtonStyle: 2\nVisualization Manager:\n  Class: \"\"\n  Displays:\n    - Alpha: 0.5\n      Cell Size: 5\n      Class: rviz/Grid\n      Color: 160; 160; 164\n      Enabled: true\n      Line Style:\n        Line Width: 0.029999999329447746\n        Value: Lines\n      Name: Grid\n      Normal Cell Count: 0\n      Offset:\n        X: 0\n        Y: 0\n        Z: 0\n      Plane: XY\n      Plane Cell Count: 50\n      Reference Frame: <Fixed Frame>\n      Value: true\n    - Class: rviz/TF\n      Enabled: true\n      Frame Timeout: 50\n      Frames:\n        All Enabled: false\n        base_link:\n          Value: true\n        map:\n          Value: false\n        odom:\n          Value: false\n        velodyne:\n          Value: false\n      Marker Alpha: 1\n      Marker Scale: 10\n      Name: TF\n      Show Arrows: false\n      Show Axes: true\n      Show Names: false\n      Tree:\n        map:\n          base_link:\n            velodyne:\n              {}\n          odom:\n            {}\n      Update Interval: 0\n      Value: true\n    - Alpha: 1\n      Autocompute Intensity Bounds: false\n      Autocompute Value Bounds:\n        Max Value: 10\n        Min Value: -10\n        Value: true\n      Axis: Z\n      Channel Name: intensity\n      Class: rviz/PointCloud2\n      Color: 255; 255; 255\n      Color Transformer: Intensity\n      Decay Time: 0\n      Enabled: false\n      Invert Rainbow: false\n      Max Color: 255; 255; 255\n      Max Intensity: 24.719999313354492\n      Min Color: 0; 0; 0\n      Min Intensity: 10.010000228881836\n      Name: oculli_points\n      Position Transformer: XYZ\n      Queue Size: 10\n      Selectable: true\n      Size (Pixels): 3\n      Size (m): 0.20000000298023224\n      Style: Points\n      Topic: /eagle_data/pc2_raw\n      Unreliable: false\n      Use Fixed Frame: true\n      Use rainbow: true\n      Value: false\n    - Alpha: 1\n      Autocompute Intensity Bounds: false\n      Autocompute Value Bounds:\n        Max Value: 10\n        Min Value: -10\n        Value: true\n      Axis: Z\n      Channel Name: z\n      Class: rviz/PointCloud2\n      Color: 255; 255; 255\n      Color Transformer: Intensity\n      Decay Time: 0\n      Enabled: true\n      Invert Rainbow: false\n      Max Color: 255; 255; 255\n      Max Intensity: 2\n      Min Color: 0; 0; 0\n      Min Intensity: -1\n      Name: map_points\n      Position Transformer: XYZ\n      Queue Size: 1\n      Selectable: true\n      Size (Pixels): 1.5\n      Size (m): 0.05000000074505806\n      Style: Points\n      Topic: /radar_graph_slam/map_points\n      Unreliable: false\n      Use Fixed Frame: true\n      Use rainbow: true\n      Value: true\n    - Class: rviz/MarkerArray\n      Enabled: true\n      Marker Topic: /radar_graph_slam/markers\n      Name: MarkerArray\n      Namespaces:\n        edges: true\n        nodes: true\n      Queue Size: 100\n      Value: true\n    - Angle Tolerance: 0.10000000149011612\n      Class: rviz/Odometry\n      Covariance:\n        Orientation:\n          Alpha: 0.5\n          Color: 255; 255; 127\n          Color Style: Unique\n          Frame: Local\n          Offset: 1\n          Scale: 1\n          Value: true\n        Position:\n          Alpha: 0.30000001192092896\n          Color: 204; 51; 204\n          Scale: 1\n          Value: true\n        Value: true\n      Enabled: false\n      Keep: 1\n      Name: /odom\n      Position Tolerance: 0.10000000149011612\n      Queue Size: 10\n      Shape:\n        Alpha: 1\n        Axes Length: 2\n        Axes Radius: 0.20000000298023224\n        Color: 255; 25; 0\n        Head Length: 0.30000001192092896\n        Head Radius: 0.10000000149011612\n        Shaft Length: 1\n        Shaft Radius: 0.05000000074505806\n        Value: Axes\n      Topic: /odom\n      Unreliable: false\n      Value: false\n    - Alpha: 1\n      Autocompute Intensity Bounds: true\n      Autocompute Value Bounds:\n        Max Value: 10\n        Min Value: -10\n        Value: true\n      Axis: Z\n      Channel Name: intensity\n      Class: rviz/PointCloud2\n      Color: 255; 255; 255\n      Color Transformer: FlatColor\n      Decay Time: 0\n      Enabled: true\n      Invert Rainbow: false\n      Max Color: 255; 255; 255\n      Min Color: 0; 0; 0\n      Name: /filtered_points\n      Position Transformer: XYZ\n      Queue Size: 10\n      Selectable: true\n      Size (Pixels): 4\n      Size (m): 0.10000000149011612\n      Style: Points\n      Topic: /filtered_points\n      Unreliable: false\n      Use Fixed Frame: true\n      Use rainbow: true\n      Value: true\n    - Alpha: 1\n      Autocompute Intensity Bounds: true\n      Autocompute Value Bounds:\n        Max Value: 264.5556640625\n        Min Value: 31.884599685668945\n        Value: true\n      Axis: X\n      Channel Name: doppler\n      Class: rviz/PointCloud2\n      Color: 255; 255; 255\n      Color Transformer: Intensity\n      Decay Time: 0\n      Enabled: false\n      Invert Rainbow: false\n      Max Color: 255; 255; 255\n      Min Color: 0; 0; 0\n      Name: inlier_pc2\n      Position Transformer: XYZ\n      Queue Size: 10\n      Selectable: true\n      Size (Pixels): 3\n      Size (m): 0.20000000298023224\n      Style: Squares\n      Topic: /eagle_data/inlier_pc2\n      Unreliable: false\n      Use Fixed Frame: true\n      Use rainbow: true\n      Value: false\n    - Alpha: 1\n      Autocompute Intensity Bounds: true\n      Autocompute Value Bounds:\n        Max Value: 10\n        Min Value: -10\n        Value: true\n      Axis: Z\n      Channel Name: intensity\n      Class: rviz/PointCloud2\n      Color: 255; 255; 255\n      Color Transformer: FlatColor\n      Decay Time: 0\n      Enabled: false\n      Invert Rainbow: false\n      Max Color: 255; 255; 255\n      Min Color: 0; 0; 0\n      Name: pc2_xyz_filtered\n      Position Transformer: XYZ\n      Queue Size: 10\n      Selectable: true\n      Size (Pixels): 4\n      Size (m): 0.009999999776482582\n      Style: Points\n      Topic: /eagle_data/pc2_xyz_filtered\n      Unreliable: false\n      Use Fixed Frame: true\n      Use rainbow: true\n      Value: false\n    - Alpha: 1\n      Autocompute Intensity Bounds: true\n      Autocompute Value Bounds:\n        Max Value: 10\n        Min Value: -10\n        Value: true\n      Axis: Z\n      Channel Name: intensity\n      Class: rviz/PointCloud2\n      Color: 255; 255; 255\n      Color Transformer: Intensity\n      Decay Time: 0\n      Enabled: false\n      Invert Rainbow: false\n      Max Color: 255; 255; 255\n      Min Color: 0; 0; 0\n      Name: segmented\n      Position Transformer: XYZ\n      Queue Size: 10\n      Selectable: true\n      Size (Pixels): 3\n      Size (m): 0.009999999776482582\n      Style: Points\n      Topic: /eagle_data/segmented\n      Unreliable: false\n      Use Fixed Frame: true\n      Use rainbow: true\n      Value: false\n    - Angle Tolerance: 0.10000000149011612\n      Class: rviz/Odometry\n      Covariance:\n        Orientation:\n          Alpha: 0.5\n          Color: 255; 255; 127\n          Color Style: Unique\n          Frame: Local\n          Offset: 1\n          Scale: 1\n          Value: true\n        Position:\n          Alpha: 0.30000001192092896\n          Color: 204; 51; 204\n          Scale: 1\n          Value: true\n        Value: true\n      Enabled: false\n      Keep: 1\n      Name: imu_odom\n      Position Tolerance: 0.10000000149011612\n      Queue Size: 10\n      Shape:\n        Alpha: 1\n        Axes Length: 2\n        Axes Radius: 0.10000000149011612\n        Color: 255; 25; 0\n        Head Length: 0.30000001192092896\n        Head Radius: 0.10000000149011612\n        Shaft Length: 1\n        Shaft Radius: 0.05000000074505806\n        Value: Axes\n      Topic: /imu_pre_integ/imu_odom\n      Unreliable: false\n      Value: false\n    - Angle Tolerance: 0\n      Class: rviz/Odometry\n      Covariance:\n        Orientation:\n          Alpha: 0.5\n          Color: 255; 255; 127\n          Color Style: Unique\n          Frame: Local\n          Offset: 1\n          Scale: 1\n          Value: true\n        Position:\n          Alpha: 0.30000001192092896\n          Color: 204; 51; 204\n          Scale: 1\n          Value: true\n        Value: true\n      Enabled: true\n      Keep: 1\n      Name: imu_odom_incre\n      Position Tolerance: 0\n      Queue Size: 10\n      Shape:\n        Alpha: 1\n        Axes Length: 1.5\n        Axes Radius: 0.10000000149011612\n        Color: 255; 25; 0\n        Head Length: 0.30000001192092896\n        Head Radius: 0.10000000149011612\n        Shaft Length: 1\n        Shaft Radius: 0.05000000074505806\n        Value: Axes\n      Topic: /imu_pre_integ/imu_odom_incre\n      Unreliable: false\n      Value: true\n    - Angle Tolerance: 0\n      Class: rviz/Odometry\n      Covariance:\n        Orientation:\n          Alpha: 0.5\n          Color: 255; 255; 127\n          Color Style: Unique\n          Frame: Local\n          Offset: 1\n          Scale: 1\n          Value: true\n        Position:\n          Alpha: 0.30000001192092896\n          Color: 204; 51; 204\n          Scale: 1\n          Value: true\n        Value: true\n      Enabled: false\n      Keep: 1\n      Name: frame2frame\n      Position Tolerance: 0\n      Queue Size: 10\n      Shape:\n        Alpha: 1\n        Axes Length: 2\n        Axes Radius: 0.20000000298023224\n        Color: 255; 25; 0\n        Head Length: 0.30000001192092896\n        Head Radius: 0.10000000149011612\n        Shaft Length: 1\n        Shaft Radius: 0.05000000074505806\n        Value: Axes\n      Topic: /radar_graph_slam/odom_frame2frame\n      Unreliable: false\n      Value: false\n    - Angle Tolerance: 0\n      Class: rviz/Odometry\n      Covariance:\n        Orientation:\n          Alpha: 0.5\n          Color: 255; 255; 127\n          Color Style: Unique\n          Frame: Local\n          Offset: 1\n          Scale: 1\n          Value: true\n        Position:\n          Alpha: 0.30000001192092896\n          Color: 204; 51; 204\n          Scale: 1\n          Value: true\n        Value: true\n      Enabled: false\n      Keep: 1\n      Name: ground_truth\n      Position Tolerance: 0\n      Queue Size: 10\n      Shape:\n        Alpha: 1\n        Axes Length: 1\n        Axes Radius: 0.10000000149011612\n        Color: 252; 233; 79\n        Head Length: 0.30000001192092896\n        Head Radius: 0.20000000298023224\n        Shaft Length: 1.5\n        Shaft Radius: 0.10000000149011612\n        Value: Arrow\n      Topic: /aftmapped_to_init\n      Unreliable: false\n      Value: false\n    - Class: rviz/Image\n      Enabled: false\n      Image Topic: /sc/cur\n      Max Value: 1\n      Median window: 5\n      Min Value: 0\n      Name: sc_cur\n      Normalize Range: true\n      Queue Size: 2\n      Transport Hint: raw\n      Unreliable: false\n      Value: false\n    - Class: rviz/Image\n      Enabled: false\n      Image Topic: /sc/pre\n      Max Value: 1\n      Median window: 5\n      Min Value: 0\n      Name: sc_pre\n      Normalize Range: true\n      Queue Size: 2\n      Transport Hint: raw\n      Unreliable: false\n      Value: false\n    - Class: rviz/Image\n      Enabled: true\n      Image Topic: /rgb_cam/image_raw\n      Max Value: 1\n      Median window: 5\n      Min Value: 0\n      Name: Image\n      Normalize Range: true\n      Queue Size: 2\n      Transport Hint: compressed\n      Unreliable: false\n      Value: true\n    - Angle Tolerance: 0.10000000149011612\n      Class: rviz/Odometry\n      Covariance:\n        Orientation:\n          Alpha: 0.5\n          Color: 255; 255; 127\n          Color Style: Unique\n          Frame: Local\n          Offset: 1\n          Scale: 1\n          Value: true\n        Position:\n          Alpha: 0.30000001192092896\n          Color: 204; 51; 204\n          Scale: 1\n          Value: true\n        Value: true\n      Enabled: false\n      Keep: 1\n      Name: orient_incre\n      Position Tolerance: 0.10000000149011612\n      Queue Size: 10\n      Shape:\n        Alpha: 1\n        Axes Length: 2\n        Axes Radius: 0.20000000298023224\n        Color: 255; 25; 0\n        Head Length: 0.30000001192092896\n        Head Radius: 0.10000000149011612\n        Shaft Length: 1\n        Shaft Radius: 0.05000000074505806\n        Value: Axes\n      Topic: /imu_pre_integ/orient_incre\n      Unreliable: false\n      Value: false\n  Enabled: true\n  Global Options:\n    Background Color: 48; 48; 48\n    Default Light: true\n    Fixed Frame: map\n    Frame Rate: 30\n  Name: root\n  Tools:\n    - Class: rviz/Interact\n      Hide Inactive Objects: true\n    - Class: rviz/MoveCamera\n    - Class: rviz/Select\n    - Class: rviz/FocusCamera\n    - Class: rviz/Measure\n    - Class: rviz/SetInitialPose\n      Theta std deviation: 0.2617993950843811\n      Topic: /initialpose\n      X std deviation: 0.5\n      Y std deviation: 0.5\n    - Class: rviz/SetGoal\n      Topic: /move_base_simple/goal\n    - Class: rviz/PublishPoint\n      Single click: true\n      Topic: /clicked_point\n  Value: true\n  Views:\n    Current:\n      Class: rviz/ThirdPersonFollower\n      Distance: 85.2797622680664\n      Enable Stereo Rendering:\n        Stereo Eye Separation: 0.05999999865889549\n        Stereo Focal Distance: 1\n        Swap Stereo Eyes: false\n        Value: false\n      Field of View: 0.7853981852531433\n      Focal Point:\n        X: 1.0382673740386963\n        Y: -3.4685535430908203\n        Z: -9.450655488763005e-05\n      Focal Shape Fixed Size: false\n      Focal Shape Size: 0.05000000074505806\n      Invert Z Axis: false\n      Name: Current View\n      Near Clip Distance: 0.009999999776482582\n      Pitch: 0.4747973680496216\n      Target Frame: base_link\n      Yaw: 3.460458517074585\n    Saved:\n      - Class: rviz/ThirdPersonFollower\n        Distance: 53.36624526977539\n        Enable Stereo Rendering:\n          Stereo Eye Separation: 0.05999999865889549\n          Stereo Focal Distance: 1\n          Swap Stereo Eyes: false\n          Value: false\n        Field of View: 0.7853981852531433\n        Focal Point:\n          X: -12.508434295654297\n          Y: -8.800935745239258\n          Z: 1.2215999364852905\n        Focal Shape Fixed Size: true\n        Focal Shape Size: 0.05000000074505806\n        Invert Z Axis: false\n        Name: ThirdPersonFollower\n        Near Clip Distance: 0.009999999776482582\n        Pitch: 0.6847967505455017\n        Target Frame: base_link\n        Yaw: -2.4200901985168457\nWindow Geometry:\n  Displays:\n    collapsed: false\n  Height: 1169\n  Hide Left Dock: false\n  Hide Right Dock: false\n  Image:\n    collapsed: false\n  QMainWindow State: 000000ff00000000fd000000040000000000000156000003d1fc020000000bfb000000100044006900730070006c006100790073010000003d00000258000000c900fffffffb0000000a0049006d006100670065010000029b000001730000001600fffffffb0000000a0063006f006c006f007200000002bb000001450000000000000000fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000b0fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb0000000c00730063005f00630075007202000001e80000004700000280000001e0fb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000014c000003d1fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d000003d1000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000b44000000a9fc0100000002fb0000000c00730063005f00700072006502000000480000004700000280000001e0fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000006c300000060fc0100000002fb0000000800540069006d00650100000000000006c3000003bc00fffffffb0000000800540069006d0065010000000000000450000000000000000000000415000003d100000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000\n  Selection:\n    collapsed: false\n  Time:\n    collapsed: false\n  Tool Properties:\n    collapsed: false\n  Views:\n    collapsed: false\n  Width: 1731\n  X: 514\n  Y: 27\n  sc_cur:\n    collapsed: false\n  sc_pre:\n    collapsed: false\n"
  },
  {
    "path": "setup.py",
    "content": "## ! DO NOT MANUALLY INVOKE THIS setup.py, USE CATKIN INSTEAD\n\nfrom setuptools import setup\nfrom catkin_pkg.python_setup import generate_distutils_setup\n\n# fetch values from package.xml\nsetup_args = generate_distutils_setup(\n    packages=['radar_graph_slam'],\n    package_dir={'': 'src'})\n\nsetup(**setup_args)\n\n"
  },
  {
    "path": "src/g2o/robust_kernel_io.cpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#include <g2o/robust_kernel_io.hpp>\n\n#include <fstream>\n#include <iostream>\n#include <g2o/core/robust_kernel.h>\n#include <g2o/core/robust_kernel_impl.h>\n#include <g2o/core/robust_kernel_factory.h>\n#include <g2o/core/sparse_optimizer.h>\n\nnamespace g2o {\n\nstd::string kernel_type(g2o::RobustKernel* kernel) {\n  if(dynamic_cast<g2o::RobustKernelHuber*>(kernel)) {\n    return \"Huber\";\n  }\n  if(dynamic_cast<g2o::RobustKernelCauchy*>(kernel)) {\n    return \"Cauchy\";\n  }\n  if(dynamic_cast<g2o::RobustKernelDCS*>(kernel)) {\n    return \"DCS\";\n  }\n  if(dynamic_cast<g2o::RobustKernelFair*>(kernel)) {\n    return \"Fair\";\n  }\n  if(dynamic_cast<g2o::RobustKernelGemanMcClure*>(kernel)) {\n    return \"GemanMcClure\";\n  }\n  if(dynamic_cast<g2o::RobustKernelPseudoHuber*>(kernel)) {\n    return \"PseudoHuber\";\n  }\n  if(dynamic_cast<g2o::RobustKernelSaturated*>(kernel)) {\n    return \"Saturated\";\n  }\n  if(dynamic_cast<g2o::RobustKernelTukey*>(kernel)) {\n    return \"Tukey\";\n  }\n  if(dynamic_cast<g2o::RobustKernelWelsch*>(kernel)) {\n    return \"Welsch\";\n  }\n  return \"\";\n}\n\nbool save_robust_kernels(const std::string& filename, g2o::SparseOptimizer* graph) {\n  std::ofstream ofs(filename);\n  if(!ofs) {\n    std::cerr << \"failed to open output stream!!\" << std::endl;\n    return false;\n  }\n\n  for(const auto& edge_ : graph->edges()) {\n    g2o::OptimizableGraph::Edge* edge = static_cast<g2o::OptimizableGraph::Edge*>(edge_);\n    g2o::RobustKernel* kernel = edge->robustKernel();\n    if(!kernel) {\n      continue;\n    }\n\n    std::string type = kernel_type(kernel);\n    if(type.empty()) {\n      std::cerr << \"unknown kernel type!!\" << std::endl;\n      continue;\n    }\n\n    ofs << edge->vertices().size() << \" \";\n    for(size_t i = 0; i < edge->vertices().size(); i++) {\n      ofs << edge->vertices()[i]->id() << \" \";\n    }\n    ofs << type << \" \" << kernel->delta() << std::endl;\n  }\n\n  return true;\n}\n\nclass KernelData {\npublic:\n  KernelData(const std::string& line) {\n    std::stringstream sst(line);\n    size_t num_vertices;\n    sst >> num_vertices;\n\n    vertex_indices.resize(num_vertices);\n    for(size_t i = 0; i < num_vertices; i++) {\n      sst >> vertex_indices[i];\n    }\n\n    sst >> type >> delta;\n  }\n\n  bool match(g2o::OptimizableGraph::Edge* edge) const {\n    if(edge->vertices().size() != vertex_indices.size()) {\n      return false;\n    }\n\n    for(size_t i = 0; i < edge->vertices().size(); i++) {\n      if(edge->vertices()[i]->id() != vertex_indices[i]) {\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  g2o::RobustKernel* create() const {\n    auto factory = g2o::RobustKernelFactory::instance();\n    g2o::RobustKernel* kernel = factory->construct(type);\n    kernel->setDelta(delta);\n\n    return kernel;\n  }\n\npublic:\n  std::vector<int> vertex_indices;\n  std::string type;\n  double delta;\n};\n\nbool load_robust_kernels(const std::string& filename, g2o::SparseOptimizer* graph) {\n  std::ifstream ifs(filename);\n  if(!ifs) {\n    std::cerr << \"warning: failed to open input stream!!\" << std::endl;\n    return true;\n  }\n\n  std::vector<KernelData> kernels;\n\n  while(!ifs.eof()) {\n    std::string line;\n    std::getline(ifs, line);\n    if(line.empty()) {\n      continue;\n    }\n\n    kernels.push_back(KernelData(line));\n  }\n  std::cout << \"kernels: \" << kernels.size() << std::endl;\n\n  for(auto& edge_ : graph->edges()) {\n    g2o::OptimizableGraph::Edge* edge = static_cast<g2o::OptimizableGraph::Edge*>(edge_);\n\n    for(auto itr = kernels.begin(); itr != kernels.end(); itr++) {\n      if(itr->match(edge)) {\n        edge->setRobustKernel(itr->create());\n        kernels.erase(itr);\n        break;\n      }\n    }\n  }\n\n  if(kernels.size() != 0) {\n    std::cerr << \"warning: there is non-associated kernels!!\" << std::endl;\n  }\n  return true;\n}\n\n}  // namespace g2o\n"
  },
  {
    "path": "src/gps_traj_align.cpp",
    "content": "#include <stdio.h>\n\n#include <algorithm>\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <vector>\n#include <boost/algorithm/string/classification.hpp>\n#include <boost/algorithm/string/split.hpp>\n\n#include <Eigen/Dense>\n\n#include <ros/ros.h>\n#include <geodesy/utm.h>\n#include <geodesy/wgs84.h>\n#include <rosbag/bag.h>\n#include <rosbag/view.h>\n#include <sensor_msgs/NavSatFix.h>\n#include <geographic_msgs/GeoPointStamped.h>\n#include <g2o/types/slam3d/edge_se3.h>\n#include <g2o/types/slam3d/vertex_se3.h>\n\n#include <radar_graph_slam/graph_slam.hpp>\n\nusing namespace std;\nusing namespace radar_graph_slam;\n\nstd::vector<std::pair<int,int>> associate(std::vector<double> first_stamps, std::vector<double> second_stamps, double offset, double max_difference)\n{\n    /*\n    associate timestamps\n\n    first_stamps, second_stamps: list of timestamps to associate\n\n    Output:\n    sorted list of matches (match_first_idx, match_second_idx)\n    */\n    // potential_matches = [(abs(a - (b + offset)), idx_a, idx_b)\n    //                      for idx_a, a in enumerate(first_stamps)\n    //                      for idx_b, b in enumerate(second_stamps)\n    //                      if abs(a - (b + offset)) < max_difference]\n    std::vector<std::pair<int,int>> potential_matches;\n    for (int i=0; i < first_stamps.size(); i++){\n        for (int j; j < second_stamps.size(); j++){\n            double& a = first_stamps.at(i);\n            double& b = second_stamps.at(j);\n            if (abs(a - (b + offset)) < max_difference)\n                potential_matches.push_back(std::pair<int,int>(i,j));\n        }\n    }\n    \n    std::sort(potential_matches.begin(), potential_matches.end());\n    // potential_matches.sort()  // prefer the closest\n#if 0\n    std::vector<int> first_idxes, second_idxes;\n    for (int i=0; i<first_stamps.size(); i++)\n        first_idxes.push_back(i);\n    for (int i=0; i<second_stamps.size(); i++)\n        second_idxes.push_back(i);\n    std::vector<std::pair<int,int>> matches;\n    \n    for (int i=0; i < potential_matches.size(); i++){\n        int idx_a = potential_matches.at(i).first;\n        int idx_b = potential_matches.at(i).second;\n        vector<int>::iterator ret_a, ret_b;\n        ret_a = std::find(first_idxes.begin(), first_idxes.end(), idx_a);\n        ret_b = std::find(second_idxes.begin(), second_idxes.end(), idx_b);\n\n        bool idx_a_in_first_idxes, idx_b_in_second_idxes;\n        if(ret_a == first_idxes.end()) idx_a_in_first_idxes = false;\n        else idx_a_in_first_idxes = true;\n        if(ret_b == second_idxes.end()) idx_b_in_second_idxes = false;\n        else idx_b_in_second_idxes = true;\n        \n        if (idx_a_in_first_idxes && idx_b_in_second_idxes) {\n            cout << first_idxes.size() << \" \" << idx_a << endl;\n            cout << second_idxes.size() << \" \" << idx_b << endl;\n            first_idxes.erase(first_idxes.begin()+idx_a, first_idxes.begin()+idx_a+1);\n            second_idxes.erase(second_idxes.begin()+idx_b, second_idxes.begin()+idx_b+1);\n            matches.push_back(std::pair<int,int>(idx_a, idx_b));\n        }\n    }\n    \n    std::sort(matches.begin(), matches.end());\n    // for diff, idx_a, idx_b in potential_matches:\n    //     if idx_a in first_idxes and idx_b in second_idxes:\n    //         first_idxes.remove(idx_a)\n    //         second_idxes.remove(idx_b)\n    //         matches.append((int(idx_a), int(idx_b)))\n    // matches.sort()\n    return matches;\n#endif\n    return potential_matches;\n}\n\nvoid read_topic_from_rosbag(rosbag::View::iterator& iter, rosbag::View& viewer, std::vector<sensor_msgs::NavSatFix::Ptr>& msgs_queue){\n    for (; iter != viewer.end(); ++iter)\n    {\n        // Get a frame of data\n        auto m = *iter;\n        std::string topic = m.getTopic();\n        if (topic == \"/ublox/fix\")\n        {\n            sensor_msgs::NavSatFix::Ptr navsat_msg = m.instantiate<sensor_msgs::NavSatFix>();\n            // cout << \"/ublox/fix   \" << setprecision(19) << navsat_msg->header.stamp.toSec() << endl;\n            msgs_queue.push_back(navsat_msg);\n        }\n    }\n}\n\nint main()\n{\n    ifstream file_in(\"/home/zhuge/datasets/Radar/gt_loop3.txt\");\n    if (!file_in.is_open()) {\n        cout << \"Can not open the groundtruth file\" << endl;\n        return 0;\n    }\n\n    rosbag::Bag bag0, bag1, bag2, bag3;\n    // bag0.open(\"/home/zhuge/datasets/Radar/ntu_loop2_car/ntu-car-day-loop2_2022-06-03_0.bag\", rosbag::bagmode::Read);\n    // bag1.open(\"/home/zhuge/datasets/Radar/ntu_loop2_car/ntu-car-day-loop2_2022-06-03_1.bag\", rosbag::bagmode::Read);\n    // bag2.open(\"/home/zhuge/datasets/Radar/ntu_loop2_car/ntu-car-day-loop2_2022-06-03_2.bag\", rosbag::bagmode::Read);\n    // bag3.open(\"/home/zhuge/datasets/Radar/ntu_loop2_car/ntu-car-day-loop2_2022-06-03_3.bag\", rosbag::bagmode::Read);\n\n    bag0.open(\"/home/zhuge/datasets/Radar/ntu_loop3_car/ntu-car-day-loop3_2022-06-03_0.bag\", rosbag::bagmode::Read);\n    bag1.open(\"/home/zhuge/datasets/Radar/ntu_loop3_car/ntu-car-day-loop3_2022-06-03_1.bag\", rosbag::bagmode::Read);\n    bag2.open(\"/home/zhuge/datasets/Radar/ntu_loop3_car/ntu-car-day-loop3_2022-06-03_2.bag\", rosbag::bagmode::Read);\n\n    std::vector<std::string> topics;\n    topics.push_back(std::string(\"/ublox/fix\"));\n\n    rosbag::View view_0(bag0, rosbag::TopicQuery(topics));\n    rosbag::View view_1(bag1, rosbag::TopicQuery(topics));\n    rosbag::View view_2(bag2, rosbag::TopicQuery(topics));\n    // rosbag::View view_3(bag3, rosbag::TopicQuery(topics));\n    // Using iterators to iterate\n    rosbag::View::iterator it_0 = view_0.begin();\n    rosbag::View::iterator it_1 = view_1.begin();\n    rosbag::View::iterator it_2 = view_2.begin();\n    // rosbag::View::iterator it_3 = view_3.begin();\n\n    std::vector<sensor_msgs::NavSatFix::Ptr> navsat_msgs;\n    read_topic_from_rosbag(it_0, view_0, navsat_msgs);\n    read_topic_from_rosbag(it_1, view_1, navsat_msgs);\n    read_topic_from_rosbag(it_2, view_2, navsat_msgs);\n    // read_topic_from_rosbag(it_3, view_3, navsat_msgs);\n\n    cout << \"The Number of navsat message in bag is \" << navsat_msgs.size() << endl;\n    bag0.close();\n    bag1.close();\n    bag2.close();\n    // bag3.close();\n\n    std::vector<Eigen::Vector3d> utm_coordinates;\n    std::vector<double> utm_stamps;\n    std::vector<Eigen::Vector3d> utm_covariences;\n    for (int i; i < navsat_msgs.size(); i++){\n        if (navsat_msgs.at(i)->position_covariance.at(0) > 3 || navsat_msgs.at(i)->position_covariance.at(8) > 8)\n                continue;\n        geographic_msgs::GeoPointStampedPtr gps_msg(new geographic_msgs::GeoPointStamped());\n        gps_msg->header = navsat_msgs.at(i)->header;\n        gps_msg->position.latitude = navsat_msgs.at(i)->latitude;\n        gps_msg->position.longitude = navsat_msgs.at(i)->longitude;\n        gps_msg->position.altitude = navsat_msgs.at(i)->altitude;\n        // convert (latitude, longitude, altitude) -> (easting, northing, altitude) in UTM coordinate\n        geodesy::UTMPoint utm;\n        geodesy::fromMsg(gps_msg->position, utm); \n        Eigen::Vector3d xyz(utm.easting, utm.northing, utm.altitude);\n        Eigen::Vector3d cov(navsat_msgs.at(i)->position_covariance.at(0), navsat_msgs.at(i)->position_covariance.at(4), navsat_msgs.at(i)->position_covariance.at(8));\n        utm_covariences.push_back(cov);\n        utm_coordinates.push_back(xyz);\n        utm_stamps.push_back(navsat_msgs.at(i)->header.stamp.toSec());\n    }\n    cout << \"The Number of chosen utm coordinate points is \" << utm_coordinates.size() << endl;\n    \n    std::vector<std::string> vectorLines;\n    std::string line;\n    while (getline(file_in, line)) \n        vectorLines.push_back(line);\n\n    std::vector<Eigen::Vector3d> gt_coordinates;\n    std::vector<double> gt_stamps;\n    for (size_t i = 1; i < vectorLines.size(); i++) {\n        std::string line_ = vectorLines.at(i);\n        double stamp,tx,ty,tz,qx,qy,qz,qw;\n        stringstream data(line_);\n        data >> stamp >> tx >> ty >> tz >> qx >> qy >> qz >> qw;\n        Eigen::Vector3d point(Eigen::Vector3d(tx,ty,tz));\n        gt_coordinates.push_back(point);\n        gt_stamps.push_back(stamp);\n    }\n    cout << \"The Number of groundtruth points is \" << gt_coordinates.size() << endl;\n\n    vector<pair<int,int>> associated_pairs = associate(gt_stamps, utm_stamps, 0, 0.02);\n    cout << \"The Number of associated point pairs is \" << associated_pairs.size() << endl;\n    // cout << \"associated_pairs:\" << endl;\n    // for (auto p:associated_pairs){\n    //     cout << p.first << \" \" << p.second << endl;\n    // }\n\n    // // Calculate the initial guess, to save computation\n    // int pair_size = associated_pairs.size();\n    // Eigen::Matrix4Xd A (4, pair_size); // we have transposed A here for the convenience of computation\n    // Eigen::Matrix4Xd b (4, pair_size);\n    // for (int k = 0; k < pair_size; ++k) {\n    //   A.block<3, 1>(0, k) = gt_coordinates.at(associated_pairs.at(k).first);\n    //   A(3, k) = 1;\n    //   b.block<3, 1>(0, k) = utm_coordinates.at(associated_pairs.at(k).second);\n    //   b(3, k) = 1;\n    // }\n    // // solve for T\n    // Eigen::Matrix4d result = A * b.inverse();\n    // Eigen::Matrix3d R_0 = result.topLeftCorner(3,3);\n    // Eigen::AngleAxisd aa(R_0);    // RotationMatrix to AxisAngle\n    // cout << aa.axis() << endl;\n    // cout << aa.axis().norm() << endl;\n    // cout << aa.angle() << endl;\n    // Eigen::Matrix3d R = aa.toRotationMatrix();  // AxisAngle      to RotationMatrix\n    // Eigen::Vector3d t = result.block<3,1>(0,3);\n    // Eigen::Matrix4d initial_transform = Eigen::Matrix4d::Identity();\n    // initial_transform.topLeftCorner(3,3) = R;\n    // initial_transform.block<3,1>(0,3) = t;\n    // cout << \"The guess transform matrix is :\" << endl;\n    // cout << initial_transform << endl;\n\n    /**** Construct the pose graph ****/\n    std::unique_ptr<GraphSLAM> graph_slam;\n    graph_slam.reset(new GraphSLAM(\"lm_var\"));\n    \n    g2o::VertexSE3* node = graph_slam->add_se3_node(Eigen::Isometry3d::Identity());\n\n    for (int i=0; i < associated_pairs.size(); i++){\n        Eigen::Vector3d& gt = gt_coordinates.at(associated_pairs.at(i).first);\n        Eigen::Vector3d& utm = utm_coordinates.at(associated_pairs.at(i).second);\n        Eigen::Matrix3d information_matrix = Eigen::Matrix3d::Identity();\n        information_matrix(0,0) /= utm_covariences.at(i)(0);\n        information_matrix(1,1) /= utm_covariences.at(i)(1);\n        information_matrix(2,2) /= utm_covariences.at(i)(2);\n        auto edge = graph_slam->add_se3_gt_utm_edge(node, gt, utm, information_matrix);\n    }\n    \n    // optimize the pose graph\n    int num_iterations = 10240;\n    graph_slam->optimize(num_iterations);\n    cout << \"optimized !\" << endl;\n\n    Eigen::Matrix4d transform = node->estimate().matrix();\n    cout << \"The final transform matrix form UTM to World is :\" << endl;\n    cout << fixed << setprecision(6);\n    cout << transform << endl;\n    \n    return 0;\n}"
  },
  {
    "path": "src/gt_adjust.cpp",
    "content": "#include <stdio.h>\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <vector>\n#include <boost/algorithm/string/classification.hpp>\n#include <boost/algorithm/string/split.hpp>\n\n#include <Eigen/Dense>\n\n#include <g2o/types/slam3d/edge_se3.h>\n#include <g2o/types/slam3d/vertex_se3.h>\n#include <g2o/edge_se3_priorxyz.hpp>\n\n#include <radar_graph_slam/graph_slam.hpp>\n\nusing namespace std;\nusing namespace radar_graph_slam;\n\n// Loop 3 matches: 0-5364 947-6168\n// Loop 2 matches: 0-8240 583-8658 1350-9373 1573-9582\n// Loop 1 matches: 0-10133(0-1654230309)  1961-11949(1654229491.74-1654230490.55)\nint main()\n{\n    ifstream file_in(\"/home/zhuge/stamped_groundtruth_1.txt\");\n    \n    if (!file_in.is_open()) {\n        cout << \"Can not open this file\" << endl;\n        return 0;\n    }\n    std::vector<std::string> vectorLines;\n    std::string line;\n    while (getline(file_in, line)) {\n        vectorLines.push_back(line);\n    }\n\n    Eigen::MatrixXd pose_data;\n    pose_data.resize(vectorLines.size()-1, 8);\n    std::vector<Eigen::Isometry3d> odoms;\n\n    for (size_t i = 1; i < vectorLines.size(); i++) {\n        std::string line_ = vectorLines.at(i);\n        double stamp,tx,ty,tz,qx,qy,qz,qw;\n        stringstream data(line_);\n        data >> stamp >> tx >> ty >> tz >> qx >> qy >> qz >> qw;\n        pose_data.block<1, 8>(i-1, 0) << stamp, tx, ty, tz, qx, qy, qz, qw;\n        Eigen::Isometry3d odom(Eigen::Isometry3d::Identity());\n        odom.pretranslate(Eigen::Vector3d(tx, ty, tz));\n        odom.rotate(Eigen::Quaterniond(qw, qx, qy, qz));\n        odoms.push_back(odom);\n    }\n\n    // Construct Pose Graph\n    std::unique_ptr<GraphSLAM> graph_slam;\n    graph_slam.reset(new GraphSLAM(\"lm_var\"));\n    std::vector<g2o::VertexSE3*> vertices;\n    for (size_t i = 0; i < odoms.size(); i++) {\n        g2o::VertexSE3* node = graph_slam->add_se3_node(odoms.at(i));\n        if (i == 0)\n            node->setFixed(true);\n        vertices.push_back(node);\n    }\n    for (size_t i = 0; i < odoms.size()-1; i++) {\n        Eigen::Isometry3d rel_odom = odoms.at(i).inverse() * odoms.at(i+1);\n        Eigen::MatrixXd information_matrix_se3 = Eigen::MatrixXd::Identity(6,6) / 0.05;\n        auto edge_odom = graph_slam->add_se3_edge(vertices.at(i), vertices.at(i+1), rel_odom, information_matrix_se3);\n        graph_slam->add_robust_kernel(edge_odom, \"Huber\", 1.0);\n    }\n    // Add Loop Edges\n    // Eigen::Matrix3d information_matrix_xyz = Eigen::Matrix3d::Identity() / 1.0;\n    // graph_slam->add_se3_prior_xyz_edge(vertices.at(8240), Eigen::Vector3d(0,0,0), information_matrix_xyz);\n    Eigen::MatrixXd information_matrix = Eigen::MatrixXd::Identity(6, 6);\n    information_matrix.block<3,3>(0,0) /= 0.5;\n    information_matrix.block<3,3>(3,3) /= 1;\n    // graph_slam->add_se3_edge(vertices.at(1961), vertices.at(11949), Eigen::Isometry3d::Identity(), information_matrix);\n    graph_slam->add_se3_edge(vertices.at(0), vertices.at(10133), Eigen::Isometry3d::Identity(), information_matrix);\n    \n    // optimize the pose graph\n    int num_iterations = 1024;\n    graph_slam->optimize(num_iterations);\n    cout << \"optimized !\" << endl;\n\n    ofstream file_out(\"/home/zhuge/stamped_groundtruth.txt\", ios::trunc);\n    file_out << \"# timestamp tx ty tz qx qy qz qw\" << endl;\n    file_out.setf(ios::fixed, ios::floatfield);  // Set to fixed mode; floating-point numbers with decimal points\n    file_out.precision(8);  // Set precision\n    for(size_t i = 0; i < vertices.size(); i++) {\n        Eigen::Vector3d pos_ = vertices.at(i)->estimate().translation();\n        Eigen::Matrix3d rot_ = vertices.at(i)->estimate().rotation();\n        Eigen::Quaterniond quat_(rot_);\n        double timestamp = pose_data(i, 0);\n        double tx = pos_(0), ty = pos_(1), tz = pos_(2);\n        double qx = quat_.x(), qy = quat_.y(), qz = quat_.z(), qw = quat_.w();\n\n        file_out << timestamp << \" \"\n            << tx << \" \" << ty << \" \" << tz << \" \"\n                << qx << \" \" << qy << \" \" << qz << \" \" << qw << endl;\n    }\n\n    file_in.close();\n    file_out.close();\n\treturn 0;\n}\n\n\n\n\n"
  },
  {
    "path": "src/radar_ego_velocity_estimator.cpp",
    "content": "// This file is part of RIO - Radar Inertial Odometry and Radar ego velocity estimation.\n// Copyright (C) 2021  Christopher Doer <christopher.doer@kit.edu>\n// (Institute of Control Systems, Karlsruhe Institute of Technology)\n\n// RIO is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n//(at your option) any later version.\n\n// RIO is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n\n// You should have received a copy of the GNU General Public License\n// along with RIO.  If not, see <https://www.gnu.org/licenses/>.\n\n#define PCL_NO_PRECOMPILE\n\n#include <random>\n#include <algorithm>\n#include <iostream>\n#include <sstream>\n\n#include <angles/angles.h>\n\n#include <pcl/point_types.h>\n#include <pcl_conversions/pcl_conversions.h>\n#include <pcl/PCLPointCloud2.h>\n#include <pcl/pcl_macros.h>\n\n#include <pcl_ros/transforms.h>\n\n#include <rio_utils/radar_point_cloud.h>\n\n#include <radar_ego_velocity_estimator.h>\n\nusing namespace std;\nusing namespace rio;\n\nstatic RadarPointCloudType toRadarPointCloudType(const Vector11& item, const RadarEgoVelocityEstimatorIndices& idx)\n{\n  RadarPointCloudType point;\n  point.x             = item[idx.x_r];\n  point.y             = item[idx.y_r];\n  point.z             = item[idx.z_r];\n  point.doppler       = -item[idx.doppler];\n  point.intensity     = item[idx.snr_db];\n  return point;\n}\n\nbool RadarEgoVelocityEstimator::estimate(const sensor_msgs::PointCloud2& radar_scan_msg,\n                                         Vector3& v_r,\n                                         Vector3& sigma_v_r)\n{\n  sensor_msgs::PointCloud2 inlier_radar_msg, outlier_radar_msg;\n  return estimate(radar_scan_msg, v_r, sigma_v_r, inlier_radar_msg, outlier_radar_msg);\n}\n\nbool RadarEgoVelocityEstimator::estimate(const sensor_msgs::PointCloud2& radar_scan_msg,\n                                         Vector3& v_r,\n                                         Vector3& sigma_v_r,\n                                         sensor_msgs::PointCloud2& inlier_radar_msg,\n                                         sensor_msgs::PointCloud2& outlier_radar_msg)\n{\n  auto radar_scan(new pcl::PointCloud<RadarPointCloudType>);\n  auto radar_scan_inlier(new pcl::PointCloud<RadarPointCloudType>); // static objects\n  auto radar_scan_outlier(new pcl::PointCloud<RadarPointCloudType>);\n\n  bool success = false;\n  pcl::fromROSMsg (radar_scan_msg, *radar_scan);\n  if (1) // pcl2msgToPcl(radar_scan_msg, *radar_scan)\n  {\n    std::vector<Vector11> valid_targets;\n    for (uint i = 0; i < radar_scan->size(); ++i)\n    {\n      const auto target = radar_scan->at(i);\n      const double r      = Vector3(target.x, target.y, target.z).norm();\n\n      Real azimuth   = std::atan2(target.y, target.x);\n      Real elevation = std::atan2(std::sqrt(target.x * target.x + target.y * target.y), target.z)- M_PI_2;\n\n      if (r > config_.min_dist && r < config_.max_dist && target.intensity > config_.min_db &&\n          std::fabs(azimuth) < angles::from_degrees(config_.azimuth_thresh_deg) &&\n          std::fabs(elevation) < angles::from_degrees(config_.elevation_thresh_deg))\n      {\n        Vector11 v_pt; // features of a point\n        v_pt << target.x, target.y, target.z, target.intensity, -target.doppler * config_.doppler_velocity_correction_factor,\n                r, azimuth, elevation, target.x / r, target.y / r, target.z / r;\n        valid_targets.emplace_back(v_pt);\n      }\n      // else{\n      //   cout << (r > config_.min_dist) << (r < config_.max_dist) << (target.intensity > config_.min_db) << \\\n      //   (std::fabs(azimuth) < angles::from_degrees(config_.azimuth_thresh_deg)) << (std::fabs(elevation) < angles::from_degrees(config_.elevation_thresh_deg)) << \\\n      //   \" \" << angles::to_degrees(azimuth) << \" \" << angles::to_degrees(elevation) << endl;\n      // }\n    }\n\n    if (valid_targets.size() > 2)\n    {\n      // check for zero velocity\n      std::vector<double> v_dopplers;\n      for (const auto& v_pt : valid_targets) v_dopplers.emplace_back(std::fabs(v_pt[idx_.doppler]));\n      const size_t n = v_dopplers.size() * (1.0 - config_.allowed_outlier_percentage);\n      std::nth_element(v_dopplers.begin(), v_dopplers.begin() + n, v_dopplers.end());\n      const auto median = v_dopplers[n];\n      \n      if (median < config_.thresh_zero_velocity)\n      {\n        // ROS_INFO_STREAM_THROTTLE(0.5, kPrefix << \"Zero velocity detected!\");\n        v_r = Vector3(0, 0, 0);\n        sigma_v_r =\n            Vector3(config_.sigma_zero_velocity_x, config_.sigma_zero_velocity_y, config_.sigma_zero_velocity_z);\n        for (const auto& item : valid_targets)\n          if (std::fabs(item[idx_.doppler]) < config_.thresh_zero_velocity)\n            radar_scan_inlier->push_back(toRadarPointCloudType(item, idx_));\n        success = true;\n      }\n      else\n      {\n        // LSQ velocity estimation\n        Matrix radar_data(valid_targets.size(), 4);  // rx, ry, rz, v\n        uint idx = 0, idx_o = 0;\n        for (const auto& v_pt : valid_targets)\n          radar_data.row(idx++) = Vector4(v_pt[idx_.normalized_x], v_pt[idx_.normalized_y], v_pt[idx_.normalized_z], v_pt[idx_.doppler]);\n\n        if (config_.use_ransac)\n        {\n          std::vector<uint> inlier_idx_best;\n          std::vector<uint> outlier_idx_best;\n          success = solve3DFullRansac(radar_data, v_r, sigma_v_r, inlier_idx_best, outlier_idx_best);\n          // Insert inlier points to the cloud\n          for (const auto& idx : inlier_idx_best)\n            radar_scan_inlier->push_back(toRadarPointCloudType(valid_targets.at(idx), idx_));\n          for (const auto& idx : outlier_idx_best)\n            radar_scan_outlier->push_back(toRadarPointCloudType(valid_targets.at(idx), idx_));\n        }\n        else\n        {\n          for (const auto& item : valid_targets) radar_scan_inlier->push_back(toRadarPointCloudType(item, idx_));\n          success = solve3DFull(radar_data, v_r, sigma_v_r);\n        }\n      }\n    }\n    else ROS_INFO(\"To small valid_targets (< 2) in radar_scan (%ld)\", radar_scan->size());\n\n    radar_scan_inlier->height = 1;\n    radar_scan_inlier->width  = radar_scan_inlier->size();\n\n    pcl::PCLPointCloud2 tmp;\n    pcl::toPCLPointCloud2<RadarPointCloudType>(*radar_scan_inlier, tmp);\n    pcl_conversions::fromPCL(tmp, inlier_radar_msg);\n    inlier_radar_msg.header = radar_scan_msg.header;\n\n    radar_scan_outlier->height = 1;\n    radar_scan_outlier->width  = radar_scan_outlier->size();\n\n    pcl::PCLPointCloud2 tmp_o;\n    pcl::toPCLPointCloud2<RadarPointCloudType>(*radar_scan_outlier, tmp_o);\n    pcl_conversions::fromPCL(tmp_o, outlier_radar_msg);\n    outlier_radar_msg.header = radar_scan_msg.header;\n  }\n\n  // if(success)\n  //   ROS_INFO(\"Ego Velocity estimation Successful\");\n  // else\n  //   ROS_INFO(\"Ego Velocity estimation Failed\");\n\n  return success;\n}\n\nbool RadarEgoVelocityEstimator::solve3DFullRansac(const Matrix& radar_data,\n                                                  Vector3& v_r,\n                                                  Vector3& sigma_v_r,\n                                                  std::vector<uint>& inlier_idx_best,\n                                                  std::vector<uint>& outlier_idx_best)\n{\n  Matrix H_all(radar_data.rows(), 3);\n  H_all.col(0)       = radar_data.col(0);\n  H_all.col(1)       = radar_data.col(1);\n  H_all.col(2)       = radar_data.col(2);\n  const Vector y_all = radar_data.col(3);\n\n  std::vector<uint> idx(radar_data.rows());\n  for (uint k = 0; k < radar_data.rows(); ++k) idx[k] = k;\n\n  std::random_device rd;\n  std::mt19937 g(rd());\n\n  if (radar_data.rows() >= config_.N_ransac_points)\n  {\n    for (uint k = 0; k < ransac_iter_; ++k)\n    {\n      std::shuffle(idx.begin(), idx.end(), g);\n      Matrix radar_data_iter;\n      radar_data_iter.resize(config_.N_ransac_points, 4);\n\n      for (uint i = 0; i < config_.N_ransac_points; ++i) radar_data_iter.row(i) = radar_data.row(idx.at(i));\n      bool rtn = solve3DFull(radar_data_iter, v_r, sigma_v_r, false);\n      if (rtn == false) ROS_INFO(\"Failure at solve3DFullRansac() 1\");\n      if (rtn)\n      {\n        const Vector err = (y_all - H_all * v_r).array().abs();\n        std::vector<uint> inlier_idx;\n        std::vector<uint> outlier_idx;\n        for (uint j = 0; j < err.rows(); ++j)\n        {\n          // ROS_INFO(\"Error: %f\",err(j));\n          if (err(j) < config_.inlier_thresh)\n            // find inlier points\n            inlier_idx.emplace_back(j);\n          else\n            outlier_idx.emplace_back(j);\n        }\n        // if too small number of inlier detected, the error is too high, so regard outlier as inlier\n        if ( float(outlier_idx.size())/(inlier_idx.size()+outlier_idx.size()) > 0.05 )\n        {\n          inlier_idx.insert(inlier_idx.end(), outlier_idx.begin(), outlier_idx.end());\n          outlier_idx.clear();\n          // outlier_idx.swap(std::vector<uint>());\n        }\n\n        // ROS_INFO(\"Inlier number: %ld, Outlier number: %ld, outlier Ratio: %f\", \n        //           inlier_idx.size(), outlier_idx.size(), float(outlier_idx.size())/(inlier_idx.size()+outlier_idx.size()));\n\n        if (inlier_idx.size() > inlier_idx_best.size())\n        {\n          inlier_idx_best = inlier_idx;\n        }\n        if (outlier_idx.size() > outlier_idx_best.size())\n        {\n          outlier_idx_best = outlier_idx;\n        }\n      }\n    }\n  }\n  else{ROS_INFO(\"Warning: radar_data.rows() < config_.N_ransac_points\");}\n\n  if (!inlier_idx_best.empty())\n  {\n    Matrix radar_data_inlier(inlier_idx_best.size(), 4);\n    for (uint i = 0; i < inlier_idx_best.size(); ++i) radar_data_inlier.row(i) = radar_data.row(inlier_idx_best.at(i));\n    \n    bool rtn = solve3DFull(radar_data_inlier, v_r, sigma_v_r, true);\n    if (rtn == false) ROS_INFO(\"Failure at solve3DFullRansac() 2\");\n    return rtn;\n  }\n  ROS_INFO(\"Failure at solve3DFullRansac() 3\");\n  return false;\n}\n\nbool RadarEgoVelocityEstimator::solve3DFull(const Matrix& radar_data,\n                                            Vector3& v_r,\n                                            Vector3& sigma_v_r,\n                                            bool estimate_sigma)\n{\n  Matrix H(radar_data.rows(), 3);\n  H.col(0)         = radar_data.col(0);\n  H.col(1)         = radar_data.col(1);\n  H.col(2)         = radar_data.col(2);\n  const Matrix HTH = H.transpose() * H;\n\n  const Vector y = radar_data.col(3);\n\n  Eigen::JacobiSVD<Matrix> svd(HTH);\n  Real cond = svd.singularValues()(0) / svd.singularValues()(svd.singularValues().size() - 1);\n\n  // cond > 1000, error occurs\n  if (1)//std::fabs(cond) < 1.0e3\n  {\n    if (config_.use_cholesky_instead_of_bdcsvd)\n    {\n      v_r = (HTH).ldlt().solve(H.transpose() * y);\n    }\n    else\n      v_r = H.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(y);\n\n    if (estimate_sigma)\n    {\n      const Vector e = H * v_r - y;\n      const Matrix C = (e.transpose() * e).x() * (HTH).inverse() / (H.rows() - 3);\n      sigma_v_r      = Vector3(C(0, 0), C(1, 1), C(2, 2));\n      sigma_v_r      = sigma_v_r.array();\n      if (sigma_v_r.x() >= 0.0 && sigma_v_r.y() >= 0.0 && sigma_v_r.z() >= 0.)\n      {\n        sigma_v_r = sigma_v_r.array().sqrt();\n        sigma_v_r += Vector3(config_.sigma_offset_radar_x, config_.sigma_offset_radar_y, config_.sigma_offset_radar_z);\n        if (sigma_v_r.x() < config_.max_sigma_x && sigma_v_r.y() < config_.max_sigma_y &&\n            sigma_v_r.z() < config_.max_sigma_z)\n        {\n          return true;\n        }\n      }\n    }\n    else\n    {\n      return true;\n    }\n  }\n  //ROS_INFO(\"cond too large, cond = %f\", cond);\n\n  return true;//return false;\n}\n"
  },
  {
    "path": "src/radar_graph_slam/Scancontext.cpp",
    "content": "#include \"scan_context/Scancontext.h\"\n\n// namespace SC2\n// {\nusing namespace radar_graph_slam;\n\nvoid coreImportTest (void)\n{\n    cout << \"scancontext lib is successfully imported.\" << endl;\n} // coreImportTest\n\n\nfloat rad2deg(float radians)\n{\n    return radians * 180.0 / M_PI;\n}\n\nfloat deg2rad(float degrees)\n{\n    return degrees * M_PI / 180.0;\n}\n\n\nfloat xy2theta( const float & _x, const float & _y )\n{\n    float theta;\n    if ( (_x >= 0) & (_y >= 0)) \n        theta = (180/M_PI) * atan(_y / _x);\n\n    if ( (_x < 0) & (_y >= 0)) \n        theta = 180 - ( (180/M_PI) * atan(_y / (-_x)) );\n\n    if ( (_x < 0) & (_y < 0)) \n        theta = 180 + ( (180/M_PI) * atan(_y / _x) );\n\n    if ( (_x >= 0) & (_y < 0))\n        theta = 360 - ( (180/M_PI) * atan((-_y) / _x) );\n    return theta;\n} // xy2theta\n\n\nMatrixXd circshift( MatrixXd &_mat, int _num_shift )\n{\n    // shift columns to right direction \n    assert(_num_shift >= 0);\n\n    if( _num_shift == 0 )\n    {\n        MatrixXd shifted_mat( _mat );\n        return shifted_mat; // Early return \n    }\n\n    MatrixXd shifted_mat = MatrixXd::Zero( _mat.rows(), _mat.cols() );\n    for ( int col_idx = 0; col_idx < _mat.cols(); col_idx++ )\n    {\n        int new_location = (col_idx + _num_shift) % _mat.cols();\n        shifted_mat.col(new_location) = _mat.col(col_idx);\n    }\n\n    return shifted_mat;\n\n} // circshift\n\nvoid SCManager::setScDistThresh(double thresh){\n    SC_DIST_THRES = thresh;\n}\nvoid SCManager::setAzimuthRange(double range){\n    PC_AZIMUTH_ANGLE_MAX = range;\n    PC_AZIMUTH_ANGLE_MIN = - range;\n    PC_UNIT_SECTOR_ANGLE = (PC_AZIMUTH_ANGLE_MAX - PC_AZIMUTH_ANGLE_MIN) / double(PC_NUM_SECTOR);\n}\n\nstd::vector<float> eig2stdvec( MatrixXd _eigmat )\n{\n    std::vector<float> vec( _eigmat.data(), _eigmat.data() + _eigmat.size() );\n    return vec;\n} // eig2stdvec\n\n\ndouble SCManager::distDirectSC ( MatrixXd &_sc1, MatrixXd &_sc2 )\n{\n    int num_eff_cols = 0; // i.e., to exclude all-nonzero sector\n    double sum_sector_similarity = 0;\n    for ( int col_idx = 0; col_idx < _sc1.cols(); col_idx++ )\n    {\n        VectorXd col_sc1 = _sc1.col(col_idx);\n        VectorXd col_sc2 = _sc2.col(col_idx);\n        \n        if( (col_sc1.norm() == 0) | (col_sc2.norm() == 0) )\n            continue; // don't count this sector pair. \n\n        double sector_similarity = col_sc1.dot(col_sc2) / (col_sc1.norm() * col_sc2.norm());\n\n        sum_sector_similarity = sum_sector_similarity + sector_similarity;\n        num_eff_cols = num_eff_cols + 1;\n    }\n    \n    double sc_sim = sum_sector_similarity / num_eff_cols;\n    return 1.0 - sc_sim;\n\n} // distDirectSC\n\n\nint SCManager::fastAlignUsingVkey( MatrixXd & _vkey1, MatrixXd & _vkey2)\n{\n    int argmin_vkey_shift = 0;\n    double min_veky_diff_norm = 10000000;\n    for ( int shift_idx = 0; shift_idx < _vkey1.cols(); shift_idx++ )\n    {\n        MatrixXd vkey2_shifted = circshift(_vkey2, shift_idx);\n\n        MatrixXd vkey_diff = _vkey1 - vkey2_shifted;\n\n        double cur_diff_norm = vkey_diff.norm();\n        if( cur_diff_norm < min_veky_diff_norm )\n        {\n            argmin_vkey_shift = shift_idx;\n            min_veky_diff_norm = cur_diff_norm;\n        }\n    }\n\n    return argmin_vkey_shift;\n\n} // fastAlignUsingVkey\n\n\nstd::pair<double, int> SCManager::distanceBtnScanContext( MatrixXd &_sc1, MatrixXd &_sc2 )\n{\n    // 1. fast align using variant key (not in original IROS18)\n    MatrixXd vkey_sc1 = makeSectorkeyFromScancontext( _sc1 );\n    MatrixXd vkey_sc2 = makeSectorkeyFromScancontext( _sc2 );\n    int argmin_vkey_shift = fastAlignUsingVkey( vkey_sc1, vkey_sc2 );\n\n    const int SEARCH_RADIUS = round( 0.5 * SEARCH_RATIO * _sc1.cols() ); // a half of search range \n    std::vector<int> shift_idx_search_space { argmin_vkey_shift };\n    for ( int ii = 1; ii < SEARCH_RADIUS + 1; ii++ )\n    {\n        shift_idx_search_space.push_back( (argmin_vkey_shift + ii + _sc1.cols()) % _sc1.cols() );\n        shift_idx_search_space.push_back( (argmin_vkey_shift - ii + _sc1.cols()) % _sc1.cols() );\n    }\n    std::sort(shift_idx_search_space.begin(), shift_idx_search_space.end());\n\n    // 2. fast columnwise diff \n    int argmin_shift = 0;\n    double min_sc_dist = 10000000;\n    for ( int num_shift: shift_idx_search_space )\n    {\n        MatrixXd sc2_shifted = circshift(_sc2, num_shift);\n        double cur_sc_dist = distDirectSC( _sc1, sc2_shifted );\n        if( cur_sc_dist < min_sc_dist )\n        {\n            argmin_shift = num_shift;\n            min_sc_dist = cur_sc_dist;\n        }\n    }\n\n    return make_pair(min_sc_dist, argmin_shift);\n\n} // distanceBtnScanContext\n\n\nMatrixXd SCManager::makeScancontext( pcl::PointCloud<SCPointType> & _scan_down )\n{\n    TicToc t_making_desc;\n\n    int num_pts_scan_down = _scan_down.points.size();\n\n    // main\n    const int NO_POINT = -1000;\n    MatrixXd desc = NO_POINT * MatrixXd::Ones(PC_NUM_RING, PC_NUM_SECTOR);\n\n    SCPointType pt;\n    float azim_angle, azim_range; // wihtin 2d plane\n    int ring_idx, sctor_idx;\n    for (int pt_idx = 0; pt_idx < num_pts_scan_down; pt_idx++)\n    {\n        pt.x = _scan_down.points[pt_idx].x; \n        pt.y = _scan_down.points[pt_idx].y;\n        pt.z = _scan_down.points[pt_idx].z + LIDAR_HEIGHT; // naive adding is ok (all points should be > 0).\n        pt.intensity = _scan_down.points[pt_idx].intensity;\n\n        // xyz to ring, sector\n        azim_range = sqrt(pt.x * pt.x + pt.y * pt.y);\n        // azim_angle = xy2theta(pt.x, pt.y);\n        azim_angle = (atan2f(pt.x, pt.y) - M_PI_2) * 180/M_PI;\n\n        if( abs(azim_angle) > PC_AZIMUTH_ANGLE_MAX)\n            continue;\n        // if range is out of roi, pass\n        if( azim_range > PC_MAX_RADIUS )\n            continue;\n\n        ring_idx = std::max( std::min( PC_NUM_RING, int(ceil( (azim_range / PC_MAX_RADIUS) * PC_NUM_RING )) ), 1 );\n        // sctor_idx = std::max( std::min( PC_NUM_SECTOR, int(ceil( (azim_angle / 360.0) * PC_NUM_SECTOR )) ), 1 );\n        sctor_idx = std::max( std::min( PC_NUM_SECTOR, int(ceil( ((azim_angle - PC_AZIMUTH_ANGLE_MIN) / (PC_AZIMUTH_ANGLE_MAX - PC_AZIMUTH_ANGLE_MIN)) * PC_NUM_SECTOR )) ), 1 );\n\n        // taking maximum z\n        // if ( desc(ring_idx-1, sctor_idx-1) < pt.z ) // -1 means cpp starts from 0\n        //     desc(ring_idx-1, sctor_idx-1) = pt.z; // update for taking maximum value at that bin\n        // taking maximun intensity\n        if ( desc(ring_idx-1, sctor_idx-1) < pt.intensity ) // -1 means cpp starts from 0\n            desc(ring_idx-1, sctor_idx-1) = pt.intensity; // update for taking maximum value at that bin\n    }\n\n    // reset no points to zero (for cosine dist later)\n    for ( int row_idx = 0; row_idx < desc.rows(); row_idx++ )\n        for ( int col_idx = 0; col_idx < desc.cols(); col_idx++ )\n            if( desc(row_idx, col_idx) == NO_POINT )\n                desc(row_idx, col_idx) = 0;\n\n    t_making_desc.toc(\"PolarContext making\");\n    \n    return desc;\n} // SCManager::makeScancontext\n\n\nMatrixXd SCManager::makeRingkeyFromScancontext( Eigen::MatrixXd &_desc )\n{\n    /* \n     * summary: rowwise mean vector\n    */\n    Eigen::MatrixXd invariant_key(_desc.rows(), 1);\n    for ( int row_idx = 0; row_idx < _desc.rows(); row_idx++ )\n    {\n        Eigen::MatrixXd curr_row = _desc.row(row_idx);\n        invariant_key(row_idx, 0) = curr_row.mean();\n    }\n\n    return invariant_key;\n} // SCManager::makeRingkeyFromScancontext\n\n\nMatrixXd SCManager::makeSectorkeyFromScancontext( Eigen::MatrixXd &_desc )\n{\n    /* \n     * summary: columnwise mean vector\n    */\n    Eigen::MatrixXd variant_key(1, _desc.cols());\n    for ( int col_idx = 0; col_idx < _desc.cols(); col_idx++ )\n    {\n        Eigen::MatrixXd curr_col = _desc.col(col_idx);\n        variant_key(0, col_idx) = curr_col.mean();\n    }\n\n    return variant_key;\n} // SCManager::makeSectorkeyFromScancontext\n\n\nconst Eigen::MatrixXd& SCManager::getConstRefRecentSCD(void)\n{\n    return polarcontexts_.back();\n}\n\n\nvoid SCManager::makeAndSaveScancontextAndKeys( pcl::PointCloud<SCPointType> & _scan_down )\n{\n    Eigen::MatrixXd sc = makeScancontext(_scan_down); // v1 \n    Eigen::MatrixXd ringkey = makeRingkeyFromScancontext( sc );\n    Eigen::MatrixXd sectorkey = makeSectorkeyFromScancontext( sc );\n    std::vector<float> polarcontext_invkey_vec = eig2stdvec( ringkey );\n\n    polarcontexts_.push_back( sc ); \n    polarcontext_invkeys_.push_back( ringkey );\n    polarcontext_vkeys_.push_back( sectorkey );\n    polarcontext_invkeys_mat_.push_back( polarcontext_invkey_vec );\n\n    // cout <<polarcontext_vkeys_.size() << endl;\n\n} // SCManager::makeAndSaveScancontextAndKeys\n\n\nstd::pair<int, float> SCManager::detectLoopClosureID ( const std::vector<KeyFrame::Ptr>& candidate_keyframes, const KeyFrame::Ptr& new_keyframe)\n{\n    int loop_id { -1 }; // init with -1, -1 means no loop (== LeGO-LOAM's variable \"closestHistoryFrameID\")\n\n    // 应该比较new_keyframe和 candidate_keyframes ，所以选用的是new_keyframe对应的sc\n    // cout << \"polarcontext_invkeys_mat_.size()=\" << polarcontext_invkeys_mat_.size() << \" polarcontexts_.size()=\" << polarcontexts_.size() << \" \" << endl;\n    auto curr_key = polarcontext_invkeys_mat_[new_keyframe->index]; //.back(); // current observation (query)\n    auto curr_desc = polarcontexts_[new_keyframe->index]; //.back(); // current observation (query)\n    /* \n     * step 1: candidates from ringkey tree_\n     */\n    // if( (int)polarcontext_invkeys_mat_.size() < NUM_EXCLUDE_RECENT + 1)\n    if( (int)new_keyframe->index < NUM_EXCLUDE_RECENT)\n    {\n        std::pair<int, float> result {loop_id, 0.0};\n        return result; // Early return \n    }\n    // indices of candidate keyframes\n    std::vector<size_t> candidate_keyframe_indices;\n    for (auto& ckf : candidate_keyframes)\n        candidate_keyframe_indices.push_back(ckf->index);\n    // tree_ reconstruction (not mandatory to make everytime)\n    if( tree_making_period_conter % TREE_MAKING_PERIOD_ == 0) // to save computation cost\n    {\n        TicToc t_tree_construction;\n\n        polarcontext_invkeys_to_search_.clear();\n        // polarcontext_invkeys_to_search_.assign( polarcontext_invkeys_mat_.begin(), polarcontext_invkeys_mat_.end() - NUM_EXCLUDE_RECENT ) ;\n        for (auto& index : candidate_keyframe_indices)\n            if (new_keyframe->index - index >= NUM_EXCLUDE_RECENT)\n                polarcontext_invkeys_to_search_.push_back(polarcontext_invkeys_mat_.at(index));\n        // error happeds here\n        polarcontext_tree_.reset();\n        polarcontext_tree_ = std::make_unique<InvKeyTree>(PC_NUM_RING /* dim */, polarcontext_invkeys_to_search_, 10 /* max leaf */ );\n        // tree_ptr_->index->buildIndex(); // inernally called in the constructor of InvKeyTree (for detail, refer the nanoflann and KDtreeVectorOfVectorsAdaptor)\n        t_tree_construction.toc(\"Tree construction\");\n\n    }\n    tree_making_period_conter = tree_making_period_conter + 1;\n\n    double min_dist = 10000000; // init with somthing large\n    int nn_align = 0;\n    int nn_idx = 0;\n    \n    // knn search\n    std::vector<size_t> knn_candidate_indexes( NUM_CANDIDATES_FROM_TREE ); \n    std::vector<float> out_dists_sqr( NUM_CANDIDATES_FROM_TREE );\n\n    TicToc t_tree_search;\n    nanoflann::KNNResultSet<float> knnsearch_result( NUM_CANDIDATES_FROM_TREE );\n    knnsearch_result.init( &knn_candidate_indexes[0], &out_dists_sqr[0] );\n    polarcontext_tree_->index->findNeighbors( knnsearch_result, &curr_key[0] /* query */, nanoflann::SearchParams(10) ); \n    t_tree_search.toc(\"Tree search\");\n\n    /* \n     *  step 2: pairwise distance (find optimal columnwise best-fit using cosine distance)\n     */\n    TicToc t_calc_dist;\n    for ( int iter_idx = 0; iter_idx < NUM_CANDIDATES_FROM_TREE; iter_idx++ )\n    {\n        if (knn_candidate_indexes[iter_idx] > candidate_keyframe_indices.size()-1){  // To avoid out-of-range knn indice\n            ROS_INFO(\"To skip out-of-range knn indice: %ld\",knn_candidate_indexes[iter_idx]);\n            continue;\n        }\n        MatrixXd polarcontext_candidate = polarcontexts_[candidate_keyframe_indices[knn_candidate_indexes[iter_idx]]];\n        std::pair<double, int> sc_dist_result = distanceBtnScanContext( curr_desc, polarcontext_candidate ); \n        double candidate_dist = sc_dist_result.first;\n        int candidate_align = sc_dist_result.second;\n\n        if( candidate_dist < min_dist )\n        {\n            min_dist = candidate_dist;\n            nn_align = candidate_align;\n\n            nn_idx = candidate_keyframe_indices[knn_candidate_indexes[iter_idx]];\n        }\n    }\n    t_calc_dist.toc(\"Distance calc\");\n\n    /* \n     * loop threshold check\n     */\n    if( min_dist < SC_DIST_THRES )\n    {\n        loop_id = nn_idx; \n        // std::cout.precision(3);\n        cout << \"\\033[32m [Loop found] Nearest SC distance: \" << min_dist << \" between \" << new_keyframe->index << \" and \" << nn_idx << \". \\033[0m\" << endl;\n        cout << \"\\033[32m [Loop found] yaw diff: \" << nn_align * PC_UNIT_SECTOR_ANGLE << \" deg. \\033[0m\" << endl;\n    }\n    else\n    {\n        std::cout.precision(3);\n        cout << \"\\033[33m [Not loop] Nearest SC distance: \" << min_dist << \" between \" << new_keyframe->index << \" and \" << nn_idx << \". \\033[0m\" << endl;\n        cout << \"\\033[33m [Not loop] yaw diff: \" << nn_align * PC_UNIT_SECTOR_ANGLE << \" deg. \\033[0m\" << endl;\n    }\n\n    // To do: return also nn_align (i.e., yaw diff)\n    float yaw_diff_rad = deg2rad(nn_align * PC_UNIT_SECTOR_ANGLE);\n    std::pair<int, float> result {loop_id, yaw_diff_rad};\n\n    return result;\n\n} // SCManager::detectLoopClosureID\n\n// } // namespace SC2"
  },
  {
    "path": "src/radar_graph_slam/__init__.py",
    "content": ""
  },
  {
    "path": "src/radar_graph_slam/bag_player.py",
    "content": "#!/usr/bin/python\n# SPDX-License-Identifier: BSD-2-Clause\nimport sys\nimport yaml\nimport time\nimport curses\nimport argparse\n\ntry:\n    from StringIO import StringIO ## for Python 2\nexcept ImportError:\n    from io import StringIO ## for Python 3\n\n\nimport rospy\nimport rosbag\nimport roslib\nfrom std_msgs.msg import *\nfrom sensor_msgs.msg import *\nfrom rosgraph_msgs.msg import *\nfrom progressbar import ProgressBar\n\n\nclass BagPlayer:\n\tdef __init__(self, bagfile, start, duration):\n\t\tprint('opening...',)\n\t\tself.bag = rosbag.Bag(bagfile, 'r')\n\t\tprint('done')\n\n\t\tself.message_generator = self.bag.read_messages()\n\n\t\tinfo_dict = yaml.safe_load(self.bag._get_yaml_info())\n\t\tself.duration = float(info_dict['duration'])\n\t\tself.endtime = float(info_dict['end'])\n\n\t\tself.progress = ProgressBar(0, self.duration, term_width=80, fd=StringIO())\n\n\t\tself.publishers = {}\n\t\tfor con in self.bag._get_connections():\n\t\t\tmsg_class = roslib.message.get_message_class(con.datatype)\n\t\t\tself.publishers[con.topic] = rospy.Publisher(con.topic, msg_class, queue_size=256)\n\t\tself.clock_pub = rospy.Publisher('/clock', Clock, queue_size=256)\n\n\t\tself.init_time = None\n\t\tself.start = start\n\t\tself.duration = duration\n\t\tself.fail_count = 0\n\t\tself.time_subs = {}\n\t\tself.target_times = {}\n\t\tself.latest_stamps = {}\n\n\t\tself.play()\n\n\tdef update_time_subs(self):\n\t\tfor topic_name, msg_type in rospy.get_published_topics():\n\t\t\tif 'read_until' in topic_name and 'std_msgs/Header' in msg_type:\n\t\t\t\tif topic_name not in self.time_subs:\n\t\t\t\t\tprint('ADD', topic_name)\n\t\t\t\t\tself.time_subs[topic_name] = rospy.Subscriber(topic_name, Header, self.time_callback, topic_name)\n\n\tdef time_callback(self, header_msg, topic_name):\n\t\tif header_msg.frame_id not in self.target_times:\n\t\t\tself.target_times[header_msg.frame_id] = {}\n\n\t\tif topic_name not in self.target_times[header_msg.frame_id] or self.target_times[header_msg.frame_id][topic_name] < header_msg.stamp:\n\t\t\tself.target_times[header_msg.frame_id][topic_name] = header_msg.stamp\n\n\tdef play_realtime(self, duration):\n\t\ttopic, msg, stamp = next(self.message_generator)\n\t\tstamp_wall = time.time()\n\n\t\tstart_stamp = stamp\n\t\tstart_stamp_wall = stamp_wall\n\n\t\tself.start_stamp = start_stamp\n\n\t\twhile not rospy.is_shutdown() and (stamp - start_stamp) < duration:\n\t\t\tstamp_wall = time.time()\n\t\t\telapsed_stamp = stamp - start_stamp\n\t\t\tif (stamp_wall - start_stamp_wall) < (elapsed_stamp.secs + elapsed_stamp.nsecs * 1e-9):\n\t\t\t\ttime.sleep(1e-6)\n\t\t\t\tself.update_time_subs()\n\t\t\t\tcontinue\n\n\t\t\tclock_msg = Clock()\n\t\t\tclock_msg.clock = stamp\n\n\t\t\tif self.init_time is None:\n\t\t\t\tself.init_time = stamp\n\n\t\t\tif self.start and (stamp - self.init_time) < rospy.Duration(float(self.start)):\n\t\t\t\tstart_stamp = stamp\n\t\t\telse:\n\t\t\t\tself.clock_pub.publish(clock_msg)\n\t\t\t\tself.publishers[topic].publish(msg)\n\n\t\t\ttopic, msg, stamp = next(self.message_generator)\n\n\t\treturn topic, msg, stamp\n\n\tdef print_progress(self, stamp):\n\t\tself.stdscr.clear()\n\t\tself.stdscr.addstr(0, 0, 'topic')\n\t\tself.stdscr.addstr(0, 25, 'time')\n\n\t\tline = 1\n\t\tfor target in self.target_times:\n\t\t\tif target not in self.publishers:\n\t\t\t\tcontinue\n\n\t\t\tfor sub_name in self.target_times[target]:\n\t\t\t\ttarget_time = self.target_times[target][sub_name]\n\t\t\t\tself.stdscr.addstr(line, 0, sub_name[:-11])\n\t\t\t\tself.stdscr.addstr(line, 25, '%.6f' % (target_time.secs + target_time.nsecs * 1e-9))\n\n\t\t\t\tresidual = target_time - self.latest_stamps[target].stamp\n\n\t\t\t\tcolor = 1 if residual.to_sec() > 0.0 else 2\n\t\t\t\tself.stdscr.addstr(line, 50, '%.5f' % residual.to_sec(), curses.color_pair(color))\n\t\t\t\tline += 1\n\n\t\tif not hasattr(self, 'prev_stamp'):\n\t\t\tself.prev_stamp = stamp\n\t\t\tself.prev_stamp_wall = time.time()\n\t\t\tself.processing_speed = 1.0\n\t\telif time.time() - self.prev_stamp_wall > 1.0:\n\t\t\tsim_duration = (stamp - self.prev_stamp).to_sec()\n\t\t\twall_duration = time.time() - self.prev_stamp_wall\n\t\t\tself.processing_speed = sim_duration / wall_duration\n\n\t\tself.stdscr.addstr(line, 0, 'current_stamp')\n\t\tself.stdscr.addstr(line, 25, '%.6f' % stamp.to_sec())\n\t\tself.stdscr.addstr(line, 50, '(x%.2f)' % self.processing_speed)\n\n\t\telapsed = (stamp - self.start_stamp).to_sec()\n\t\tself.progress.fd = StringIO()\n\t\ttry:\n\t\t\tself.progress.update(elapsed)\n\t\texcept:\n\t\t\t# nothing to do\n\t\t\tpass\n\t\tself.stdscr.addstr(line + 1, 0, '----------')\n\t\tself.stdscr.addstr(line + 2, 0, self.progress.fd.getvalue())\n\n\t\tself.stdscr.refresh()\n\n\tdef check_stamp(self, topic, msg):\n\t\tif topic not in self.target_times:\n\t\t\treturn True\n\n\t\tif self.fail_count > 10:\n\t\t\tself.fail_count = 0\n\t\t\treturn True\n\n\t\ttarget_time_map = self.target_times[topic]\n\t\tfor sub_name in target_time_map:\n\t\t\tself.latest_stamps[topic] = msg.header\n\t\t\tif msg.header.stamp > target_time_map[sub_name]:\n\t\t\t\tself.fail_count += 1\n\t\t\t\treturn False\n\n\t\tself.fail_count = 0\n\t\treturn True\n\n\tdef play(self):\n\t\tprint('play realtime for 3.0[sec]')\n\t\ttopic, msg, stamp = self.play_realtime(rospy.Duration(5.0))\n\t\tself.update_time_subs()\n\n\t\tprint('play as fast as possible')\n\t\tself.stdscr = curses.initscr()\n\t\tcurses.start_color()\n\t\tcurses.noecho()\n\n\t\tcurses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_WHITE)\n\t\tcurses.init_pair(2, curses.COLOR_RED, curses.COLOR_WHITE)\n\n\t\ttry:\n\t\t\twhile not rospy.is_shutdown():\n\t\t\t\tif not self.check_stamp(topic, msg):\n\t\t\t\t\tself.update_time_subs()\n\t\t\t\t\tself.print_progress(stamp)\n\t\t\t\t\ttime.sleep(0.1)\n\t\t\t\t\tcontinue\n\n\t\t\t\tclock_msg = Clock()\n\t\t\t\tclock_msg.clock = stamp\n\n\t\t\t\tif self.duration:\n\t\t\t\t\tif (stamp - self.init_time) > rospy.Duration(float(self.duration)):\n\t\t\t\t\t\tbreak\n\n\t\t\t\tself.clock_pub.publish(clock_msg)\n\t\t\t\tself.publishers[topic].publish(msg)\n\t\t\t\ttopic, msg, stamp = next(self.message_generator)\n\t\texcept:\n\t\t\tprint(sys.exc_info()[0])\n\t\t\tclock_msg = Clock()\n\t\t\tclock_msg.clock = stamp + rospy.Duration(30.0)\n\t\t\tself.clock_pub.publish(clock_msg)\n\t\t\ttime.sleep(0.5)\n\n\t\tcurses.echo()\n\t\tcurses.endwin()\n\n\ndef main():\n\tmyargv = rospy.myargv(sys.argv)\n\tparser = argparse.ArgumentParser()\n\tparser.add_argument('input_bag', help='bag file to be played')\n\tparser.add_argument('-s', '--start', help='start sec seconds into the bag')\n\tparser.add_argument('-u', '--duration', help='play only sec seconds into the bag')\n\targs = parser.parse_args(myargv[1:])\n\n\tif len(sys.argv) < 2:\n\t\tprint('usage bag_player src_bagname')\n\t\treturn\n\n\trospy.init_node('bag_player')\n\tBagPlayer(args.input_bag, args.start, args.duration)\n\nif __name__ == '__main__':\n\tmain()\n"
  },
  {
    "path": "src/radar_graph_slam/ford2bag.py",
    "content": "#!/usr/bin/python\n# SPDX-License-Identifier: BSD-2-Clause\nimport re\nimport os\nimport sys\nimport struct\nimport numpy\nimport scipy.io\n\nimport rospy\nimport rosbag\nimport progressbar\nimport sensor_msgs.point_cloud2 as pc2\n\nfrom sensor_msgs.msg import NavSatFix, NavSatStatus, PointCloud2\nfrom geographic_msgs.msg import GeoPointStamped\n\n\ndef gps2navsat(filename, bag):\n\twith open(filename, 'r') as file:\n\t\ttry:\n\t\t\twhile True:\n\t\t\t\tdata = struct.unpack('qddd', file.read(8*4))\n\t\t\t\ttime = data[0]\n\t\t\t\tlocal = data[1:]\n\t\t\t\tlat_lon_el_theta = struct.unpack('dddd', file.read(8*4))\n\t\t\t\tgps_cov = struct.unpack('dddddddddddddddd', file.read(8*16))\n\n\t\t\t\tif abs(lat_lon_el_theta[0]) < 1e-1:\n\t\t\t\t\tcontinue\n\n\t\t\t\tnavsat = NavSatFix()\n\t\t\t\tnavsat.header.frame_id = 'gps'\n\t\t\t\tnavsat.header.stamp = rospy.Time.from_sec(time * 1e-6)\n\t\t\t\tnavsat.status.status = NavSatStatus.STATUS_FIX\n\t\t\t\tnavsat.status.service = NavSatStatus.SERVICE_GPS\n\n\t\t\t\tnavsat.latitude = lat_lon_el_theta[0]\n\t\t\t\tnavsat.longitude = lat_lon_el_theta[1]\n\t\t\t\tnavsat.altitude = lat_lon_el_theta[2]\n\n\t\t\t\tnavsat.position_covariance = numpy.array(gps_cov).reshape(4, 4)[:3, :3].flatten().tolist()\n\t\t\t\tnavsat.position_covariance_type = NavSatFix.COVARIANCE_TYPE_KNOWN\n\n\t\t\t\tbag.write('/gps/fix', navsat, navsat.header.stamp)\n\n\t\t\t\tgeopoint = GeoPointStamped()\n\t\t\t\tgeopoint.header = navsat.header\n\t\t\t\tgeopoint.position.latitude = lat_lon_el_theta[0]\n\t\t\t\tgeopoint.position.longitude = lat_lon_el_theta[1]\n\t\t\t\tgeopoint.position.altitude = lat_lon_el_theta[2]\n\n\t\t\t\tbag.write('/gps/geopoint', geopoint, geopoint.header.stamp)\n\n\t\texcept:\n\t\t\tprint 'done'\n\n\ndef mat2pointcloud(filename):\n\tm = scipy.io.loadmat(filename)\n\tscan = numpy.transpose(m['SCAN']['XYZ'][0][0]).astype(numpy.float32)\n\tstamp = m['SCAN']['timestamp_laser'][0][0][0][0] * 1e-6\n\n\tcloud = PointCloud2()\n\tcloud.header.stamp = rospy.Time.from_sec(stamp)\n\tcloud.header.frame_id = 'velodyne'\n\tcloud = pc2.create_cloud_xyz32(cloud.header, scan)\n\treturn cloud\n\n\ndef main():\n\tif len(sys.argv) < 2:\n\t\tprint 'usage: ford2bag.py src_dirname output_filename'\n\n\toutput_filename = sys.argv[1]\n\n\trospy.init_node('bag')\n\tfilenames = sorted(['SCANS/' + x for x in os.listdir('SCANS') if re.match('Scan[0-9]*\\.mat', x)])\n\tprint filenames\n\n\tprogress = progressbar.ProgressBar(max_value=len(filenames))\n\tpub = rospy.Publisher('/velodyne_points', PointCloud2, queue_size=32)\n\twith rosbag.Bag(output_filename, 'w') as bag:\n\t\tgps2navsat('GPS.log', bag)\n\t\tfor i, filename in enumerate(filenames):\n\t\t\tif rospy.is_shutdown():\n\t\t\t\tbreak\n\t\t\tprogress.update(i)\n\t\t\tcloud = mat2pointcloud(filename)\n\t\t\tif pub.get_num_connections():\n\t\t\t\tpub.publish(cloud)\n\t\t\tbag.write('/velodyne_points', cloud, cloud.header.stamp)\n\n\nif __name__ == '__main__':\n\tmain()\n"
  },
  {
    "path": "src/radar_graph_slam/graph_slam.cpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#include <radar_graph_slam/graph_slam.hpp>\n\n#include <boost/format.hpp>\n#include <g2o/stuff/macros.h>\n#include <g2o/core/factory.h>\n#include <g2o/core/block_solver.h>\n#include <g2o/core/linear_solver.h>\n#include <g2o/core/sparse_optimizer.h>\n#include <g2o/core/robust_kernel_factory.h>\n#include <g2o/core/optimization_algorithm.h>\n#include <g2o/core/optimization_algorithm_factory.h>\n#include <g2o/solvers/pcg/linear_solver_pcg.h>\n#include <g2o/types/slam3d/types_slam3d.h>\n#include <g2o/types/slam3d/edge_se3_pointxyz.h>\n#include <g2o/types/slam3d_addons/types_slam3d_addons.h>\n#include <g2o/edge_se3_plane.hpp>\n#include <g2o/edge_se3_priorxy.hpp>\n#include <g2o/edge_se3_priorxyz.hpp>\n#include <g2o/edge_se3_priorz.hpp>\n#include <g2o/edge_se3_z.hpp>\n#include <g2o/edge_se3_se3.hpp>\n#include <g2o/edge_se3_priorvec.hpp>\n#include <g2o/edge_se3_priorquat.hpp>\n#include <g2o/edge_plane_prior.hpp>\n#include <g2o/edge_plane_identity.hpp>\n#include <g2o/edge_plane_parallel.hpp>\n#include <g2o/robust_kernel_io.hpp>\n#include <g2o/edge_se3_gt_utm.hpp>\n\nG2O_USE_OPTIMIZATION_LIBRARY(pcg)\nG2O_USE_OPTIMIZATION_LIBRARY(cholmod)  // be aware of that cholmod brings GPL dependency\nG2O_USE_OPTIMIZATION_LIBRARY(csparse)  // be aware of that csparse brings LGPL unless it is dynamically linked\n\nnamespace g2o {\nG2O_REGISTER_TYPE(EDGE_SE3_PLANE, EdgeSE3Plane)\nG2O_REGISTER_TYPE(EDGE_SE3_PRIORXY, EdgeSE3PriorXY)\nG2O_REGISTER_TYPE(EDGE_SE3_PRIORXYZ, EdgeSE3PriorXYZ)\nG2O_REGISTER_TYPE(EDGE_SE3_PRIORZ, EdgeSE3PriorZ)\nG2O_REGISTER_TYPE(EDGE_SE3_Z, EdgeSE3Z)\nG2O_REGISTER_TYPE(EDGE_SE3_SE3, EdgeSE3SE3)\nG2O_REGISTER_TYPE(EDGE_SE3_PRIORVEC, EdgeSE3PriorVec)\nG2O_REGISTER_TYPE(EDGE_SE3_PRIORQUAT, EdgeSE3PriorQuat)\nG2O_REGISTER_TYPE(EDGE_PLANE_PRIOR_NORMAL, EdgePlanePriorNormal)\nG2O_REGISTER_TYPE(EDGE_PLANE_PRIOR_DISTANCE, EdgePlanePriorDistance)\nG2O_REGISTER_TYPE(EDGE_PLANE_IDENTITY, EdgePlaneIdentity)\nG2O_REGISTER_TYPE(EDGE_PLANE_PARALLEL, EdgePlaneParallel)\nG2O_REGISTER_TYPE(EDGE_PLANE_PAERPENDICULAR, EdgePlanePerpendicular)\nG2O_REGISTER_TYPE(EDGE_SE3_GTUTM, EdgeSE3GtUTM)\n}  // namespace g2o\n\nnamespace radar_graph_slam {\n\n/**\n * @brief constructor\n */\nGraphSLAM::GraphSLAM(const std::string& solver_type) {\n  graph.reset(new g2o::SparseOptimizer());\n  g2o::SparseOptimizer* graph = dynamic_cast<g2o::SparseOptimizer*>(this->graph.get());\n\n  std::cout << \"construct solver: \" << solver_type << std::endl;\n  g2o::OptimizationAlgorithmFactory* solver_factory = g2o::OptimizationAlgorithmFactory::instance();\n  g2o::OptimizationAlgorithmProperty solver_property;\n  g2o::OptimizationAlgorithm* solver = solver_factory->construct(solver_type, solver_property);\n  graph->setAlgorithm(solver);\n\n  if(!graph->solver()) {\n    std::cerr << std::endl;\n    std::cerr << \"error : failed to allocate solver!!\" << std::endl;\n    solver_factory->listSolvers(std::cerr);\n    std::cerr << \"-------------\" << std::endl;\n    std::cin.ignore(1);\n    return;\n  }\n  std::cout << \"done\" << std::endl;\n\n  robust_kernel_factory = g2o::RobustKernelFactory::instance();\n}\n\n/**\n * @brief destructor\n */\nGraphSLAM::~GraphSLAM() {\n  graph.reset();\n}\n\nvoid GraphSLAM::set_solver(const std::string& solver_type) {\n  g2o::SparseOptimizer* graph = dynamic_cast<g2o::SparseOptimizer*>(this->graph.get());\n\n  std::cout << \"construct solver: \" << solver_type << std::endl;\n  g2o::OptimizationAlgorithmFactory* solver_factory = g2o::OptimizationAlgorithmFactory::instance();\n  g2o::OptimizationAlgorithmProperty solver_property;\n  g2o::OptimizationAlgorithm* solver = solver_factory->construct(solver_type, solver_property);\n  graph->setAlgorithm(solver);\n\n  if(!graph->solver()) {\n    std::cerr << std::endl;\n    std::cerr << \"error : failed to allocate solver!!\" << std::endl;\n    solver_factory->listSolvers(std::cerr);\n    std::cerr << \"-------------\" << std::endl;\n    std::cin.ignore(1);\n    return;\n  }\n  std::cout << \"done\" << std::endl;\n}\n\nint GraphSLAM::num_vertices() const {\n  return graph->vertices().size();\n}\nint GraphSLAM::num_edges() const {\n  return graph->edges().size();\n}\n\ng2o::VertexSE3* GraphSLAM::add_se3_node(const Eigen::Isometry3d& pose) {\n  g2o::VertexSE3* vertex(new g2o::VertexSE3());\n  vertex->setId(static_cast<int>(graph->vertices().size()));\n  vertex->setEstimate(pose);\n  graph->addVertex(vertex);\n\n  return vertex;\n}\n\ng2o::VertexPlane* GraphSLAM::add_plane_node(const Eigen::Vector4d& plane_coeffs) {\n  g2o::VertexPlane* vertex(new g2o::VertexPlane());\n  vertex->setId(static_cast<int>(graph->vertices().size()));\n  vertex->setEstimate(plane_coeffs);\n  graph->addVertex(vertex);\n\n  return vertex;\n}\n\ng2o::VertexPointXYZ* GraphSLAM::add_point_xyz_node(const Eigen::Vector3d& xyz) {\n  g2o::VertexPointXYZ* vertex(new g2o::VertexPointXYZ());\n  vertex->setId(static_cast<int>(graph->vertices().size()));\n  vertex->setEstimate(xyz);\n  graph->addVertex(vertex);\n\n  return vertex;\n}\n\ng2o::EdgeSE3* GraphSLAM::add_se3_edge(g2o::VertexSE3* v1, g2o::VertexSE3* v2, const Eigen::Isometry3d& relative_pose, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3* edge(new g2o::EdgeSE3());\n  edge->setMeasurement(relative_pose);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v1;\n  edge->vertices()[1] = v2;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgeSE3Prior* GraphSLAM::add_se3_prior_edge(g2o::VertexSE3* v_se3, const Eigen::Isometry3d& pose, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3Prior* edge(new g2o::EdgeSE3Prior());\n  edge->setMeasurement(pose);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v_se3;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\n// Information matrix should be 6*6\ng2o::EdgeSE3SE3* GraphSLAM::add_se3_se3_edge(g2o::VertexSE3* v1, g2o::VertexSE3* v2, const g2o::SE3Quat& relative_pose, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3SE3* edge(new g2o::EdgeSE3SE3());\n  edge->setMeasurement(relative_pose);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v1;\n  edge->vertices()[1] = v2;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgeSE3Plane* GraphSLAM::add_se3_plane_edge(g2o::VertexSE3* v_se3, g2o::VertexPlane* v_plane, const Eigen::Vector4d& plane_coeffs, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3Plane* edge(new g2o::EdgeSE3Plane());\n  edge->setMeasurement(plane_coeffs);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v_se3;\n  edge->vertices()[1] = v_plane;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgeSE3PointXYZ* GraphSLAM::add_se3_point_xyz_edge(g2o::VertexSE3* v_se3, g2o::VertexPointXYZ* v_xyz, const Eigen::Vector3d& xyz, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3PointXYZ* edge(new g2o::EdgeSE3PointXYZ());\n  edge->setMeasurement(xyz);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v_se3;\n  edge->vertices()[1] = v_xyz;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgeSE3GtUTM* GraphSLAM::add_se3_gt_utm_edge(g2o::VertexSE3* v_se3, const Eigen::Vector3d& gt_xyz, const Eigen::Vector3d& utm_xyz, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3GtUTM* edge(new g2o::EdgeSE3GtUTM());\n  edge->setMeasurement(gt_xyz);\n  edge->setMeasurement_utm(utm_xyz);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v_se3;\n  graph->addEdge(edge);\n  return edge;\n}\n\ng2o::EdgePlanePriorNormal* GraphSLAM::add_plane_normal_prior_edge(g2o::VertexPlane* v, const Eigen::Vector3d& normal, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgePlanePriorNormal* edge(new g2o::EdgePlanePriorNormal());\n  edge->setMeasurement(normal);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgePlanePriorDistance* GraphSLAM::add_plane_distance_prior_edge(g2o::VertexPlane* v, double distance, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgePlanePriorDistance* edge(new g2o::EdgePlanePriorDistance());\n  edge->setMeasurement(distance);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgeSE3PriorXY* GraphSLAM::add_se3_prior_xy_edge(g2o::VertexSE3* v_se3, const Eigen::Vector2d& xy, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3PriorXY* edge(new g2o::EdgeSE3PriorXY());\n  edge->setMeasurement(xy);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v_se3;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgeSE3PriorXYZ* GraphSLAM::add_se3_prior_xyz_edge(g2o::VertexSE3* v_se3, const Eigen::Vector3d& xyz, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3PriorXYZ* edge(new g2o::EdgeSE3PriorXYZ());\n  edge->setMeasurement(xyz);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v_se3;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgeSE3PriorZ* GraphSLAM::add_se3_prior_z_edge(g2o::VertexSE3* v_se3, const Eigen::Vector1d& z, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3PriorZ* edge(new g2o::EdgeSE3PriorZ());\n  edge->setMeasurement(z);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v_se3;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgeSE3Z* GraphSLAM::add_se3_z_edge(g2o::VertexSE3* v1, g2o::VertexSE3* v2, const Eigen::Vector1d& z, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3Z* edge(new g2o::EdgeSE3Z());\n  edge->setMeasurement(z);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v1;\n  edge->vertices()[1] = v2;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgeSE3PriorVec* GraphSLAM::add_se3_prior_vec_edge(g2o::VertexSE3* v_se3, const Eigen::Vector3d& direction, const Eigen::Vector3d& measurement, const Eigen::MatrixXd& information_matrix) {\n  Eigen::Matrix<double, 6, 1> m;\n  m.head<3>() = direction;\n  m.tail<3>() = measurement;\n\n  g2o::EdgeSE3PriorVec* edge(new g2o::EdgeSE3PriorVec());\n  edge->setMeasurement(m);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v_se3;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgeSE3PriorQuat* GraphSLAM::add_se3_prior_quat_edge(g2o::VertexSE3* v_se3, const Eigen::Quaterniond& quat, const Eigen::MatrixXd& information_matrix) {\n  g2o::EdgeSE3PriorQuat* edge(new g2o::EdgeSE3PriorQuat());\n  edge->setMeasurement(quat);\n  edge->setInformation(information_matrix);\n  edge->vertices()[0] = v_se3;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgePlane* GraphSLAM::add_plane_edge(g2o::VertexPlane* v_plane1, g2o::VertexPlane* v_plane2, const Eigen::Vector4d& measurement, const Eigen::Matrix4d& information) {\n  g2o::EdgePlane* edge(new g2o::EdgePlane());\n  edge->setMeasurement(measurement);\n  edge->setInformation(information);\n  edge->vertices()[0] = v_plane1;\n  edge->vertices()[1] = v_plane2;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgePlaneIdentity* GraphSLAM::add_plane_identity_edge(g2o::VertexPlane* v_plane1, g2o::VertexPlane* v_plane2, const Eigen::Vector4d& measurement, const Eigen::Matrix4d& information) {\n  g2o::EdgePlaneIdentity* edge(new g2o::EdgePlaneIdentity());\n  edge->setMeasurement(measurement);\n  edge->setInformation(information);\n  edge->vertices()[0] = v_plane1;\n  edge->vertices()[1] = v_plane2;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgePlaneParallel* GraphSLAM::add_plane_parallel_edge(g2o::VertexPlane* v_plane1, g2o::VertexPlane* v_plane2, const Eigen::Vector3d& measurement, const Eigen::Matrix3d& information) {\n  g2o::EdgePlaneParallel* edge(new g2o::EdgePlaneParallel());\n  edge->setMeasurement(measurement);\n  edge->setInformation(information);\n  edge->vertices()[0] = v_plane1;\n  edge->vertices()[1] = v_plane2;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\ng2o::EdgePlanePerpendicular* GraphSLAM::add_plane_perpendicular_edge(g2o::VertexPlane* v_plane1, g2o::VertexPlane* v_plane2, const Eigen::Vector3d& measurement, const Eigen::MatrixXd& information) {\n  g2o::EdgePlanePerpendicular* edge(new g2o::EdgePlanePerpendicular());\n  edge->setMeasurement(measurement);\n  edge->setInformation(information);\n  edge->vertices()[0] = v_plane1;\n  edge->vertices()[1] = v_plane2;\n  graph->addEdge(edge);\n\n  return edge;\n}\n\nvoid GraphSLAM::add_robust_kernel(g2o::HyperGraph::Edge* edge, const std::string& kernel_type, double kernel_size) {\n  if(kernel_type == \"NONE\") {\n    return;\n  }\n\n  g2o::RobustKernel* kernel = robust_kernel_factory->construct(kernel_type);\n  if(kernel == nullptr) {\n    std::cerr << \"warning : invalid robust kernel type: \" << kernel_type << std::endl;\n    return;\n  }\n\n  kernel->setDelta(kernel_size);\n\n  g2o::OptimizableGraph::Edge* edge_ = dynamic_cast<g2o::OptimizableGraph::Edge*>(edge);\n  edge_->setRobustKernel(kernel);\n}\n\nint GraphSLAM::optimize(int num_iterations) {\n  g2o::SparseOptimizer* graph = dynamic_cast<g2o::SparseOptimizer*>(this->graph.get());\n  if(graph->edges().size() < 10) {\n    return -1;\n  }\n\n  std::cout << std::endl;\n  std::cout << \"--- pose graph optimization ---\" << std::endl;\n  std::cout << \"nodes: \" << graph->vertices().size() << \"   edges: \" << graph->edges().size() << std::endl;\n  std::cout << \"optimizing... \" << std::flush;\n\n  std::cout << \"init\" << std::endl;\n  graph->initializeOptimization();\n  graph->setVerbose(false);  // Open/Close debug output\n\n  std::cout << \"chi2\" << std::endl;\n  double chi2 = graph->chi2();\n\n  std::cout << \"optimizing!!\" << std::endl;\n  auto t1 = ros::WallTime::now();\n  int iterations = graph->optimize(num_iterations);\n\n  auto t2 = ros::WallTime::now();\n  std::cout << \"done\" << std::endl;\n  std::cout << \"iterations: \" << iterations << \" / \" << num_iterations << std::endl;\n  std::cout << \"chi2: (before)\" << chi2 << \" -> (after)\" << graph->chi2() << std::endl;\n  std::cout << \"time: \" << boost::format(\"%.3f\") % (t2 - t1).toSec() << \"[sec]\" << std::endl;\n\n  return iterations;\n}\n\nvoid GraphSLAM::save(const std::string& filename) {\n  g2o::SparseOptimizer* graph = dynamic_cast<g2o::SparseOptimizer*>(this->graph.get());\n\n  std::ofstream ofs(filename);\n  graph->save(ofs);\n\n  g2o::save_robust_kernels(filename + \".kernels\", graph);\n}\n\nbool GraphSLAM::load(const std::string& filename) {\n  std::cout << \"loading pose graph...\" << std::endl;\n  g2o::SparseOptimizer* graph = dynamic_cast<g2o::SparseOptimizer*>(this->graph.get());\n\n  std::ifstream ifs(filename);\n  if(!graph->load(ifs)) {\n    return false;\n  }\n\n  std::cout << \"nodes  : \" << graph->vertices().size() << std::endl;\n  std::cout << \"edges  : \" << graph->edges().size() << std::endl;\n\n  if(!g2o::load_robust_kernels(filename + \".kernels\", graph)) {\n    return false;\n  }\n\n  return true;\n}\n\n}  // namespace radar_graph_slam\n"
  },
  {
    "path": "src/radar_graph_slam/information_matrix_calculator.cpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#include <iostream>\n\n#include <radar_graph_slam/information_matrix_calculator.hpp>\n\n#include <pcl/search/kdtree.h>\n#include <pcl/common/transforms.h>\n\nusing namespace std;\n\nnamespace radar_graph_slam {\n\nInformationMatrixCalculator::InformationMatrixCalculator(ros::NodeHandle& nh) {\n  use_const_inf_matrix = nh.param<bool>(\"use_const_inf_matrix\", false);\n  const_stddev_x = nh.param<double>(\"const_stddev_x\", 0.5);\n  const_stddev_q = nh.param<double>(\"const_stddev_q\", 0.1);\n\n  var_gain_a = nh.param<double>(\"var_gain_a\", 20.0);\n  min_stddev_x = nh.param<double>(\"min_stddev_x\", 0.1);\n  max_stddev_x = nh.param<double>(\"max_stddev_x\", 5.0);\n  min_stddev_q = nh.param<double>(\"min_stddev_q\", 0.05);\n  max_stddev_q = nh.param<double>(\"max_stddev_q\", 0.2);\n  fitness_score_thresh = nh.param<double>(\"fitness_score_thresh\", 0.5);\n}\n\nInformationMatrixCalculator::~InformationMatrixCalculator() {}\n\nEigen::MatrixXd InformationMatrixCalculator::calc_information_matrix(const pcl::PointCloud<PointT>::ConstPtr& cloud1, const pcl::PointCloud<PointT>::ConstPtr& cloud2, const Eigen::Isometry3d& relpose) const {\n  if(use_const_inf_matrix) {\n    Eigen::MatrixXd inf = Eigen::MatrixXd::Identity(6, 6);\n    inf.topLeftCorner(3, 3).array() /= const_stddev_x;\n    inf.bottomRightCorner(3, 3).array() /= const_stddev_q;\n    return inf;\n  }\n\n  double fitness_score = calc_fitness_score(cloud1, cloud2, relpose);\n\n  double min_var_x = std::pow(min_stddev_x, 2);\n  double max_var_x = std::pow(max_stddev_x, 2);\n  double min_var_q = std::pow(min_stddev_q, 2);\n  double max_var_q = std::pow(max_stddev_q, 2);\n\n  float w_x = weight(var_gain_a, fitness_score_thresh, min_var_x, max_var_x, fitness_score);\n  float w_q = weight(var_gain_a, fitness_score_thresh, min_var_q, max_var_q, fitness_score);\n\n  Eigen::MatrixXd inf = Eigen::MatrixXd::Identity(6, 6);\n  inf.topLeftCorner(3, 3).array() /= w_x;\n  inf.bottomRightCorner(3, 3).array() /= w_q;\n\n  // cout << \"Information Matrix Parameters: \" << inf(1,1) << inf(4,4) << endl;\n  return inf;\n}\n\ndouble InformationMatrixCalculator::calc_fitness_score(const pcl::PointCloud<PointT>::ConstPtr& cloud1, const pcl::PointCloud<PointT>::ConstPtr& cloud2, const Eigen::Isometry3d& relpose, double max_range) {\n  pcl::search::KdTree<PointT>::Ptr tree_(new pcl::search::KdTree<PointT>());\n  tree_->setInputCloud(cloud1);\n\n  double fitness_score = 0.0;\n\n  // Transform the input dataset using the final transformation\n  pcl::PointCloud<PointT> input_transformed;\n  pcl::transformPointCloud(*cloud2, input_transformed, relpose.cast<float>());\n\n  std::vector<int> nn_indices(1);\n  std::vector<float> nn_dists(1);\n\n  // For each point in the source dataset\n  int nr = 0;\n  for(size_t i = 0; i < input_transformed.points.size(); ++i) {\n    // Find its nearest neighbor in the target\n    tree_->nearestKSearch(input_transformed.points[i], 1, nn_indices, nn_dists);\n\n    // Deal with occlusions (incomplete targets)\n    if(nn_dists[0] <= max_range) {\n      // Add to the fitness score\n      fitness_score += nn_dists[0];\n      nr++;\n    }\n  }\n\n  if(nr > 0)\n    return (fitness_score / nr);\n  else\n    return (std::numeric_limits<double>::max());\n}\n\n}  // namespace radar_graph_slam\n"
  },
  {
    "path": "src/radar_graph_slam/keyframe.cpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#include <radar_graph_slam/keyframe.hpp>\n\n#include <boost/filesystem.hpp>\n\n#include <pcl/io/pcd_io.h>\n#include <g2o/core/sparse_optimizer.h>\n#include <g2o/types/slam3d/vertex_se3.h>\n\nnamespace radar_graph_slam {\n\nKeyFrame::KeyFrame(const size_t index, const ros::Time& stamp, const Eigen::Isometry3d& odom_scan2scan, double accum_distance, const pcl::PointCloud<PointT>::ConstPtr& cloud) : \n  index(index), stamp(stamp), odom_scan2scan(odom_scan2scan), accum_distance(accum_distance), cloud(cloud), node(nullptr) {}\n\nKeyFrame::KeyFrame(const std::string& directory, g2o::HyperGraph* graph) : stamp(), odom_scan2scan(Eigen::Isometry3d::Identity()), accum_distance(-1), cloud(nullptr), node(nullptr) {\n  load(directory, graph);\n}\n\nKeyFrame::~KeyFrame() {}\n\nvoid KeyFrame::save(const std::string& directory) {\n  if(!boost::filesystem::is_directory(directory)) {\n    boost::filesystem::create_directory(directory);\n  }\n\n  std::ofstream ofs(directory + \"/data\");\n  ofs << \"stamp \" << stamp.sec << \" \" << stamp.nsec << \"\\n\";\n\n  ofs << \"estimate\\n\";\n  ofs << node->estimate().matrix() << \"\\n\";\n\n  ofs << \"odom_scan2scan\\n\";\n  ofs << odom_scan2scan.matrix() << \"\\n\";\n\n  ofs << \"accum_distance \" << accum_distance << \"\\n\";\n\n  if(floor_coeffs) {\n    ofs << \"floor_coeffs \" << floor_coeffs->transpose() << \"\\n\";\n  }\n\n  if(utm_coord) {\n    ofs << \"utm_coord \" << utm_coord->transpose() << \"\\n\";\n  }\n\n  if(acceleration) {\n    ofs << \"acceleration \" << acceleration->transpose() << \"\\n\";\n  }\n\n  if(orientation) {\n    ofs << \"orientation \" << orientation->w() << \" \" << orientation->x() << \" \" << orientation->y() << \" \" << orientation->z() << \"\\n\";\n  }\n\n  if(node) {\n    ofs << \"id \" << node->id() << \"\\n\";\n  }\n\n  pcl::io::savePCDFileBinary(directory + \"/cloud.pcd\", *cloud);\n}\n\nbool KeyFrame::load(const std::string& directory, g2o::HyperGraph* graph) {\n  std::ifstream ifs(directory + \"/data\");\n  if(!ifs) {\n    return false;\n  }\n\n  long node_id = -1;\n  boost::optional<Eigen::Isometry3d> estimate;\n\n  while(!ifs.eof()) {\n    std::string token;\n    ifs >> token;\n\n    if(token == \"stamp\") {\n      ifs >> stamp.sec >> stamp.nsec;\n    } else if(token == \"estimate\") {\n      Eigen::Matrix4d mat;\n      for(int i = 0; i < 4; i++) {\n        for(int j = 0; j < 4; j++) {\n          ifs >> mat(i, j);\n        }\n      }\n      estimate = Eigen::Isometry3d::Identity();\n      estimate->linear() = mat.block<3, 3>(0, 0);\n      estimate->translation() = mat.block<3, 1>(0, 3);\n    } else if(token == \"odom_scan2scan\") {\n      Eigen::Matrix4d odom_mat = Eigen::Matrix4d::Identity();\n      for(int i = 0; i < 4; i++) {\n        for(int j = 0; j < 4; j++) {\n          ifs >> odom_mat(i, j);\n        }\n      }\n\n      odom_scan2scan.setIdentity();\n      odom_scan2scan.linear() = odom_mat.block<3, 3>(0, 0);\n      odom_scan2scan.translation() = odom_mat.block<3, 1>(0, 3);\n    } else if(token == \"accum_distance\") {\n      ifs >> accum_distance;\n    } else if(token == \"floor_coeffs\") {\n      Eigen::Vector4d coeffs;\n      ifs >> coeffs[0] >> coeffs[1] >> coeffs[2] >> coeffs[3];\n      floor_coeffs = coeffs;\n    } else if(token == \"utm_coord\") {\n      Eigen::Vector3d coord;\n      ifs >> coord[0] >> coord[1] >> coord[2];\n      utm_coord = coord;\n    } else if(token == \"acceleration\") {\n      Eigen::Vector3d acc;\n      ifs >> acc[0] >> acc[1] >> acc[2];\n      acceleration = acc;\n    } else if(token == \"orientation\") {\n      Eigen::Quaterniond quat;\n      ifs >> quat.w() >> quat.x() >> quat.y() >> quat.z();\n      orientation = quat;\n    } else if(token == \"id\") {\n      ifs >> node_id;\n    }\n  }\n\n  if(node_id < 0) {\n    ROS_ERROR_STREAM(\"invalid node id!!\");\n    ROS_ERROR_STREAM(directory);\n    return false;\n  }\n\n  if(graph->vertices().find(node_id) == graph->vertices().end()) {\n    ROS_ERROR_STREAM(\"vertex ID=\" << node_id << \" does not exist!!\");\n    return false;\n  }\n\n  node = dynamic_cast<g2o::VertexSE3*>(graph->vertices()[node_id]);\n  if(node == nullptr) {\n    ROS_ERROR_STREAM(\"failed to downcast!!\");\n    return false;\n  }\n\n  if(estimate) {\n    node->setEstimate(*estimate);\n  }\n\n  pcl::PointCloud<PointT>::Ptr cloud_(new pcl::PointCloud<PointT>());\n  pcl::io::loadPCDFile(directory + \"/cloud.pcd\", *cloud_);\n  cloud = cloud_;\n\n  return true;\n}\n\nlong KeyFrame::id() const {\n  return node->id();\n}\n\nEigen::Isometry3d KeyFrame::estimate() const {\n  return node->estimate();\n}\n\nKeyFrameSnapshot::KeyFrameSnapshot(const Eigen::Isometry3d& pose, const pcl::PointCloud<PointT>::ConstPtr& cloud) : pose(pose), cloud(cloud) {}\n\nKeyFrameSnapshot::KeyFrameSnapshot(const KeyFrame::Ptr& key) : pose(key->node->estimate()), cloud(key->cloud) {}\n\nKeyFrameSnapshot::~KeyFrameSnapshot() {}\n\n}  // namespace radar_graph_slam\n"
  },
  {
    "path": "src/radar_graph_slam/loop_detector.cpp",
    "content": "#include <radar_graph_slam/loop_detector.hpp>\n\nusing namespace std;\n\nEigen::Vector3d R2ypr_(const Eigen::Matrix3d& R) {\n  Eigen::Vector3d n = R.col(0);\n  Eigen::Vector3d o = R.col(1);\n  Eigen::Vector3d a = R.col(2);\n\n  Eigen::Vector3d ypr(3);\n  double y = atan2(n(1), n(0));\n  double p = atan2(-n(2), n(0) * cos(y) + n(1) * sin(y));\n  double r = atan2(a(0) * sin(y) - a(1) * cos(y), -o(0) * sin(y) + o(1) * cos(y));\n  ypr(0) = y;\n  ypr(1) = p;\n  ypr(2) = r;\n\n  return ypr;\n}\n\ndouble limit_value(int value, int min_, int max_){\n    if (value < min_)\n        return min_;\n    else if (value > max_)\n        return max_;\n    else return value;\n}\n\nEigen::Vector3d monoToRainbow(int value){\n    double k = 4.65454545454;\n    double blue = limit_value(- k * (value - 140), 0, 255);\n    double green, red;\n    if (value < 30){\n        green = 0;\n        red = limit_value(- k * (value - 30), 0, 255);\n    }\n    else if (value < 140){\n        green = limit_value(k * (value - 30), 0, 255);\n        red = 0;\n    }\n    else {\n        green = limit_value(- k * (value - 250), 0, 255);\n        red = limit_value(k * (value - 140), 0, 255);\n    }\n    return Eigen::Vector3d(blue, green, red);\n}\n\nnamespace radar_graph_slam {\n\nLoopDetector::LoopDetector(ros::NodeHandle& pnh) {\n    enable_pf = pnh.param<bool>(\"enable_pf\", true);\n    enable_odom_check = pnh.param<bool>(\"enable_odom_check\", true);\n\n    distance_thresh = pnh.param<double>(\"distance_thresh\", 5.0);\n    accum_distance_thresh = pnh.param<double>(\"accum_distance_thresh\", 8.0);\n    min_loop_interval_dist = pnh.param<double>(\"min_loop_interval_dist\", 5.0);\n\n    fitness_score_max_range = pnh.param<double>(\"fitness_score_max_range\", std::numeric_limits<double>::max());\n    fitness_score_thresh = pnh.param<double>(\"fitness_score_thresh\", 0.5);\n\n    odom_drift_xy = pnh.param<double>(\"odometry_drift_xy\", 0.05);\n    odom_drift_z = pnh.param<double>(\"odometry_drift_z\", 0.20);\n    drift_scale_xy = pnh.param<double>(\"odometry_drift_scale_xy\", 2.0);\n    drift_scale_z = pnh.param<double>(\"odometry_drift_scale_z\", 2.0);\n\n    max_baro_difference = pnh.param<double>(\"max_baro_difference\", 3.0);\n    max_yaw_difference = pnh.param<double>(\"max_yaw_difference\", 45.0);\n\n    double sc_dist_thresh = pnh.param<double>(\"sc_dist_thresh\", 0.3);\n    double sc_azimuth_range = pnh.param<double>(\"sc_azimuth_range\", 0.3);\n\n    registration = select_registration_method(pnh);\n\n    // Loop Closure Parameters\n    pnh.param<int>(\"lio_sam/historyKeyframeSearchNum\", historyKeyframeSearchNum, 25);\n    historyKeyframeFitnessScore = pnh.param<float>(\"historyKeyframeFitnessScore\", 6);\n    odom_check_trans_thresh = pnh.param<double>(\"odom_check_trans_thresh\", 0.1);\n    odom_check_rot_thresh = pnh.param<double>(\"odom_check_rot_thresh\", 0.05);\n    pairwise_check_trans_thresh = pnh.param<double>(\"pairwise_check_trans_thresh\", 0.1);\n    pairwise_check_rot_thresh = pnh.param<double>(\"pairwise_check_rot_thresh\", 0.05);\n\n    scManager.reset(new SCManager());\n    inf_calclator.reset(new InformationMatrixCalculator(pnh));\n    image_transport.reset(new image_transport::ImageTransport(pnh));\n    pub_cur_sc = image_transport->advertise(\"/sc/cur\",1);\n    pub_pre_sc = image_transport->advertise(\"/sc/pre\",1);\n\n    scManager->setScDistThresh(sc_dist_thresh);\n    scManager->setAzimuthRange(sc_azimuth_range);\n}\n \nLoopDetector::~LoopDetector() {}\n\n/**\n * @brief detect loops and add them to the pose graph\n * @param keyframes       keyframes\n * @param new_keyframes   newly registered keyframes\n * @param graph_slam      pose graph\n */\nstd::vector<Loop::Ptr> LoopDetector::detect(const std::vector<KeyFrame::Ptr>& keyframes, const std::deque<KeyFrame::Ptr>& new_keyframes, radar_graph_slam::GraphSLAM& graph_slam) {\n    std::vector<Loop::Ptr> detected_loops;\n    for(const auto& new_keyframe : new_keyframes) {\n        std::vector<KeyFrame::Ptr> candidates;\n        Loop::Ptr loop = nullptr;\n        if (enable_pf){\n        start_ms_pf = clock();\n        candidates = find_candidates(keyframes, new_keyframe);\n        end_ms_pf = clock();\n        }\n        else{\n        for (auto kf:keyframes){\n            if (new_keyframe->accum_distance > kf->accum_distance + distance_thresh);\n            candidates.push_back(kf);\n        }\n        }\n        loop = performScanContextLoopClosure(keyframes, candidates, new_keyframe); // 找与new_keyframe匹配的SC\n        // auto loop = matching(candidates, new_keyframe);\n        if(loop) {\n        detected_loops.push_back(loop);\n        double time_used = double(end_ms_pf - start_ms_pf) / CLOCKS_PER_SEC;\n        pf_time.push_back(time_used);\n        time_used = double(end_ms_sc - start_ms_sc) / CLOCKS_PER_SEC;\n        sc_time.push_back(time_used);\n        time_used = double(end_ms_oc - start_ms_oc) / CLOCKS_PER_SEC;\n        oc_time.push_back(time_used);\n        }\n    }\n\n    return detected_loops;\n}\n\n\n/**\n * @brief find loop candidates. A detected loop begins at one of #keyframes and ends at #new_keyframe\n * @param keyframes      candidate keyframes of loop start\n * @param new_keyframe   loop end keyframe\n * @return loop candidates\n */\nstd::vector<KeyFrame::Ptr> LoopDetector::find_candidates(const std::vector<KeyFrame::Ptr>& keyframes, const KeyFrame::Ptr& new_keyframe) const {\n    // 1. too close to the last registered loop edge\n    double dist_btn_last_loop_edge = new_keyframe->accum_distance - last_loop_edge_accum_distance;\n    if(dist_btn_last_loop_edge < min_loop_interval_dist) {\n        return std::vector<KeyFrame::Ptr>();\n    }\n    // Pick loop candicates considering distance and barometer measurements\n    std::vector<KeyFrame::Ptr> candidates;\n    candidates.reserve(32);\n\n    for(const auto& k : keyframes) {\n        // 2. traveled distance between keyframes is too small\n        double accum_distance = new_keyframe->accum_distance - k->accum_distance;\n        if(accum_distance < accum_distance_thresh) {\n        continue; \n        }\n        // 3. If keyframes_ have similar barometer measurements // value\n        if (k->altitude.is_initialized()){\n        if(fabs(k->altitude.value()[0] - new_keyframe->altitude.value()[0]) > max_baro_difference)\n            continue;\n        }\n        // 4. yaw angle difference\n        Eigen::Matrix4d T = k->node->estimate().matrix().inverse() * new_keyframe->node->estimate().matrix();\n        Eigen::Vector3d ypr_diff = R2ypr_(T.block<3,3>(0,0));\n        if (abs(ypr_diff(0) * 180/M_PI) > max_yaw_difference)\n            continue;\n        // 5. estimated distance between new-keyframe is too Large\n        Eigen::Vector3d pos_between = T.block<3,1>(0,3);\n        double dist = pos_between.norm(); // measure x-y distance\n        double x_diff = pos_between(0), y_diff = pos_between(1), z_diff = pos_between(2);\n        double rad_xy_loop = 3 + dist_btn_last_loop_edge * odom_drift_xy * drift_scale_xy;\n        double rad_z_loop = 3 + dist_btn_last_loop_edge * odom_drift_z * drift_scale_z;\n        double aa_lle = pow((x_diff/rad_xy_loop),2) + pow((y_diff/rad_xy_loop),2);\n        if (aa_lle > 1) continue;\n        // cout << \"last_loop_edge_index: \" << last_loop_edge_index << \" new_keyframe: \" << new_keyframe->index\n        //         << \" dist_btn_last_loop_edge = \" << dist_btn_last_loop_edge << endl;\n        // cout << \"rad_xy_loop \" << rad_xy_loop << endl;\n        // cout << \"pos_btn: \" << pos_between(0) << \" \" << pos_between(1) << \" \" << pos_between(2) << endl;\n        // cout << \" aa_lle = \" << aa_lle << endl << endl;\n        \n        double rad_xy = 10.0 + odom_drift_xy * accum_distance * drift_scale_xy; \n        double rad_z = 10.0 + odom_drift_z * accum_distance * drift_scale_z;\n        double aa = pow((x_diff/rad_xy),2)+pow((y_diff/rad_xy),2);//+pow((z_diff/rad_z),2);\n        if(aa > 1)  // candidate keyframe pose is out of elipse  (dist > distance_thresh)\n        continue;\n\n        candidates.push_back(k);\n    }\n\n    return candidates;\n}\n\n\nLoop::Ptr LoopDetector::performScanContextLoopClosure(const std::vector<KeyFrame::Ptr>& keyframes, const std::vector<KeyFrame::Ptr>& candidate_keyframes, const KeyFrame::Ptr& new_keyframe)\n{\n    start_ms_sc = clock();\n    if (candidate_keyframes.empty() == true){\n        return nullptr;\n        // ROS_INFO(\"\\033[33m Cannot find Loop Candidate \\033[0m\");\n    }\n    std::cout << \"\\033[36m\" << \" New keyframe index: \" << new_keyframe->index << \". \" \n    << \"Number of candidate frames: \" << candidate_keyframes.size();\n    // << \"Candidate frames index: \";\n    // for (auto& ckf : candidate_keyframes){\n    //   std::cout << ckf->index << \" \";\n    // }\n    std::cout << \"\\033[0m\" << endl;\n\n    // Detect keys, first: nn index, second: yaw diff\n    auto detectResult = scManager->detectLoopClosureID(candidate_keyframes, new_keyframe);\n    int loopKeyCur = new_keyframe->index;\n    int loopKeyPre = detectResult.first;\n    float yawDiffRad = detectResult.second; // not use for v1 (because pcl icp withi initial somthing wrong...)\n    if( loopKeyPre == -1 )  return nullptr; /* No loop found */\n\n    // extract cloud\n    pcl::PointCloud<PointT>::Ptr cureKeyframeCloud(new pcl::PointCloud<PointT>());\n    pcl::PointCloud<PointT>::Ptr prevKeyframeCloud(new pcl::PointCloud<PointT>());\n    {\n        *cureKeyframeCloud += *new_keyframe->cloud; // Because new_keyframe is not in keyframes\n        *prevKeyframeCloud += *keyframes[loopKeyPre]->cloud;\n    }\n\n    registration->setInputSource(cureKeyframeCloud);\n    registration->setInputTarget(prevKeyframeCloud);\n    pcl::PointCloud<PointT>::Ptr unused_result(new pcl::PointCloud<PointT>());\n    registration->align(*unused_result);\n    // TODO icp align with initial \n    end_ms_sc = clock();\n\n    if (registration->hasConverged() == false || registration->getFitnessScore() > historyKeyframeFitnessScore) {\n        bool converged = registration->hasConverged();\n        std::cout << \"\\033[31m\" << \"ICP fitness test failed. ICP convergence: \" << converged << \" (\" << registration->getFitnessScore() << \" > \" << historyKeyframeFitnessScore << \"). Reject this SC loop.\" << \"\\033[0m\" << std::endl;\n        return nullptr;\n    }\n    // Get pose transformation\n    Eigen::Matrix4f correctionLidarFrame;\n    correctionLidarFrame = registration->getFinalTransformation();\n\n    Eigen::Isometry3d poseFrom(Eigen::Isometry3d::Identity());\n    Eigen::Isometry3d poseTo(Eigen::Isometry3d::Identity());\n    Eigen::Vector3d vec = Eigen::Vector3d(correctionLidarFrame(0,3),correctionLidarFrame(1,3),correctionLidarFrame(2,3));\n    Eigen::Matrix3d mat = correctionLidarFrame.block(0,0,2,2).cast<double>();\n    poseFrom.prerotate(mat); // Set rotation\n    poseFrom.pretranslate(vec);\n    Eigen::Isometry3d T_lc_ij = poseFrom.inverse() * poseTo;\n    \n    /****** Incremental Consistent Measurement Set Maximization ******/\n    // Odometry check (source_frame<->target_frame and source_frame<->last_loop_edge)\n    start_ms_oc = clock();\n    if (enable_odom_check){\n        // source_frame <-> target_frame\n        Eigen::Isometry3d T_odom_ji = new_keyframe->odom_scan2scan.inverse() * keyframes[loopKeyPre]->odom_scan2scan;\n        Eigen::Isometry3d T_err_ij = T_lc_ij * T_odom_ji;\n        int num_between = loopKeyCur - loopKeyPre;// the number of edges along the loop\n        Eigen::AngleAxisd rotation_vector;\n        rotation_vector.fromRotationMatrix(T_err_ij.rotation());\n        double oc_err_trans = T_err_ij.translation().norm() / num_between;\n        double oc_err_rot = rotation_vector.angle() / num_between;\n        // double err_rot = std::acos(Eigen::Quaterniond(T_err_ij.rotation()).w())/num_between;\n        // cout << oc_err_rot << \" \" << err_rot << endl;\n        if(oc_err_trans > odom_check_trans_thresh || oc_err_rot > odom_check_rot_thresh){\n        cout << \"\\033[31m Odometry Check invalid, Translation Error: \" << oc_err_trans \n                << \", rotation Error: \" << oc_err_rot << \" \\033[0m\" << endl;\n        return nullptr;\n        }\n        // source_frame <-> last_loop_edge\n\n    }\n    end_ms_oc = clock();\n    \n    if (!loopIndexQueue.empty()){\n        for (size_t i = loopIndexQueue.size()-1; i < loopIndexQueue.size(); i++) {\n            // Pairwise Consistency check\n#if 0\n            // Eigen::Isometry3d T_odom_jl = Eigen::Isometry3d( keyframes[loopIndexQueue.at(i).second]->node->estimate().matrix().inverse() * new_keyframe->node->estimate().matrix());\n            Eigen::Isometry3d T_odom_jl = keyframes[loopIndexQueue.at(i).second]->odom_scan2scan.inverse() * new_keyframe->odom_scan2scan;\n            Eigen::Isometry3d T_lc_lk = loopPoseQueue.at(i);\n            // Eigen::Isometry3d T_odom_ki = Eigen::Isometry3d( keyframes[loopKeyPre]->node->estimate().matrix().inverse() * keyframes[loopIndexQueue.at(i).first]->node->estimate().matrix());\n            Eigen::Isometry3d T_odom_ki = keyframes[loopKeyPre]->odom_scan2scan.inverse() * keyframes[loopIndexQueue.at(i).first]->odom_scan2scan;\n            Eigen::Isometry3d T_err_ij_kl = T_lc_ij * T_odom_jl * T_lc_lk * T_odom_ki;\n#endif\n            Eigen::Isometry3d T_odom_li = keyframes[loopKeyPre]->odom_scan2scan.inverse() * keyframes[loopIndexQueue.at(i).second]->odom_scan2scan;\n            Eigen::Isometry3d T_lc_kl = loopPoseQueue.at(i).inverse();\n            Eigen::Isometry3d T_odom_jk = keyframes[loopIndexQueue.at(i).first]->odom_scan2scan.inverse() * new_keyframe->odom_scan2scan;\n            Eigen::Isometry3d T_err_ij_kl = T_lc_ij * T_odom_li * T_lc_kl * T_odom_jk;\n\n            Eigen::AngleAxisd rotation_vector;\n            rotation_vector.fromRotationMatrix(T_err_ij_kl.rotation());\n            // int num_between = loopKeyCur - loopKeyPre - (loopIndexQueue.at(i).first - loopIndexQueue.at(i).second);\n            double pcc_err_trans = T_err_ij_kl.translation().norm();\n            cout << \"T_err_ij_kl translation: \" << T_err_ij_kl.translation().norm() << endl;\n            double pcc_err_rot = rotation_vector.angle();\n            if(pcc_err_trans > pairwise_check_trans_thresh || pcc_err_rot > pairwise_check_rot_thresh){\n            ROS_INFO(\"\\033[31m Pairwise check invalid, Translation Error: %lf, rotation Error: %lf \\033[0m\", pcc_err_trans, pcc_err_rot);\n            return nullptr;\n            }\n        }\n    }\n    \n    std::cout << \"\\033[32m\" << \" Add this ScanContext loop. ICP fitness test passed ( Fitness Score \" << registration->getFitnessScore() << \" < \" << historyKeyframeFitnessScore << \"). \" << \"\\033[0m\" << std::endl;\n\n    // Publish context as image\n    Eigen::MatrixXd& cur_sc = scManager->polarcontexts_.at(loopKeyCur);\n    Eigen::MatrixXd& pre_sc = scManager->polarcontexts_.at(loopKeyPre);\n    cv::Mat mat_cur_sc = makeSCImage(cur_sc);\n    cv::Mat color_cur_sc = getColorImage(mat_cur_sc);\n    cv::Mat mat_pre_sc = makeSCImage(pre_sc);\n    cv::Mat color_pre_sc = getColorImage(mat_pre_sc);\n\n    sensor_msgs::ImagePtr cur_img_msg=cv_bridge::CvImage(std_msgs::Header(),\"bgr8\",color_cur_sc).toImageMsg();\n    pub_cur_sc.publish(cur_img_msg);\n    sensor_msgs::ImagePtr pre_img_msg=cv_bridge::CvImage(std_msgs::Header(),\"bgr8\",color_pre_sc).toImageMsg();\n    pub_pre_sc.publish(pre_img_msg);\n\n    // robust kernel for a SC loop\n    Eigen::MatrixXd information = inf_calclator->calc_information_matrix(cureKeyframeCloud, prevKeyframeCloud, T_lc_ij);\n\n    if(new_keyframe->accum_distance > last_loop_edge_accum_distance){\n        last_loop_edge_accum_distance = new_keyframe->accum_distance;\n        last_loop_edge_index = new_keyframe->index;\n    }\n\n    // Add pose constraint\n    mtx.lock();\n    loopIndexQueue.push_back(make_pair(loopKeyCur, loopKeyPre));\n    loopPoseQueue.push_back(T_lc_ij);\n    loopInfoQueue.push_back(information);\n    mtx.unlock();\n    // Add loop constriant\n    loopIndexContainer.insert(std::pair<int, int>(loopKeyCur, loopKeyPre)); // giseop for multimap\n\n    return std::make_shared<Loop>(new_keyframe, keyframes[loopKeyPre], T_lc_ij.matrix().cast<float>());\n}\n\ncv::Mat LoopDetector::makeSCImage(Eigen::MatrixXd src_mat)\n{\n    // double max_ = src_mat.rowwise().maxCoeff().colwise().maxCoeff().value();\n    // double min_ = src_mat.rowwise().minCoeff().colwise().minCoeff().value();\n    double max_ = 35;\n    double min_ = 0;\n    cv::Mat ssc = cv::Mat::zeros(cv::Size(scManager->PC_NUM_SECTOR, scManager->PC_NUM_RING), CV_8U);\n    for (int i = 0; i < scManager->PC_NUM_SECTOR; i++) { // 60\n        for (int j = 0; j < scManager->PC_NUM_RING; j++) { // 20\n            double& d = src_mat(j,i);\n            int uc = round((d - min_) / (max_ - min_) * 255);\n            ssc.at<uchar>(j, i) = uc;\n        }\n    }\n    return ssc;\n}\n\ncv::Mat LoopDetector::getColorImage(cv::Mat &desc)\n{\n    cv::Mat out = cv::Mat::zeros(desc.size(), CV_8UC3);\n    for (int i = 0; i < desc.rows; ++i)\n    {\n        for (int j = 0; j < desc.cols; ++j)\n        {\n            int value_mono = (int)desc.at<uchar>(i, j);//std::get<2>(_argmax_to_rgb[(int)desc.at<uchar>(i, j)]);\n            if (value_mono == 0){\n                out.at<cv::Vec3b>(i, j)[0] = 255;\n                out.at<cv::Vec3b>(i, j)[1] = 255;\n                out.at<cv::Vec3b>(i, j)[2] = 255;\n            } else {\n                Eigen::Vector3d bgr = monoToRainbow(value_mono);\n                out.at<cv::Vec3b>(i, j)[0] = (int)bgr(0);//255 - value_mono;\n                out.at<cv::Vec3b>(i, j)[1] = (int)bgr(1);//0;//std::min(std::max(128 - value_mono, 0), value_mono);\n                out.at<cv::Vec3b>(i, j)[2] = (int)bgr(2);//value_mono;\n            }\n        }\n    }\n    return out;\n}\n\n\ndouble LoopDetector::get_distance_thresh() const {\n    return distance_thresh;\n}\n\n#if 0\n/**\n * @brief To validate a loop candidate this function applies a scan matching between keyframes consisting the loop. If they are matched well, the loop is added to the pose graph\n * @param candidate_keyframes  candidate keyframes of loop start\n * @param new_keyframe         loop end keyframe\n * @param graph_slam           graph slam\n */\nLoop::Ptr LoopDetector::matching(const std::vector<KeyFrame::Ptr>& candidate_keyframes, const KeyFrame::Ptr& new_keyframe) {\n    if(candidate_keyframes.empty()) {\n        return nullptr;\n    }\n\n    registration->setInputTarget(new_keyframe->cloud);\n\n    double best_score = std::numeric_limits<double>::max();\n    KeyFrame::Ptr best_matched;\n    Eigen::Matrix4f relative_pose;\n\n    std::cout << std::endl;\n    std::cout << \"--- loop detection ---\" << std::endl;\n    std::cout << \"num_candidates: \" << candidate_keyframes.size() << std::endl;\n    std::cout << \"matching\" << std::flush;\n    auto t1 = ros::Time::now();\n\n    pcl::PointCloud<PointT>::Ptr aligned(new pcl::PointCloud<PointT>());\n    for(const auto& candidate : candidate_keyframes) {\n        registration->setInputSource(candidate->cloud);\n        Eigen::Isometry3d new_keyframe_estimate = new_keyframe->node->estimate();\n        new_keyframe_estimate.linear() = Eigen::Quaterniond(new_keyframe_estimate.linear()).normalized().toRotationMatrix();\n        Eigen::Isometry3d candidate_estimate = candidate->node->estimate();\n        candidate_estimate.linear() = Eigen::Quaterniond(candidate_estimate.linear()).normalized().toRotationMatrix();\n        Eigen::Matrix4f guess = (new_keyframe_estimate.inverse() * candidate_estimate).matrix().cast<float>();\n        guess(2, 3) = 0.0;\n        registration->align(*aligned, guess);\n        std::cout << \".\" << std::flush;\n\n        double score = registration->getFitnessScore(fitness_score_max_range);\n        if(!registration->hasConverged() || score > best_score) {\n        continue;\n        }\n\n        best_score = score;\n        best_matched = candidate;\n        relative_pose = registration->getFinalTransformation();\n    }\n\n    auto t2 = ros::Time::now();\n    std::cout << \" done\" << std::endl;\n    std::cout << \"best_score: \" << boost::format(\"%.3f\") % best_score << \"    time: \" << boost::format(\"%.3f\") % (t2 - t1).toSec() << \"[sec]\" << std::endl;\n\n    if(best_score > fitness_score_thresh) {\n        std::cout << \"loop not found...\" << std::endl;\n        return nullptr;\n    }\n\n    std::cout << \"loop found!!\" << std::endl;\n    std::cout << \"relative pose: \" << relative_pose.block<3, 1>(0, 3) << \" - \" << Eigen::Quaternionf(relative_pose.block<3, 3>(0, 0)).coeffs().transpose() << std::endl;\n\n    last_loop_edge_accum_distance = new_keyframe->accum_distance;\n\n    return std::make_shared<Loop>(new_keyframe, best_matched, relative_pose);\n}\n#endif\n\n\n}  // namespace radar_graph_slam\n"
  },
  {
    "path": "src/radar_graph_slam/map2odom_publisher.py",
    "content": "#!/usr/bin/python\n# SPDX-License-Identifier: BSD-2-Clause\nimport tf\nimport rospy\nfrom geometry_msgs.msg import *\n\n\nclass Map2OdomPublisher:\n\tdef __init__(self):\n\t\tself.broadcaster = tf.TransformBroadcaster()\n\t\tself.subscriber = rospy.Subscriber('/radar_graph_slam/odom2pub', TransformStamped, self.callback)\n\n\tdef callback(self, odom_msg):\n\t\tself.odom_msg = odom_msg\n\n\tdef spin(self):\n\t\tif not hasattr(self, 'odom_msg'):\n\t\t\tself.broadcaster.sendTransform((0, 0, 0), (0, 0, 0, 1), rospy.Time.now(), 'odom', 'map')\n\t\t\treturn\n\n\t\tpose = self.odom_msg.transform\n\t\tpos = (pose.translation.x, pose.translation.y, pose.translation.z)\n\t\tquat = (pose.rotation.x, pose.rotation.y, pose.rotation.z, pose.rotation.w)\n\n\t\tmap_frame_id = self.odom_msg.header.frame_id\n\t\todom_frame_id = self.odom_msg.child_frame_id\n\n\t\tself.broadcaster.sendTransform(pos, quat, rospy.Time.now(), odom_frame_id, map_frame_id)\n\n\ndef main():\n\trospy.init_node('map2odom_publisher')\n\tnode = Map2OdomPublisher()\n\n\trate = rospy.Rate(10.0)\n\twhile not rospy.is_shutdown():\n\t\tnode.spin()\n\t\trate.sleep()\n\nif __name__ == '__main__':\n\tmain()\n"
  },
  {
    "path": "src/radar_graph_slam/map_cloud_generator.cpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#include <radar_graph_slam/map_cloud_generator.hpp>\n\n#include <pcl/octree/octree_search.h>\n\nnamespace radar_graph_slam {\n\nMapCloudGenerator::MapCloudGenerator() {}\n\nMapCloudGenerator::~MapCloudGenerator() {}\n\npcl::PointCloud<MapCloudGenerator::PointT>::Ptr MapCloudGenerator::generate(const std::vector<KeyFrameSnapshot::Ptr>& keyframes, double resolution) const {\n  if(keyframes.empty()) {\n    std::cerr << \"warning: keyframes empty!!\" << std::endl;\n    return nullptr;\n  }\n\n  pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>());\n  cloud->reserve(keyframes.front()->cloud->size() * keyframes.size());\n\n  for(const auto& keyframe : keyframes) {\n    Eigen::Matrix4f pose = keyframe->pose.matrix().cast<float>();\n    for(const auto& src_pt : keyframe->cloud->points) {\n      double d = src_pt.getVector3fMap().norm();\n      if (d > 50) continue;\n      PointT dst_pt;\n      dst_pt.getVector4fMap() = pose * src_pt.getVector4fMap();\n      dst_pt.intensity = src_pt.intensity;\n      cloud->push_back(dst_pt); \n    }\n  }\n\n  cloud->width = cloud->size();\n  cloud->height = 1;\n  cloud->is_dense = false;\n\n  if (resolution <=0.0)\n    return cloud; // To get unfiltered point cloud with intensity\n\n  pcl::octree::OctreePointCloud<PointT> octree(resolution);\n  octree.setInputCloud(cloud);\n  octree.addPointsFromInputCloud();\n\n  pcl::PointCloud<PointT>::Ptr filtered(new pcl::PointCloud<PointT>());\n  octree.getOccupiedVoxelCenters(filtered->points);\n\n  filtered->width = filtered->size();\n  filtered->height = 1;\n  filtered->is_dense = false;\n\n  return filtered;\n}\n\n}  // namespace radar_graph_slam\n"
  },
  {
    "path": "src/radar_graph_slam/registrations.cpp",
    "content": "// SPDX-License-Identifier: BSD-2-Clause\n\n#include <radar_graph_slam/registrations.hpp>\n\n#include <iostream>\n\n#include <pcl/registration/ndt.h>\n#include <pcl/registration/icp.h>\n#include <pcl/registration/gicp.h>\n\n#include <pclomp/ndt_omp.h>\n#include <pclomp/gicp_omp.h>\n#include <fast_gicp/gicp/fast_gicp.hpp>\n#include <fast_gicp/gicp/fast_vgicp.hpp>\n#include <fast_gicp/gicp/fast_apdgicp.hpp>\n\n#ifdef USE_VGICP_CUDA\n#include <fast_gicp/gicp/fast_vgicp_cuda.hpp>\n#endif\n\nnamespace radar_graph_slam {\n\npcl::Registration<pcl::PointXYZI, pcl::PointXYZI>::Ptr select_registration_method(ros::NodeHandle& pnh) {\n  using PointT = pcl::PointXYZI;\n\n  // select a registration method (ICP, GICP, NDT)\n  std::string registration_method = pnh.param<std::string>(\"registration_method\", \"NDT_OMP\");\n  if(registration_method == \"FAST_GICP\") {\n    std::cout << \"registration: FAST_GICP\" << std::endl;\n    fast_gicp::FastGICP<PointT, PointT>::Ptr gicp(new fast_gicp::FastGICP<PointT, PointT>());\n    gicp->setNumThreads(pnh.param<int>(\"reg_num_threads\", 0));\n    gicp->setTransformationEpsilon(pnh.param<double>(\"reg_transformation_epsilon\", 0.01));\n    gicp->setMaximumIterations(pnh.param<int>(\"reg_maximum_iterations\", 64));\n    gicp->setMaxCorrespondenceDistance(pnh.param<double>(\"reg_max_correspondence_distance\", 2.5));\n    gicp->setCorrespondenceRandomness(pnh.param<int>(\"reg_correspondence_randomness\", 20));\n    return gicp;\n  }\n  else if(registration_method == \"FAST_APDGICP\") {\n    std::cout << \"registration: FAST_APDGICP\" << std::endl;\n    fast_gicp::FastAPDGICP<PointT, PointT>::Ptr apdgicp(new fast_gicp::FastAPDGICP<PointT, PointT>());\n    apdgicp->setNumThreads(pnh.param<int>(\"reg_num_threads\", 0));\n    apdgicp->setTransformationEpsilon(pnh.param<double>(\"reg_transformation_epsilon\", 0.01));\n    apdgicp->setMaximumIterations(pnh.param<int>(\"reg_maximum_iterations\", 64));\n    apdgicp->setMaxCorrespondenceDistance(pnh.param<double>(\"reg_max_correspondence_distance\", 2.5));\n    apdgicp->setCorrespondenceRandomness(pnh.param<int>(\"reg_correspondence_randomness\", 20));\n    apdgicp->setDistVar(pnh.param<double>(\"dist_var\", 0.86));\n    apdgicp->setAzimuthVar(pnh.param<double>(\"azimuth_var\", 0.5));\n    apdgicp->setElevationVar(pnh.param<double>(\"elevation_var\", 1.0));\n    return apdgicp;\n  }\n#ifdef USE_VGICP_CUDA\n  else if(registration_method == \"FAST_VGICP_CUDA\") {\n    std::cout << \"registration: FAST_VGICP_CUDA\" << std::endl;\n    fast_gicp::FastVGICPCuda<PointT, PointT>::Ptr vgicp(new fast_gicp::FastVGICPCuda<PointT, PointT>());\n    vgicp->setResolution(pnh.param<double>(\"reg_resolution\", 1.0));\n    vgicp->setTransformationEpsilon(pnh.param<double>(\"reg_transformation_epsilon\", 0.01));\n    vgicp->setMaximumIterations(pnh.param<int>(\"reg_maximum_iterations\", 64));\n    vgicp->setCorrespondenceRandomness(pnh.param<int>(\"reg_correspondence_randomness\", 20));\n    return vgicp;\n  }\n#endif\n  else if(registration_method == \"FAST_VGICP\") {\n    std::cout << \"registration: FAST_VGICP\" << std::endl;\n    fast_gicp::FastVGICP<PointT, PointT>::Ptr vgicp(new fast_gicp::FastVGICP<PointT, PointT>());\n    vgicp->setNumThreads(pnh.param<int>(\"reg_num_threads\", 0));\n    vgicp->setResolution(pnh.param<double>(\"reg_resolution\", 1.0));\n    vgicp->setTransformationEpsilon(pnh.param<double>(\"reg_transformation_epsilon\", 0.01));\n    vgicp->setMaximumIterations(pnh.param<int>(\"reg_maximum_iterations\", 64));\n    vgicp->setCorrespondenceRandomness(pnh.param<int>(\"reg_correspondence_randomness\", 20));\n    return vgicp;\n  } else if(registration_method == \"ICP\") {\n    std::cout << \"registration: ICP\" << std::endl;\n    pcl::IterativeClosestPoint<PointT, PointT>::Ptr icp(new pcl::IterativeClosestPoint<PointT, PointT>());\n    icp->setTransformationEpsilon(pnh.param<double>(\"reg_transformation_epsilon\", 0.01));\n    icp->setMaximumIterations(pnh.param<int>(\"reg_maximum_iterations\", 64));\n    icp->setMaxCorrespondenceDistance(pnh.param<double>(\"reg_max_correspondence_distance\", 2.5));\n    icp->setUseReciprocalCorrespondences(pnh.param<bool>(\"reg_use_reciprocal_correspondences\", false));\n    return icp;\n  } else if(registration_method.find(\"GICP\") != std::string::npos) {\n    if(registration_method.find(\"OMP\") == std::string::npos) {\n      std::cout << \"registration: GICP\" << std::endl;\n      pcl::GeneralizedIterativeClosestPoint<PointT, PointT>::Ptr gicp(new pcl::GeneralizedIterativeClosestPoint<PointT, PointT>());\n      gicp->setTransformationEpsilon(pnh.param<double>(\"reg_transformation_epsilon\", 0.01));\n      gicp->setMaximumIterations(pnh.param<int>(\"reg_maximum_iterations\", 64));\n      gicp->setUseReciprocalCorrespondences(pnh.param<bool>(\"reg_use_reciprocal_correspondences\", false));\n      gicp->setMaxCorrespondenceDistance(pnh.param<double>(\"reg_max_correspondence_distance\", 2.5));\n      gicp->setCorrespondenceRandomness(pnh.param<int>(\"reg_correspondence_randomness\", 20));\n      gicp->setMaximumOptimizerIterations(pnh.param<int>(\"reg_max_optimizer_iterations\", 20));\n      return gicp;\n    } else {\n      std::cout << \"registration: GICP_OMP\" << std::endl;\n      pclomp::GeneralizedIterativeClosestPoint<PointT, PointT>::Ptr gicp(new pclomp::GeneralizedIterativeClosestPoint<PointT, PointT>());\n      gicp->setTransformationEpsilon(pnh.param<double>(\"reg_transformation_epsilon\", 0.01));\n      gicp->setMaximumIterations(pnh.param<int>(\"reg_maximum_iterations\", 64));\n      gicp->setUseReciprocalCorrespondences(pnh.param<bool>(\"reg_use_reciprocal_correspondences\", false));\n      gicp->setMaxCorrespondenceDistance(pnh.param<double>(\"reg_max_correspondence_distance\", 2.5));\n      gicp->setCorrespondenceRandomness(pnh.param<int>(\"reg_correspondence_randomness\", 20));\n      gicp->setMaximumOptimizerIterations(pnh.param<int>(\"reg_max_optimizer_iterations\", 20));\n      return gicp;\n    }\n  } else {\n    if(registration_method.find(\"NDT\") == std::string::npos) {\n      std::cerr << \"warning: unknown registration type(\" << registration_method << \")\" << std::endl;\n      std::cerr << \"       : use NDT\" << std::endl;\n    }\n\n    double ndt_resolution = pnh.param<double>(\"reg_resolution\", 0.5);\n    if(registration_method.find(\"OMP\") == std::string::npos) {\n      std::cout << \"registration: NDT \" << ndt_resolution << std::endl;\n      pcl::NormalDistributionsTransform<PointT, PointT>::Ptr ndt(new pcl::NormalDistributionsTransform<PointT, PointT>());\n      ndt->setTransformationEpsilon(pnh.param<double>(\"reg_transformation_epsilon\", 0.01));\n      ndt->setMaximumIterations(pnh.param<int>(\"reg_maximum_iterations\", 64));\n      ndt->setResolution(ndt_resolution);\n      return ndt;\n    } else {\n      int num_threads = pnh.param<int>(\"reg_num_threads\", 0);\n      std::string nn_search_method = pnh.param<std::string>(\"reg_nn_search_method\", \"DIRECT7\");\n      std::cout << \"registration: NDT_OMP \" << nn_search_method << \" \" << ndt_resolution << \" (\" << num_threads << \" threads)\" << std::endl;\n      pclomp::NormalDistributionsTransform<PointT, PointT>::Ptr ndt(new pclomp::NormalDistributionsTransform<PointT, PointT>());\n      if(num_threads > 0) {\n        ndt->setNumThreads(num_threads);\n      }\n      ndt->setTransformationEpsilon(pnh.param<double>(\"reg_transformation_epsilon\", 0.01));\n      ndt->setMaximumIterations(pnh.param<int>(\"reg_maximum_iterations\", 64));\n      ndt->setResolution(ndt_resolution);\n      if(nn_search_method == \"KDTREE\") {\n        ndt->setNeighborhoodSearchMethod(pclomp::KDTREE);\n      } else if(nn_search_method == \"DIRECT1\") {\n        ndt->setNeighborhoodSearchMethod(pclomp::DIRECT1);\n      } else {\n        ndt->setNeighborhoodSearchMethod(pclomp::DIRECT7);\n      }\n      return ndt;\n    }\n  }\n\n  return nullptr;\n}\n\n}  // namespace radar_graph_slam\n"
  },
  {
    "path": "srv/DumpGraph.srv",
    "content": "\nstring destination\n---\nbool success\n"
  },
  {
    "path": "srv/SaveMap.srv",
    "content": "bool utm\nfloat32 resolution\nstring destination\n---\nbool success\n"
  }
]