Skip to content

Commit 2809a67

Browse files
feat(ndt_scan_matcher): remake diag (#5076)
* feat(ndt_scan_matcher): remake diag Signed-off-by: yamato-ando <Yamato ANDO> * style(pre-commit): autofix * add latest_ndt_aling_service_best_score Signed-off-by: yamato-ando <Yamato ANDO> * style(pre-commit): autofix * check nullptr Signed-off-by: yamato-ando <Yamato ANDO> * style(pre-commit): autofix * add validate_distance_from_initial_to_result Signed-off-by: yamato-ando <Yamato ANDO> * style(pre-commit): autofix * rename Signed-off-by: yamato-ando <Yamato ANDO> * style(pre-commit): autofix * style(pre-commit): autofix * [WIP] update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * [WIP] update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * [WIP] update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * [WIP] update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * fix typo Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * [WIP] update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * update readme Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * [WIP] update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * [WIP] update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * fix typo Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * [WIP] update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * [WIP] udpate Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * [WIP] udpate Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * add is_need_rebuild Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * fix typo Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * add image Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * remove unused func Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * fix Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * fix typo Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * update image Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * fix typo Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * fix Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * remove unused include Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * move code Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * move code Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * fix FIX ME Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * remove unused func Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * [WIP] update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * add diag for trigger node service Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * move code Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * delete unused code Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * udpate Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * delete RCLCPP message Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * update Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * fix typo Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * rename diag Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * rename func Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * remove Transition condition to OK Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * fix table Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * update readme Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * fix order Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * fix typo Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * remove diag prefix Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * fix readme Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * rename diag Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * remove unused code Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * fix double free Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * fix typo Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * output to terminal Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> * style(pre-commit): autofix * fix typo Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> --------- Signed-off-by: yamato-ando <Yamato ANDO> Signed-off-by: Yamato Ando <yamato.ando@tier4.jp> Co-authored-by: yamato-ando <Yamato ANDO> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 1872e9b commit 2809a67

15 files changed

+734
-276
lines changed

localization/ndt_scan_matcher/CMakeLists.txt

+12-7
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ find_package(PCL REQUIRED COMPONENTS common io registration)
2727
include_directories(${PCL_INCLUDE_DIRS})
2828

2929
ament_auto_add_executable(ndt_scan_matcher
30-
src/particle.cpp
31-
src/ndt_scan_matcher_node.cpp
32-
src/ndt_scan_matcher_core.cpp
30+
src/diagnostics_module.cpp
3331
src/map_update_module.cpp
32+
src/ndt_scan_matcher_core.cpp
33+
src/ndt_scan_matcher_node.cpp
34+
src/particle.cpp
3435
)
3536

3637
link_directories(${PCL_LIBRARY_DIRS})
@@ -45,15 +46,19 @@ if(BUILD_TESTING)
4546
find_package(ament_cmake_gtest REQUIRED)
4647
ament_auto_add_gtest(standard_sequence_for_initial_pose_estimation
4748
test/test_cases/standard_sequence_for_initial_pose_estimation.cpp
48-
src/particle.cpp
49-
src/ndt_scan_matcher_core.cpp
49+
src/diagnostics_module.cpp
5050
src/map_update_module.cpp
51+
src/ndt_scan_matcher_core.cpp
52+
# src/ndt_scan_matcher_node.cpp
53+
src/particle.cpp
5154
)
5255
ament_auto_add_gtest(once_initialize_at_out_of_map_then_initialize_correctly
5356
test/test_cases/once_initialize_at_out_of_map_then_initialize_correctly.cpp
54-
src/particle.cpp
55-
src/ndt_scan_matcher_core.cpp
57+
src/diagnostics_module.cpp
5658
src/map_update_module.cpp
59+
src/ndt_scan_matcher_core.cpp
60+
# src/ndt_scan_matcher_node.cpp
61+
src/particle.cpp
5762
)
5863
endif()
5964

localization/ndt_scan_matcher/README.md

+100
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2023 Autoware Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef NDT_SCAN_MATCHER__DIAGNOSTICS_MODULE_HPP_
16+
#define NDT_SCAN_MATCHER__DIAGNOSTICS_MODULE_HPP_
17+
18+
#include <rclcpp/rclcpp.hpp>
19+
20+
#include <diagnostic_msgs/msg/diagnostic_array.hpp>
21+
22+
#include <string>
23+
#include <vector>
24+
25+
class DiagnosticsModule
26+
{
27+
public:
28+
DiagnosticsModule(rclcpp::Node * node, const std::string & diagnostic_name);
29+
void clear();
30+
void addKeyValue(const diagnostic_msgs::msg::KeyValue & key_value_msg);
31+
template <typename T>
32+
void addKeyValue(const std::string & key, const T & value);
33+
void updateLevelAndMessage(const int8_t level, const std::string & message);
34+
void publish();
35+
36+
private:
37+
diagnostic_msgs::msg::DiagnosticArray createDiagnosticsArray() const;
38+
39+
rclcpp::Clock::SharedPtr clock_;
40+
rclcpp::Publisher<diagnostic_msgs::msg::DiagnosticArray>::SharedPtr diagnostics_pub_;
41+
42+
diagnostic_msgs::msg::DiagnosticStatus diagnostics_status_msg_;
43+
};
44+
45+
template <typename T>
46+
void DiagnosticsModule::addKeyValue(const std::string & key, const T & value)
47+
{
48+
diagnostic_msgs::msg::KeyValue key_value;
49+
key_value.key = key;
50+
key_value.value = std::to_string(value);
51+
addKeyValue(key_value);
52+
}
53+
54+
template <>
55+
void DiagnosticsModule::addKeyValue(const std::string & key, const std::string & value);
56+
template <>
57+
void DiagnosticsModule::addKeyValue(const std::string & key, const bool & value);
58+
59+
#endif // NDT_SCAN_MATCHER__DIAGNOSTICS_MODULE_HPP_

localization/ndt_scan_matcher/include/ndt_scan_matcher/hyper_parameters.hpp

+6-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <multigrid_pclomp/multigrid_ndt_omp.h>
2121

2222
#include <algorithm>
23+
#include <sstream>
2324
#include <string>
2425
#include <vector>
2526

@@ -162,10 +163,11 @@ struct HyperParameters
162163
initial_pose_offset_model_y[i];
163164
}
164165
} else {
165-
RCLCPP_WARN(
166-
node->get_logger(),
167-
"Invalid initial pose offset model parameters. Disable covariance estimation.");
168-
covariance.covariance_estimation.enable = false;
166+
std::stringstream message;
167+
message << "Invalid initial pose offset model parameters."
168+
<< "Please make sure that the number of elements in "
169+
<< "initial_pose_offset_model_x and initial_pose_offset_model_y are the same.";
170+
throw std::runtime_error(message.str());
169171
}
170172
}
171173

localization/ndt_scan_matcher/include/ndt_scan_matcher/map_update_module.hpp

+14-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#define NDT_SCAN_MATCHER__MAP_UPDATE_MODULE_HPP_
1717

1818
#include "localization_util/util_func.hpp"
19+
#include "ndt_scan_matcher/diagnostics_module.hpp"
1920
#include "ndt_scan_matcher/hyper_parameters.hpp"
2021
#include "ndt_scan_matcher/particle.hpp"
2122

@@ -54,10 +55,20 @@ class MapUpdateModule
5455
private:
5556
friend class NDTScanMatcher;
5657

58+
void callback_timer(
59+
const bool is_activated, const std::optional<geometry_msgs::msg::Point> & position,
60+
std::unique_ptr<DiagnosticsModule> & diagnostics_ptr);
61+
62+
[[nodiscard]] bool should_update_map(
63+
const geometry_msgs::msg::Point & position,
64+
std::unique_ptr<DiagnosticsModule> & diagnostics_ptr);
65+
void update_map(
66+
const geometry_msgs::msg::Point & position,
67+
std::unique_ptr<DiagnosticsModule> & diagnostics_ptr);
5768
// Update the specified NDT
58-
bool update_ndt(const geometry_msgs::msg::Point & position, NdtType & ndt);
59-
void update_map(const geometry_msgs::msg::Point & position);
60-
[[nodiscard]] bool should_update_map(const geometry_msgs::msg::Point & position);
69+
bool update_ndt(
70+
const geometry_msgs::msg::Point & position, NdtType & ndt,
71+
std::unique_ptr<DiagnosticsModule> & diagnostics_ptr);
6172
void publish_partial_pcd_map();
6273

6374
rclcpp::Publisher<sensor_msgs::msg::PointCloud2>::SharedPtr loaded_pcd_pub_;

localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp

+28-18
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define FMT_HEADER_ONLY
1919

2020
#include "localization_util/smart_pose_buffer.hpp"
21+
#include "ndt_scan_matcher/diagnostics_module.hpp"
2122
#include "ndt_scan_matcher/hyper_parameters.hpp"
2223
#include "ndt_scan_matcher/map_update_module.hpp"
2324

@@ -62,6 +63,7 @@
6263
#include <map>
6364
#include <memory>
6465
#include <mutex>
66+
#include <sstream>
6567
#include <string>
6668
#include <thread>
6769
#include <vector>
@@ -77,21 +79,32 @@ class NDTScanMatcher : public rclcpp::Node
7779
explicit NDTScanMatcher(const rclcpp::NodeOptions & options = rclcpp::NodeOptions());
7880

7981
private:
80-
void service_ndt_align(
81-
const tier4_localization_msgs::srv::PoseWithCovarianceStamped::Request::SharedPtr req,
82-
tier4_localization_msgs::srv::PoseWithCovarianceStamped::Response::SharedPtr res);
83-
void service_trigger_node(
84-
const std_srvs::srv::SetBool::Request::SharedPtr req,
85-
std_srvs::srv::SetBool::Response::SharedPtr res);
86-
8782
void callback_timer();
88-
void callback_sensor_points(
89-
sensor_msgs::msg::PointCloud2::ConstSharedPtr sensor_points_msg_in_sensor_frame);
83+
9084
void callback_initial_pose(
9185
geometry_msgs::msg::PoseWithCovarianceStamped::ConstSharedPtr initial_pose_msg_ptr);
86+
void callback_initial_pose_main(
87+
const geometry_msgs::msg::PoseWithCovarianceStamped::ConstSharedPtr initial_pose_msg_ptr);
88+
9289
void callback_regularization_pose(
9390
geometry_msgs::msg::PoseWithCovarianceStamped::ConstSharedPtr pose_conv_msg_ptr);
9491

92+
void callback_sensor_points(
93+
sensor_msgs::msg::PointCloud2::ConstSharedPtr sensor_points_msg_in_sensor_frame);
94+
bool callback_sensor_points_main(
95+
sensor_msgs::msg::PointCloud2::ConstSharedPtr sensor_points_msg_in_sensor_frame);
96+
97+
void service_trigger_node(
98+
const std_srvs::srv::SetBool::Request::SharedPtr req,
99+
std_srvs::srv::SetBool::Response::SharedPtr res);
100+
101+
void service_ndt_align(
102+
const tier4_localization_msgs::srv::PoseWithCovarianceStamped::Request::SharedPtr req,
103+
tier4_localization_msgs::srv::PoseWithCovarianceStamped::Response::SharedPtr res);
104+
void service_ndt_align_main(
105+
const tier4_localization_msgs::srv::PoseWithCovarianceStamped::Request::SharedPtr req,
106+
tier4_localization_msgs::srv::PoseWithCovarianceStamped::Response::SharedPtr res);
107+
95108
geometry_msgs::msg::PoseWithCovarianceStamped align_pose(
96109
const geometry_msgs::msg::PoseWithCovarianceStamped & initial_pose_with_cov);
97110

@@ -116,11 +129,6 @@ class NDTScanMatcher : public rclcpp::Node
116129
const geometry_msgs::msg::PoseWithCovarianceStamped & initial_pose_old_msg,
117130
const geometry_msgs::msg::PoseWithCovarianceStamped & initial_pose_new_msg);
118131

119-
bool validate_num_iteration(const int iter_num, const int max_iter_num);
120-
bool validate_score(
121-
const double score, const double score_threshold, const std::string & score_name);
122-
bool validate_converged_param(
123-
const double & transform_probability, const double & nearest_voxel_transformation_likelihood);
124132
static int count_oscillation(const std::vector<geometry_msgs::msg::Pose> & result_pose_msg_array);
125133

126134
std::array<double, 36> rotate_covariance(
@@ -131,8 +139,6 @@ class NDTScanMatcher : public rclcpp::Node
131139

132140
void add_regularization_pose(const rclcpp::Time & sensor_ros_time);
133141

134-
void publish_diagnostic();
135-
136142
rclcpp::TimerBase::SharedPtr map_update_timer_;
137143
rclcpp::Subscription<geometry_msgs::msg::PoseWithCovarianceStamped>::SharedPtr initial_pose_sub_;
138144
rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr sensor_points_sub_;
@@ -168,7 +174,6 @@ class NDTScanMatcher : public rclcpp::Node
168174
rclcpp::Publisher<visualization_msgs::msg::MarkerArray>::SharedPtr ndt_marker_pub_;
169175
rclcpp::Publisher<visualization_msgs::msg::MarkerArray>::SharedPtr
170176
ndt_monte_carlo_initial_pose_marker_pub_;
171-
rclcpp::Publisher<diagnostic_msgs::msg::DiagnosticArray>::SharedPtr diagnostics_pub_;
172177

173178
rclcpp::Service<tier4_localization_msgs::srv::PoseWithCovarianceStamped>::SharedPtr service_;
174179
rclcpp::Service<std_srvs::srv::SetBool>::SharedPtr service_trigger_node_;
@@ -180,7 +185,6 @@ class NDTScanMatcher : public rclcpp::Node
180185
rclcpp::CallbackGroup::SharedPtr timer_callback_group_;
181186

182187
std::shared_ptr<NormalDistributionsTransform> ndt_ptr_;
183-
std::shared_ptr<std::map<std::string, std::string>> state_ptr_;
184188

185189
Eigen::Matrix4f base_to_sensor_matrix_;
186190

@@ -194,6 +198,12 @@ class NDTScanMatcher : public rclcpp::Node
194198
std::unique_ptr<SmartPoseBuffer> regularization_pose_buffer_;
195199

196200
std::atomic<bool> is_activated_;
201+
std::unique_ptr<DiagnosticsModule> diagnostics_scan_points_;
202+
std::unique_ptr<DiagnosticsModule> diagnostics_initial_pose_;
203+
std::unique_ptr<DiagnosticsModule> diagnostics_regularization_pose_;
204+
std::unique_ptr<DiagnosticsModule> diagnostics_map_update_;
205+
std::unique_ptr<DiagnosticsModule> diagnostics_ndt_align_;
206+
std::unique_ptr<DiagnosticsModule> diagnostics_trigger_node_;
197207
std::unique_ptr<MapUpdateModule> map_update_module_;
198208
std::unique_ptr<tier4_autoware_utils::LoggerLevelConfigure> logger_configure_;
199209

Loading
Loading
Loading
Loading
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2023 Autoware Foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "ndt_scan_matcher/diagnostics_module.hpp"
16+
17+
#include <rclcpp/rclcpp.hpp>
18+
19+
#include <diagnostic_msgs/msg/diagnostic_array.hpp>
20+
21+
#include <algorithm>
22+
#include <string>
23+
24+
DiagnosticsModule::DiagnosticsModule(rclcpp::Node * node, const std::string & diagnostic_name)
25+
: clock_(node->get_clock())
26+
{
27+
diagnostics_pub_ =
28+
node->create_publisher<diagnostic_msgs::msg::DiagnosticArray>("/diagnostics", 10);
29+
30+
diagnostics_status_msg_.name =
31+
std::string(node->get_name()) + std::string(": ") + diagnostic_name;
32+
diagnostics_status_msg_.hardware_id = node->get_name();
33+
}
34+
35+
void DiagnosticsModule::clear()
36+
{
37+
diagnostics_status_msg_.values.clear();
38+
diagnostics_status_msg_.values.shrink_to_fit();
39+
40+
diagnostics_status_msg_.level = diagnostic_msgs::msg::DiagnosticStatus::OK;
41+
diagnostics_status_msg_.message = "";
42+
}
43+
44+
void DiagnosticsModule::addKeyValue(const diagnostic_msgs::msg::KeyValue & key_value_msg)
45+
{
46+
auto it = std::find_if(
47+
std::begin(diagnostics_status_msg_.values), std::end(diagnostics_status_msg_.values),
48+
[key_value_msg](const auto & arg) { return arg.key == key_value_msg.key; });
49+
50+
if (it != std::cend(diagnostics_status_msg_.values)) {
51+
it->value = key_value_msg.value;
52+
} else {
53+
diagnostics_status_msg_.values.push_back(key_value_msg);
54+
}
55+
}
56+
57+
template <>
58+
void DiagnosticsModule::addKeyValue(const std::string & key, const std::string & value)
59+
{
60+
diagnostic_msgs::msg::KeyValue key_value;
61+
key_value.key = key;
62+
key_value.value = value;
63+
addKeyValue(key_value);
64+
}
65+
66+
template <>
67+
void DiagnosticsModule::addKeyValue(const std::string & key, const bool & value)
68+
{
69+
diagnostic_msgs::msg::KeyValue key_value;
70+
key_value.key = key;
71+
key_value.value = value ? "True" : "False";
72+
addKeyValue(key_value);
73+
}
74+
75+
void DiagnosticsModule::updateLevelAndMessage(const int8_t level, const std::string & message)
76+
{
77+
if ((level > diagnostic_msgs::msg::DiagnosticStatus::OK)) {
78+
if (!diagnostics_status_msg_.message.empty()) {
79+
diagnostics_status_msg_.message += "; ";
80+
}
81+
diagnostics_status_msg_.message += message;
82+
}
83+
if (level > diagnostics_status_msg_.level) {
84+
diagnostics_status_msg_.level = level;
85+
}
86+
}
87+
88+
void DiagnosticsModule::publish()
89+
{
90+
diagnostics_pub_->publish(createDiagnosticsArray());
91+
}
92+
93+
diagnostic_msgs::msg::DiagnosticArray DiagnosticsModule::createDiagnosticsArray() const
94+
{
95+
diagnostic_msgs::msg::DiagnosticArray diagnostics_msg;
96+
diagnostics_msg.header.stamp = clock_->now();
97+
diagnostics_msg.status.push_back(diagnostics_status_msg_);
98+
99+
if (diagnostics_msg.status.at(0).level == diagnostic_msgs::msg::DiagnosticStatus::OK) {
100+
diagnostics_msg.status.at(0).message = "OK";
101+
}
102+
103+
return diagnostics_msg;
104+
}

0 commit comments

Comments
 (0)