add gemini2 XL launch file

This commit is contained in:
Joe Dong
2023-09-05 17:59:19 +08:00
parent 99e1baee8d
commit 8509ccfd67
24 changed files with 248 additions and 365 deletions
-155
View File
@@ -1,155 +0,0 @@
#include "orbbec_camera/gst_decoder.h"
#include <rclcpp/rclcpp.hpp>
#include <utility>
#include <gst/app/gstappsrc.h>
#include <gst/app/gstappsink.h>
#include <glog/logging.h>
namespace orbbec_camera {
GstreamerMjpegDecoder::GstreamerMjpegDecoder(int width, int height, std::string jpeg_decoder,
std::string video_convert, std::string jpeg_parse)
: MjpegDecoder(width, height),
jpeg_decoder_(std::move(jpeg_decoder)),
video_convert_(std::move(video_convert)),
jpeg_parse_(std::move(jpeg_parse)),
buffer_size_(width * height * 3) {
gst_init(NULL, NULL);
buffer_pool_ = gst_buffer_pool_new();
CHECK_NOTNULL(buffer_pool_);
GstStructure* config = gst_buffer_pool_get_config(buffer_pool_);
CHECK_NOTNULL(config);
gst_buffer_pool_config_set_params(config, NULL, buffer_size_, 0, 0);
if (jpeg_decoder_ == "unknown") {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("gstreamer_mjpeg_decoder"), "hw decoder is unknown");
throw std::runtime_error("hw decoder is unknown");
}
if (!gst_buffer_pool_set_config(buffer_pool_, config)) {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("gstreamer_mjpeg_decoder"),
"gst buffer pool set config error");
throw std::runtime_error("gst buffer pool set config error");
}
if (gst_buffer_pool_set_active(buffer_pool_, TRUE) != TRUE) {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("gstreamer_mjpeg_decoder"),
"gst buffer pool set active error");
throw std::runtime_error("gst buffer pool set active error");
}
// Create GStreamer elements
RCLCPP_INFO_STREAM(rclcpp::get_logger("gstreamer_mjpeg_decoder"),
"hw decoder: " << jpeg_decoder_ << ", video convert: " << video_convert_
<< ", jpeg parse: " << jpeg_parse_);
appsrc_ = gst_element_factory_make("appsrc", "appsrc");
jpegparse_ = gst_element_factory_make(jpeg_parse_.c_str(), jpeg_parse_.c_str());
jpegdec_ = gst_element_factory_make(jpeg_decoder_.c_str(), jpeg_decoder_.c_str());
videoconvert_ = gst_element_factory_make(video_convert_.c_str(), video_convert_.c_str());
appsink_ = gst_element_factory_make("appsink", "appsink");
// Check for null pointers
if (!appsrc_ || !jpegparse_ || !jpegdec_ || !videoconvert_ || !appsink_) {
throw std::runtime_error("Failed to create GStreamer elements");
}
// Set element properties
g_object_set(G_OBJECT(appsrc_), "caps",
gst_caps_new_simple("image/jpeg", "width", G_TYPE_INT, width, "height", G_TYPE_INT,
height, "framerate", GST_TYPE_FRACTION, 0, 1, NULL),
NULL);
g_object_set(G_OBJECT(appsink_), "blocksize", buffer_size_, "sync", FALSE, "max-buffers", 1,
"drop", TRUE, NULL);
g_object_set(G_OBJECT(appsink_), "caps",
gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, "RGB", "width",
G_TYPE_INT, width, "height", G_TYPE_INT, height, NULL),
NULL);
// Create pipeline and add elements
pipeline_ = gst_pipeline_new("pipeline");
if (!pipeline_) {
throw std::runtime_error("Failed to create pipeline");
}
gst_bin_add_many(GST_BIN(pipeline_), appsrc_, jpegparse_, jpegdec_, videoconvert_, appsink_,
NULL);
if (!gst_element_link_many(appsrc_, jpegparse_, jpegdec_, videoconvert_, appsink_, NULL)) {
throw std::runtime_error("Failed to link GStreamer elements");
}
// Set pipeline to playing state
if (gst_element_set_state(pipeline_, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
throw std::runtime_error("Failed to set GStreamer pipeline to playing state");
}
}
GstreamerMjpegDecoder::~GstreamerMjpegDecoder() {
if (pipeline_) {
gst_element_set_state(pipeline_, GST_STATE_NULL);
gst_object_unref(pipeline_);
pipeline_ = nullptr;
}
if (buffer_pool_) {
gst_buffer_pool_set_active(buffer_pool_, FALSE);
g_object_unref(buffer_pool_);
}
gst_deinit();
}
bool GstreamerMjpegDecoder::decode(const std::shared_ptr<ob::ColorFrame>& frame, uint8_t* dest) {
GstBuffer* buffer = NULL;
GstMapInfo map;
// Acquire buffer from pool
if (gst_buffer_pool_acquire_buffer(buffer_pool_, &buffer, NULL) != GST_FLOW_OK) {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("gstreamer_mjpeg_decoder"),
"gst buffer pool acquire buffer error");
return false;
}
// Map buffer and copy frame data
if (gst_buffer_map(buffer, &map, GST_MAP_WRITE)) {
if (frame->dataSize() <= map.size) {
memcpy(map.data, frame->data(), frame->dataSize());
} else {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("gstreamer_mjpeg_decoder"),
"Frame data size exceeds buffer size");
gst_buffer_unmap(buffer, &map);
return false;
}
gst_buffer_unmap(buffer, &map);
} else {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("gstreamer_mjpeg_decoder"), "Failed to map buffer");
return false;
}
// Push buffer to appsrc
GstFlowReturn flow_return = gst_app_src_push_buffer(GST_APP_SRC(appsrc_), buffer);
if (flow_return != GST_FLOW_OK) {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("gstreamer_mjpeg_decoder"),
"Failed to push buffer to appsrc, GstFlowReturn: " << flow_return);
return false;
}
// Pull sample from appsink
GstClockTime timeout = GST_SECOND;
GstSample* sample = gst_app_sink_try_pull_sample(GST_APP_SINK(appsink_), timeout);
if (sample) {
GstBuffer* outBuffer = gst_sample_get_buffer(sample);
GstMapInfo mapInfo;
if (gst_buffer_map(outBuffer, &mapInfo, GST_MAP_READ)) {
// Copy data to destination buffer
memcpy(dest, mapInfo.data, mapInfo.size);
gst_buffer_unmap(outBuffer, &mapInfo);
} else {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("gstreamer_mjpeg_decoder"),
"Failed to map output buffer");
gst_sample_unref(sample);
return false;
}
gst_sample_unref(sample);
return true;
} else {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("gstreamer_mjpeg_decoder"),
"Failed to decode frame " << strerror(errno) << " " << errno);
return false;
}
}
} // namespace orbbec_camera
+41 -37
View File
@@ -20,8 +20,6 @@
#if defined(USE_RK_HW_DECODER)
#include "orbbec_camera/rk_mpp_decoder.h"
#elif defined(USE_GST_HW_DECODER)
#include "orbbec_camera/gst_decoder.h"
#endif
namespace orbbec_camera {
@@ -37,7 +35,8 @@ OBCameraNode::OBCameraNode(rclcpp::Node *node, std::shared_ptr<ob::Device> devic
stream_name_[COLOR] = "color";
stream_name_[DEPTH] = "depth";
stream_name_[INFRA0] = "ir";
stream_name_[INFRA1] = "ir2";
stream_name_[INFRA1] = "left_ir";
stream_name_[INFRA2] = "right_ir";
stream_name_[ACCEL] = "accel";
stream_name_[GYRO] = "gyro";
@@ -49,9 +48,6 @@ OBCameraNode::OBCameraNode(rclcpp::Node *node, std::shared_ptr<ob::Device> devic
setupTopics();
#if defined(USE_RK_HW_DECODER)
mjpeg_decoder_ = std::make_unique<RKMjpegDecoder>(width_[COLOR], height_[COLOR]);
#elif defined(USE_GST_HW_DECODER)
mjpeg_decoder_ = std::make_unique<GstreamerMjpegDecoder>(
width_[COLOR], height_[COLOR], jpeg_decoder_, video_convert_, jpeg_parse_);
#endif
startStreams();
if (enable_d2c_viewer_) {
@@ -124,17 +120,15 @@ void OBCameraNode::setupDevices() {
if (!depth_work_mode_.empty()) {
device_->switchDepthWorkMode(depth_work_mode_.c_str());
}
if (sync_mode_ != OB_SYNC_MODE_CLOSE) {
OBDeviceSyncConfig sync_config;
if (sync_mode_ != OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN) {
auto sync_config = device_->getMultiDeviceSyncConfig();
sync_config.syncMode = sync_mode_;
sync_config.irTriggerSignalInDelay = ir_trigger_signal_in_delay_;
sync_config.rgbTriggerSignalInDelay = rgb_trigger_signal_in_delay_;
sync_config.deviceTriggerSignalOutDelay = device_trigger_signal_out_delay_;
device_->setSyncConfig(sync_config);
if (device_->isPropertySupported(OB_PROP_SYNC_SIGNAL_TRIGGER_OUT_BOOL,
OB_PERMISSION_READ_WRITE)) {
device_->setBoolProperty(OB_PROP_SYNC_SIGNAL_TRIGGER_OUT_BOOL, sync_signal_trigger_out_);
}
sync_config.depthDelayUs = depth_delay_us_;
sync_config.colorDelayUs = color_delay_us_;
sync_config.trigger2ImageDelayUs = trigger2image_delay_us_;
sync_config.triggerSignalOutputDelayUs = trigger_signal_output_delay_us_;
sync_config.triggerSignalOutputEnable = trigger_signal_output_enabled_;
device_->setMultiDeviceSyncConfig(sync_config);
}
if (info->pid() == GEMINI2_PID) {
auto default_precision_level = device_->getIntProperty(OB_PROP_DEPTH_PRECISION_LEVEL_INT);
@@ -194,7 +188,7 @@ void OBCameraNode::setupProfiles() {
<< ", Stream Index: " << elem.second << ", Width: " << width_[elem]
<< ", Height: " << height_[elem] << ", FPS: " << fps_[elem]
<< ", Format: " << magic_enum::enum_name(format_[elem]));
throw;
exit(-1);
}
if (!selected_profile) {
@@ -334,6 +328,18 @@ void OBCameraNode::setupDefaultImageFormat() {
encoding_[INFRA0] = sensor_msgs::image_encodings::MONO16;
unit_step_size_[INFRA0] = sizeof(uint16_t);
format_[INFRA1] = OB_FORMAT_Y16;
format_str_[INFRA1] = "Y16";
image_format_[INFRA1] = CV_16UC1;
encoding_[INFRA1] = sensor_msgs::image_encodings::MONO16;
unit_step_size_[INFRA1] = sizeof(uint16_t);
format_[INFRA2] = OB_FORMAT_Y16;
format_str_[INFRA2] = "Y16";
image_format_[INFRA2] = CV_16UC1;
encoding_[INFRA2] = sensor_msgs::image_encodings::MONO16;
unit_step_size_[INFRA2] = sizeof(uint16_t);
image_format_[COLOR] = CV_8UC3;
encoding_[COLOR] = sensor_msgs::image_encodings::RGB8;
unit_step_size_[COLOR] = 3 * sizeof(uint8_t);
@@ -404,7 +410,6 @@ void OBCameraNode::getParameters() {
setAndGetNodeParameter(enable_colored_point_cloud_, "enable_colored_point_cloud", false);
setAndGetNodeParameter(enable_point_cloud_, "enable_point_cloud", true);
setAndGetNodeParameter<std::string>(point_cloud_qos_, "point_cloud_qos", "default");
setAndGetNodeParameter(enable_publish_extrinsic_, "enable_publish_extrinsic", false);
setAndGetNodeParameter(enable_d2c_viewer_, "enable_d2c_viewer", false);
setAndGetNodeParameter(enable_hardware_d2d_, "enable_hardware_d2d", true);
setAndGetNodeParameter(enable_soft_filter_, "enable_soft_filter", true);
@@ -412,10 +417,11 @@ void OBCameraNode::getParameters() {
setAndGetNodeParameter(enable_ir_auto_exposure_, "enable_ir_auto_exposure", true);
setAndGetNodeParameter<std::string>(depth_work_mode_, "depth_work_mode", "");
setAndGetNodeParameter<std::string>(sync_mode_str_, "sync_mode", "close");
setAndGetNodeParameter(ir_trigger_signal_in_delay_, "ir_trigger_signal_in_delay", 0);
setAndGetNodeParameter(rgb_trigger_signal_in_delay_, "rgb_trigger_signal_in_delay", 0);
setAndGetNodeParameter(device_trigger_signal_out_delay_, "device_trigger_signal_out_delay", 0);
setAndGetNodeParameter(sync_signal_trigger_out_, "sync_signal_trigger_out", false);
setAndGetNodeParameter(depth_delay_us_, "depth_delay_us", 0);
setAndGetNodeParameter(color_delay_us_, "color_delay_us", 0);
setAndGetNodeParameter(trigger2image_delay_us_, "trigger2image_delay_us", 0);
setAndGetNodeParameter(trigger_signal_output_delay_us_, "trigger_signal_output_delay_us", 0);
setAndGetNodeParameter(trigger_signal_output_enabled_, "trigger_signal_output_enabled", false);
setAndGetNodeParameter<std::string>(depth_precision_str_, "depth_precision", "1mm");
std::transform(sync_mode_str_.begin(), sync_mode_str_.end(), sync_mode_str_.begin(), ::toupper);
sync_mode_ = OBSyncModeFromString(sync_mode_str_);
@@ -428,9 +434,6 @@ void OBCameraNode::getParameters() {
setAndGetNodeParameter<int>(soft_filter_speckle_size_, "soft_filter_speckle_size", -1);
setAndGetNodeParameter<double>(liner_accel_cov_, "linear_accel_cov", 0.0003);
setAndGetNodeParameter<double>(angular_vel_cov_, "angular_vel_cov", 0.02);
setAndGetNodeParameter<std::string>(jpeg_decoder_, "jpeg_decoder", "avdec_mjpeg");
setAndGetNodeParameter<std::string>(video_convert_, "video_convert", "videoconvert");
setAndGetNodeParameter<std::string>(jpeg_parse_, "jpeg_parse", "jpegparse");
}
void OBCameraNode::setupTopics() {
@@ -502,10 +505,6 @@ void OBCameraNode::setupPublishers() {
imu_publishers_[stream_index] = node_->create_publisher<sensor_msgs::msg::Imu>(
data_topic_name, rclcpp::QoS(rclcpp::QoSInitialization::from_rmw(data_qos), data_qos));
}
if (enable_publish_extrinsic_) {
extrinsics_publisher_ = node_->create_publisher<orbbec_camera_msgs::msg::Extrinsics>(
"extrinsic/depth_to_color", rclcpp::QoS{1}.transient_local());
}
}
void OBCameraNode::publishPointCloud(const std::shared_ptr<ob::FrameSet> &frame_set) {
@@ -702,12 +701,16 @@ void OBCameraNode::onNewFrameSetCallback(const std::shared_ptr<ob::FrameSet> &fr
tf_published_ = true;
}
publishPointCloud(frame_set);
auto color_frame = std::dynamic_pointer_cast<ob::Frame>(frame_set->colorFrame());
auto depth_frame = std::dynamic_pointer_cast<ob::Frame>(frame_set->depthFrame());
auto ir_frame = std::dynamic_pointer_cast<ob::Frame>(frame_set->irFrame());
onNewFrameCallback(color_frame, COLOR);
onNewFrameCallback(depth_frame, DEPTH);
onNewFrameCallback(ir_frame, INFRA0);
for (const auto &stream_index : IMAGE_STREAMS) {
if (enable_stream_[stream_index]) {
auto frame_type = STREAM_TYPE_TO_FRAME_TYPE.at(stream_index.first);
auto frame = frame_set->getFrame(frame_type);
if (frame == nullptr) {
continue;
}
onNewFrameCallback(frame, stream_index);
}
}
} catch (const ob::Error &e) {
RCLCPP_ERROR_STREAM(logger_, "onNewFrameSetCallback error: " << e.getMessage());
} catch (const std::exception &e) {
@@ -748,7 +751,7 @@ void OBCameraNode::onNewFrameCallback(const std::shared_ptr<ob::Frame> &frame,
auto frame_format = frame->format();
if (frame->type() == OB_FRAME_COLOR && frame_format != OB_FORMAT_RGB888) {
if (frame_format == OB_FORMAT_MJPG || frame_format == OB_FORMAT_MJPEG) {
#if defined(USE_RK_HW_DECODER) || defined(USE_GST_HW_DECODER)
#if defined(USE_RK_HW_DECODER)
CHECK_NOTNULL(mjpeg_decoder_.get());
video_frame = frame->as<ob::ColorFrame>();
const auto &color_frame = frame->as<ob::ColorFrame>();
@@ -773,7 +776,8 @@ void OBCameraNode::onNewFrameCallback(const std::shared_ptr<ob::Frame> &frame,
video_frame = frame->as<ob::ColorFrame>();
} else if (frame->type() == OB_FRAME_DEPTH) {
video_frame = frame->as<ob::DepthFrame>();
} else if (frame->type() == OB_FRAME_IR) {
} else if (frame->type() == OB_FRAME_IR || frame->type() == OB_FRAME_IR_LEFT ||
frame->type() == OB_FRAME_IR_RIGHT) {
video_frame = frame->as<ob::IRFrame>();
} else {
RCLCPP_ERROR(logger_, "Unsupported frame type: %d", frame->type());
+3 -4
View File
@@ -16,7 +16,7 @@
#include <sys/shm.h>
#include <ament_index_cpp/get_package_share_directory.hpp>
#include <rclcpp_components/register_node_macro.hpp>
#include <signal.h>
#include <csignal>
namespace orbbec_camera {
OBCameraNodeDriver::OBCameraNodeDriver(const rclcpp::NodeOptions &node_options)
@@ -86,7 +86,6 @@ void OBCameraNodeDriver::init() {
RCLCPP_INFO_STREAM(rclcpp::get_logger("orbbec_camera_node_driver"), "SIGTERM received");
exit(0);
});
}
void OBCameraNodeDriver::onDeviceConnected(const std::shared_ptr<ob::DeviceList> &device_list) {
@@ -185,7 +184,7 @@ void OBCameraNodeDriver::deviceCountUpdate() {
void OBCameraNodeDriver::syncTime() {
while (is_alive_ && rclcpp::ok()) {
if (device_ && device_info_ && !isOpenNIDevice(device_info_->pid())) {
ctx_->enableMultiDeviceSync(0);
ctx_->enableDeviceClockSync(0);
}
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
}
@@ -386,7 +385,7 @@ void OBCameraNodeDriver::initializeDevice(const std::shared_ptr<ob::Device> &dev
CHECK_NOTNULL(device_info_.get());
device_unique_id_ = device_info_->uid();
if (!isOpenNIDevice(device_info_->pid())) {
ctx_->enableMultiDeviceSync(0); // sync time stamp
ctx_->enableDeviceClockSync(0); // sync time stamp
}
RCLCPP_INFO_STREAM(logger_, "Device " << device_info_->name() << " connected");
RCLCPP_INFO_STREAM(logger_, "Serial number: " << device_info_->serialNumber());
+15 -6
View File
@@ -101,17 +101,12 @@ RKMjpegDecoder::~RKMjpegDecoder() {
mpp_destroy(mpp_ctx_);
mpp_ctx_ = nullptr;
}
if(rgb_buffer_){
if (rgb_buffer_) {
delete[] rgb_buffer_;
}
}
bool RKMjpegDecoder::mppFrame2RGB(const MppFrame frame, uint8_t *data) {
rga_info_t src_info;
rga_info_t dst_info;
// NOTE: memset to zero is MUST
memset(&src_info, 0, sizeof(rga_info_t));
memset(&dst_info, 0, sizeof(rga_info_t));
int width = mpp_frame_get_width(frame);
int height = mpp_frame_get_height(frame);
MppBuffer buffer = mpp_frame_get_buffer(frame);
@@ -122,6 +117,19 @@ bool RKMjpegDecoder::mppFrame2RGB(const MppFrame frame, uint8_t *data) {
CHECK_EQ(height, height_);
memset(data, 0, width * height * 3);
auto buffer_ptr = mpp_buffer_get_ptr(buffer);
#if defined(USE_LIBYUV)
// use libyuv to convert yuv420sp to rgb888
libyuv::I420ToRGB24((const uint8_t *)buffer_ptr, width,
(const uint8_t *)buffer_ptr + width * height, width / 2,
(const uint8_t *)buffer_ptr + width * height * 5 / 4, width / 2, data,
width * 3, width, height);
return true;
#else
rga_info_t src_info;
rga_info_t dst_info;
// NOTE: memset to zero is MUST
memset(&src_info, 0, sizeof(rga_info_t));
memset(&dst_info, 0, sizeof(rga_info_t));
src_info.fd = -1;
src_info.mmuFlag = 1;
src_info.virAddr = buffer_ptr;
@@ -139,6 +147,7 @@ bool RKMjpegDecoder::mppFrame2RGB(const MppFrame frame, uint8_t *data) {
return false;
}
return true;
#endif
}
bool RKMjpegDecoder::decode(const std::shared_ptr<ob::ColorFrame> &frame, uint8_t *dest) {
+14 -16
View File
@@ -281,25 +281,23 @@ OB_DEPTH_PRECISION_LEVEL depthPrecisionLevelFromString(
return OB_PRECISION_0MM8;
}
}
OBSyncMode OBSyncModeFromString(const std::string &mode) {
if (mode == "CLOSE") {
return OBSyncMode::OB_SYNC_MODE_CLOSE;
OBMultiDeviceSyncMode OBSyncModeFromString(const std::string &mode) {
if (mode == "FREE_RUN") {
return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN;
} else if (mode == "STANDALONE") {
return OBSyncMode::OB_SYNC_MODE_STANDALONE;
return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_STANDALONE;
} else if (mode == "PRIMARY") {
return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_PRIMARY;
} else if (mode == "SECONDARY") {
return OBSyncMode::OB_SYNC_MODE_SECONDARY;
} else if (mode == "PRIMARY_MCU_TRIGGER") {
return OBSyncMode::OB_SYNC_MODE_PRIMARY_MCU_TRIGGER;
} else if (mode == "PRIMARY_IR_TRIGGER") {
return OBSyncMode::OB_SYNC_MODE_PRIMARY_IR_TRIGGER;
} else if (mode == "PRIMARY_SOFT_TRIGGER") {
return OBSyncMode::OB_SYNC_MODE_PRIMARY_SOFT_TRIGGER;
} else if (mode == "SECONDARY_SOFT_TRIGGER") {
return OBSyncMode::OB_SYNC_MODE_SECONDARY_SOFT_TRIGGER;
return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_SECONDARY;
} else if (mode == "SECONDARY_SYNCED") {
return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_SECONDARY_SYNCED;
} else if (mode == "SOFTWARE_TRIGGERING") {
return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING;
} else if (mode == "HARDWARE_TRIGGERING") {
return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING;
} else {
RCLCPP_ERROR_STREAM(rclcpp::get_logger("utils"), "Unknown OBSyncMode: " << mode);
return OBSyncMode::OB_SYNC_MODE_CLOSE;
return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN;
}
}