#include "vp_analysis_board.h" #include "vp_version.h" #include "../vp_pipe_checker.h" namespace vp_utils { vp_analysis_board::vp_analysis_board(std::vector> src_nodes_in_pipe): src_nodes_in_pipe(src_nodes_in_pipe) { init(); } vp_analysis_board::~vp_analysis_board() { // set alive to false and wait threads exits alive = false; if (display_th.joinable()) { display_th.join(); } if (rtmp_th.joinable()) { rtmp_th.join(); } } void vp_analysis_board::init() { src_nodes_on_screen.clear(); des_nodes_on_screen.clear(); // check pipe vp_pipe_checker pipe_checker; pipe_checker(src_nodes_in_pipe); // layers number and max nodes number of all layers pipe_width = pipe_checker.pipe_width(); pipe_height = pipe_checker.pipe_height(); // calculate the w and h of canvas canvas_width = pipe_width * node_width + (pipe_width - 1) * node_gap_horizontal + 2 * canvas_gap_horizontal; canvas_height = pipe_height * node_height + (pipe_height - 1) * node_gap_vertical + 2 * canvas_gap_vertical; // create canvas Mat and initialize it with white bg_canvas.create(canvas_height, canvas_width, CV_8UC3); bg_canvas = cv::Scalar(255, 255, 255); // map recursively map_nodes(src_nodes_on_screen, 1); // render static parts starting with 1st layer render_layer(src_nodes_on_screen, bg_canvas); // save to local by default save(board_title + ".png"); } void vp_analysis_board::reload(std::vector> new_src_nodes_in_pipe) { std::lock_guard guard(reload_lock); if (new_src_nodes_in_pipe.size() != 0) { this->src_nodes_in_pipe = new_src_nodes_in_pipe; } init(); } void vp_analysis_board::save(std::string path) { cv::imwrite(path, bg_canvas); } void vp_analysis_board::display(int interval, bool block) { assert(interval > 0); if (displaying) { return; } auto display_func = [&, interval](){ while (alive) { auto loop_start = std::chrono::system_clock::now(); { std::lock_guard guard(reload_lock); // in case it reloading // deep copy the static background cv::Mat mat_to_display = bg_canvas.clone(); // render dynamic parts starting with 1 st layer render_layer(src_nodes_on_screen, mat_to_display, false); cv::imshow(board_title, mat_to_display); } // calculate the time need wait for auto loop_cost = std::chrono::system_clock::now() - loop_start; auto wait_time = std::chrono::duration_cast(std::chrono::seconds(interval) - loop_cost); cv::waitKey(wait_time.count()); }}; displaying = true; display_th = std::thread(display_func); if (block) { display_th.join(); } } void vp_analysis_board::push_rtmp(std::string rtmp, int bitrate) { if (displaying) { return; } auto fps = 10; auto rtmp_url = vp_utils::string_format(gst_template, bitrate, rtmp.c_str()); // 10 fps by default assert(rtmp_writer.open(rtmp_url, cv::CAP_GSTREAMER, fps, {bg_canvas.cols, bg_canvas.rows})); auto display_func = [&, fps](){ while (alive) { auto loop_start = std::chrono::system_clock::now(); { std::lock_guard guard(reload_lock); // in case it reloading // deep copy the static background cv::Mat mat_to_display = bg_canvas.clone(); // render dynamic parts starting with 1 st layer render_layer(src_nodes_on_screen, mat_to_display, false); rtmp_writer.write(mat_to_display); } // calculate the time need wait for auto loop_cost = std::chrono::system_clock::now() - loop_start; auto wait_time = std::chrono::duration_cast(std::chrono::milliseconds(1000 / fps) - loop_cost); std::this_thread::sleep_for(wait_time); }}; displaying = true; rtmp_th = std::thread(display_func); } void vp_analysis_board::render_layer(std::vector> nodes_in_layer, cv::Mat& canvas, bool static_parts) { std::vector> nodes_in_next_layer; for(auto& i : nodes_in_layer) { if (static_parts) { i->render_static_parts(canvas); } else { i->render_dynamic_parts(canvas); } auto n = i->get_next_nodes_on_screen(); nodes_in_next_layer.insert(nodes_in_next_layer.end(), n.begin(), n.end()); } if (nodes_in_next_layer.size() > 0) { bool all_the_same = true; for(auto & i: nodes_in_next_layer) { if (i != nodes_in_next_layer[0]) { all_the_same = false; break; } } // just keep the first one if all the next nodes are the same if (all_the_same) { nodes_in_next_layer.erase(nodes_in_next_layer.begin() + 1, nodes_in_next_layer.end()); } render_layer(nodes_in_next_layer, canvas, static_parts); } else { // recursion end /* global drawing */ // draw layer index at the bottom of canvas if (static_parts) { for (int i = 0; i < pipe_width; i++) { /* code */ cv::putText(canvas, "layer_" + std::to_string(i + 1), cv::Point(canvas_gap_horizontal + (node_width + node_gap_horizontal) * i, canvas_height - int(canvas_gap_vertical / 3)), 1, 1, cv::Scalar(255, 0, 0)); } } // refresh time & version info at the top left of canvas if (!static_parts) { auto time = vp_utils::time_format(NOW, "::"); cv::putText(canvas, time, cv::Point(20, 20), 1, 1, cv::Scalar(255, 0, 0)); auto version_info = APP_VERSION; cv::putText(canvas, version_info, cv::Point(canvas.cols - 240, 20), 1, 0.8, cv::Scalar(255, 0, 0)); } } } void vp_analysis_board::map_nodes(std::vector> nodes_in_layer, int layer) { if (layer == 1) { // here nodes_in_layer is empty auto num_src_nodes_in_pipe = src_nodes_in_pipe.size(); auto base_left = layer_base_left_cal(layer - 1); auto base_top = layer_base_top_cal(num_src_nodes_in_pipe); // map nodes at 1st layer in memory to screen for (int i = 0; i < num_src_nodes_in_pipe; i++) { auto node_left = base_left; auto node_top = base_top + i * (node_height + node_gap_vertical); auto node_on_screen = std::make_shared(src_nodes_in_pipe[i], vp_objects::vp_rect(node_left, node_top, node_width, node_height), layer); src_nodes_on_screen.push_back(node_on_screen); } map_nodes(src_nodes_on_screen, layer + 1); } else { std::vector> all_nodes_in_next_layer; for(auto &i: nodes_in_layer) { auto next_nodes = i->get_orginal_node()->next_nodes(); all_nodes_in_next_layer.insert(all_nodes_in_next_layer.end(), next_nodes.begin(), next_nodes.end()); } if (all_nodes_in_next_layer.size() > 0) { bool all_the_same = true; for(auto & i: all_nodes_in_next_layer) { if (i != all_nodes_in_next_layer[0]) { all_the_same = false; break; } } // just keep the first one if all the next nodes are the same if (all_the_same) { all_nodes_in_next_layer.erase(all_nodes_in_next_layer.begin() + 1, all_nodes_in_next_layer.end()); } auto num_all_nodes_in_next_layer = all_nodes_in_next_layer.size(); auto base_left = layer_base_left_cal(layer - 1); auto base_top = layer_base_top_cal(num_all_nodes_in_next_layer); auto index = 0; std::shared_ptr node_on_screen = nullptr; std::vector> nodes_in_next_layer; for(int i = 0; i < nodes_in_layer.size(); i++) { auto node_left = base_left; auto next_nodes_in_pipe = nodes_in_layer[i]->get_orginal_node()->next_nodes(); for (int j = 0; j < next_nodes_in_pipe.size(); j++) { auto node_top = base_top + index * (node_height + node_gap_vertical); if (!all_the_same || node_on_screen == nullptr) { node_on_screen = std::make_shared(next_nodes_in_pipe[j], vp_objects::vp_rect(node_left, node_top, node_width, node_height), layer); } nodes_in_layer[i]->get_next_nodes_on_screen().push_back(node_on_screen); if (!all_the_same || nodes_in_next_layer.empty()) { nodes_in_next_layer.push_back(node_on_screen); } index++; } } // next layer map_nodes(nodes_in_next_layer, layer + 1); } else { // cache the last layer des_nodes_on_screen = nodes_in_layer; } // recursion end } } }