pipeline_builder.hpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. #ifndef OPENCV_GAPI_PIPELINE_MODELING_TOOL_PIPELINE_BUILDER_HPP
  2. #define OPENCV_GAPI_PIPELINE_MODELING_TOOL_PIPELINE_BUILDER_HPP
  3. #include <map>
  4. #include <opencv2/gapi/infer.hpp> // cv::gapi::GNetPackage
  5. #include <opencv2/gapi/streaming/cap.hpp> // cv::gapi::wip::IStreamSource
  6. #include <opencv2/gapi/infer/ie.hpp> // cv::gapi::ie::Params
  7. #include <opencv2/gapi/gcommon.hpp> // cv::gapi::GCompileArgs
  8. #include <opencv2/gapi/cpu/gcpukernel.hpp> // GAPI_OCV_KERNEL
  9. #include <opencv2/gapi/gkernel.hpp> // G_API_OP
  10. #include "pipeline.hpp"
  11. #include "utils.hpp"
  12. struct Edge {
  13. struct P {
  14. std::string name;
  15. size_t port;
  16. };
  17. P src;
  18. P dst;
  19. };
  20. struct CallParams {
  21. std::string name;
  22. size_t call_every_nth;
  23. };
  24. struct CallNode {
  25. using F = std::function<void(const cv::GProtoArgs&, cv::GProtoArgs&)>;
  26. CallParams params;
  27. F run;
  28. };
  29. struct DataNode {
  30. cv::optional<cv::GProtoArg> arg;
  31. };
  32. struct Node {
  33. using Ptr = std::shared_ptr<Node>;
  34. using WPtr = std::weak_ptr<Node>;
  35. using Kind = cv::util::variant<CallNode, DataNode>;
  36. std::vector<Node::WPtr> in_nodes;
  37. std::vector<Node::Ptr> out_nodes;
  38. Kind kind;
  39. };
  40. struct SubGraphCall {
  41. G_API_OP(GSubGraph,
  42. <cv::GMat(cv::GMat, cv::GComputation, cv::GCompileArgs, size_t)>,
  43. "custom.subgraph") {
  44. static cv::GMatDesc outMeta(const cv::GMatDesc& in,
  45. cv::GComputation comp,
  46. cv::GCompileArgs compile_args,
  47. const size_t call_every_nth) {
  48. GAPI_Assert(call_every_nth > 0);
  49. auto out_metas =
  50. comp.compile(in, std::move(compile_args)).outMetas();
  51. GAPI_Assert(out_metas.size() == 1u);
  52. GAPI_Assert(cv::util::holds_alternative<cv::GMatDesc>(out_metas[0]));
  53. return cv::util::get<cv::GMatDesc>(out_metas[0]);
  54. }
  55. };
  56. struct SubGraphState {
  57. cv::Mat last_result;
  58. cv::GCompiled cc;
  59. int call_counter = 0;
  60. };
  61. GAPI_OCV_KERNEL_ST(SubGraphImpl, GSubGraph, SubGraphState) {
  62. static void setup(const cv::GMatDesc& in,
  63. cv::GComputation comp,
  64. cv::GCompileArgs compile_args,
  65. const size_t /*call_every_nth*/,
  66. std::shared_ptr<SubGraphState>& state,
  67. const cv::GCompileArgs& /*args*/) {
  68. state.reset(new SubGraphState{});
  69. state->cc = comp.compile(in, std::move(compile_args));
  70. auto out_desc =
  71. cv::util::get<cv::GMatDesc>(state->cc.outMetas()[0]);
  72. utils::createNDMat(state->last_result,
  73. out_desc.dims,
  74. out_desc.depth);
  75. }
  76. static void run(const cv::Mat& in,
  77. cv::GComputation /*comp*/,
  78. cv::GCompileArgs /*compile_args*/,
  79. const size_t call_every_nth,
  80. cv::Mat& out,
  81. SubGraphState& state) {
  82. // NB: Make a call on the first iteration and skip the furthers.
  83. if (state.call_counter == 0) {
  84. state.cc(in, state.last_result);
  85. }
  86. state.last_result.copyTo(out);
  87. state.call_counter = (state.call_counter + 1) % call_every_nth;
  88. }
  89. };
  90. void operator()(const cv::GProtoArgs& inputs, cv::GProtoArgs& outputs);
  91. size_t numInputs() const { return 1; }
  92. size_t numOutputs() const { return 1; }
  93. cv::GComputation comp;
  94. cv::GCompileArgs compile_args;
  95. size_t call_every_nth;
  96. };
  97. void SubGraphCall::operator()(const cv::GProtoArgs& inputs,
  98. cv::GProtoArgs& outputs) {
  99. GAPI_Assert(inputs.size() == 1u);
  100. GAPI_Assert(cv::util::holds_alternative<cv::GMat>(inputs[0]));
  101. GAPI_Assert(outputs.empty());
  102. auto in = cv::util::get<cv::GMat>(inputs[0]);
  103. outputs.emplace_back(GSubGraph::on(in, comp, compile_args, call_every_nth));
  104. }
  105. struct DummyCall {
  106. G_API_OP(GDummy,
  107. <cv::GMat(cv::GMat, double, OutputDescr)>,
  108. "custom.dummy") {
  109. static cv::GMatDesc outMeta(const cv::GMatDesc& /* in */,
  110. double /* time */,
  111. const OutputDescr& output) {
  112. if (output.dims.size() == 2) {
  113. return cv::GMatDesc(output.precision,
  114. 1,
  115. // NB: Dims[H, W] -> Size(W, H)
  116. cv::Size(output.dims[1], output.dims[0]));
  117. }
  118. return cv::GMatDesc(output.precision, output.dims);
  119. }
  120. };
  121. struct DummyState {
  122. cv::Mat mat;
  123. };
  124. // NB: Generate random mat once and then
  125. // copy to dst buffer on every iteration.
  126. GAPI_OCV_KERNEL_ST(GCPUDummy, GDummy, DummyState) {
  127. static void setup(const cv::GMatDesc& /*in*/,
  128. double /*time*/,
  129. const OutputDescr& output,
  130. std::shared_ptr<DummyState>& state,
  131. const cv::GCompileArgs& /*args*/) {
  132. state.reset(new DummyState{});
  133. utils::createNDMat(state->mat, output.dims, output.precision);
  134. utils::generateRandom(state->mat);
  135. }
  136. static void run(const cv::Mat& /*in_mat*/,
  137. double time,
  138. const OutputDescr& /*output*/,
  139. cv::Mat& out_mat,
  140. DummyState& state) {
  141. using namespace std::chrono;
  142. auto start_ts = utils::timestamp<utils::double_ms_t>();
  143. state.mat.copyTo(out_mat);
  144. auto elapsed = utils::timestamp<utils::double_ms_t>() - start_ts;
  145. utils::busyWait(duration_cast<microseconds>(utils::double_ms_t{time-elapsed}));
  146. }
  147. };
  148. void operator()(const cv::GProtoArgs& inputs, cv::GProtoArgs& outputs);
  149. size_t numInputs() const { return 1; }
  150. size_t numOutputs() const { return 1; }
  151. double time;
  152. OutputDescr output;
  153. };
  154. void DummyCall::operator()(const cv::GProtoArgs& inputs,
  155. cv::GProtoArgs& outputs) {
  156. GAPI_Assert(inputs.size() == 1u);
  157. GAPI_Assert(cv::util::holds_alternative<cv::GMat>(inputs[0]));
  158. GAPI_Assert(outputs.empty());
  159. auto in = cv::util::get<cv::GMat>(inputs[0]);
  160. outputs.emplace_back(GDummy::on(in, time, output));
  161. }
  162. struct InferCall {
  163. void operator()(const cv::GProtoArgs& inputs, cv::GProtoArgs& outputs);
  164. size_t numInputs() const { return input_layers.size(); }
  165. size_t numOutputs() const { return output_layers.size(); }
  166. std::string tag;
  167. std::vector<std::string> input_layers;
  168. std::vector<std::string> output_layers;
  169. };
  170. void InferCall::operator()(const cv::GProtoArgs& inputs,
  171. cv::GProtoArgs& outputs) {
  172. GAPI_Assert(inputs.size() == input_layers.size());
  173. GAPI_Assert(outputs.empty());
  174. cv::GInferInputs g_inputs;
  175. // TODO: Add an opportunity not specify input/output layers in case
  176. // there is only single layer.
  177. for (size_t i = 0; i < inputs.size(); ++i) {
  178. // TODO: Support GFrame as well.
  179. GAPI_Assert(cv::util::holds_alternative<cv::GMat>(inputs[i]));
  180. auto in = cv::util::get<cv::GMat>(inputs[i]);
  181. g_inputs[input_layers[i]] = in;
  182. }
  183. auto g_outputs = cv::gapi::infer<cv::gapi::Generic>(tag, g_inputs);
  184. for (size_t i = 0; i < output_layers.size(); ++i) {
  185. outputs.emplace_back(g_outputs.at(output_layers[i]));
  186. }
  187. }
  188. struct SourceCall {
  189. void operator()(const cv::GProtoArgs& inputs, cv::GProtoArgs& outputs);
  190. size_t numInputs() const { return 0; }
  191. size_t numOutputs() const { return 1; }
  192. };
  193. void SourceCall::operator()(const cv::GProtoArgs& inputs,
  194. cv::GProtoArgs& outputs) {
  195. GAPI_Assert(inputs.empty());
  196. GAPI_Assert(outputs.empty());
  197. // NB: Since NV12 isn't exposed source always produce GMat.
  198. outputs.emplace_back(cv::GMat());
  199. }
  200. struct LoadPath {
  201. std::string xml;
  202. std::string bin;
  203. };
  204. struct ImportPath {
  205. std::string blob;
  206. };
  207. using ModelPath = cv::util::variant<ImportPath, LoadPath>;
  208. struct DummyParams {
  209. double time;
  210. OutputDescr output;
  211. };
  212. struct InferParams {
  213. std::string name;
  214. ModelPath path;
  215. std::string device;
  216. std::vector<std::string> input_layers;
  217. std::vector<std::string> output_layers;
  218. std::map<std::string, std::string> config;
  219. cv::gapi::ie::InferMode mode;
  220. cv::util::optional<int> out_precision;
  221. };
  222. class ElapsedTimeCriterion : public StopCriterion {
  223. public:
  224. ElapsedTimeCriterion(int64_t work_time_mcs);
  225. void start() override;
  226. void iter() override;
  227. bool done() override;
  228. private:
  229. int64_t m_work_time_mcs;
  230. int64_t m_start_ts = -1;
  231. int64_t m_curr_ts = -1;
  232. };
  233. ElapsedTimeCriterion::ElapsedTimeCriterion(int64_t work_time_mcs)
  234. : m_work_time_mcs(work_time_mcs) {
  235. };
  236. void ElapsedTimeCriterion::start() {
  237. m_start_ts = m_curr_ts = utils::timestamp<std::chrono::microseconds>();
  238. }
  239. void ElapsedTimeCriterion::iter() {
  240. m_curr_ts = utils::timestamp<std::chrono::microseconds>();
  241. }
  242. bool ElapsedTimeCriterion::done() {
  243. return (m_curr_ts - m_start_ts) >= m_work_time_mcs;
  244. }
  245. class NumItersCriterion : public StopCriterion {
  246. public:
  247. NumItersCriterion(int64_t num_iters);
  248. void start() override;
  249. void iter() override;
  250. bool done() override;
  251. private:
  252. int64_t m_num_iters;
  253. int64_t m_curr_iters = 0;
  254. };
  255. NumItersCriterion::NumItersCriterion(int64_t num_iters)
  256. : m_num_iters(num_iters) {
  257. }
  258. void NumItersCriterion::start() {
  259. m_curr_iters = 0;
  260. }
  261. void NumItersCriterion::iter() {
  262. ++m_curr_iters;
  263. }
  264. bool NumItersCriterion::done() {
  265. return m_curr_iters == m_num_iters;
  266. }
  267. class PipelineBuilder {
  268. public:
  269. PipelineBuilder();
  270. void addDummy(const CallParams& call_params,
  271. const DummyParams& dummy_params);
  272. void addInfer(const CallParams& call_params,
  273. const InferParams& infer_params);
  274. void setSource(const std::string& name,
  275. std::shared_ptr<DummySource> src);
  276. void addEdge(const Edge& edge);
  277. void setMode(PLMode mode);
  278. void setDumpFilePath(const std::string& dump);
  279. void setQueueCapacity(const size_t qc);
  280. void setName(const std::string& name);
  281. void setStopCriterion(StopCriterion::Ptr stop_criterion);
  282. Pipeline::Ptr build();
  283. private:
  284. template <typename CallT>
  285. void addCall(const CallParams& call_params,
  286. CallT&& call);
  287. Pipeline::Ptr construct();
  288. template <typename K, typename V>
  289. using M = std::unordered_map<K, V>;
  290. struct State {
  291. struct NodeEdges {
  292. std::vector<Edge> input_edges;
  293. std::vector<Edge> output_edges;
  294. };
  295. M<std::string, Node::Ptr> calls_map;
  296. std::vector<Node::Ptr> all_calls;
  297. cv::gapi::GNetPackage networks;
  298. cv::gapi::GKernelPackage kernels;
  299. cv::GCompileArgs compile_args;
  300. std::shared_ptr<DummySource> src;
  301. PLMode mode = PLMode::STREAMING;
  302. std::string name;
  303. StopCriterion::Ptr stop_criterion;
  304. };
  305. std::unique_ptr<State> m_state;
  306. };
  307. PipelineBuilder::PipelineBuilder() : m_state(new State{}) { };
  308. void PipelineBuilder::addDummy(const CallParams& call_params,
  309. const DummyParams& dummy_params) {
  310. m_state->kernels.include<DummyCall::GCPUDummy>();
  311. addCall(call_params,
  312. DummyCall{dummy_params.time, dummy_params.output});
  313. }
  314. template <typename CallT>
  315. void PipelineBuilder::addCall(const CallParams& call_params,
  316. CallT&& call) {
  317. size_t num_inputs = call.numInputs();
  318. size_t num_outputs = call.numOutputs();
  319. Node::Ptr call_node(new Node{{},{},Node::Kind{CallNode{call_params,
  320. std::move(call)}}});
  321. // NB: Create placeholders for inputs.
  322. call_node->in_nodes.resize(num_inputs);
  323. // NB: Create outputs with empty data.
  324. for (size_t i = 0; i < num_outputs; ++i) {
  325. call_node->out_nodes.emplace_back(new Node{{call_node},
  326. {},
  327. Node::Kind{DataNode{}}});
  328. }
  329. auto it = m_state->calls_map.find(call_params.name);
  330. if (it != m_state->calls_map.end()) {
  331. throw std::logic_error("Node: " + call_params.name + " already exists!");
  332. }
  333. m_state->calls_map.emplace(call_params.name, call_node);
  334. m_state->all_calls.emplace_back(call_node);
  335. }
  336. void PipelineBuilder::addInfer(const CallParams& call_params,
  337. const InferParams& infer_params) {
  338. // NB: No default ctor for Params.
  339. std::unique_ptr<cv::gapi::ie::Params<cv::gapi::Generic>> pp;
  340. if (cv::util::holds_alternative<LoadPath>(infer_params.path)) {
  341. auto load_path = cv::util::get<LoadPath>(infer_params.path);
  342. pp.reset(new cv::gapi::ie::Params<cv::gapi::Generic>(call_params.name,
  343. load_path.xml,
  344. load_path.bin,
  345. infer_params.device));
  346. } else {
  347. GAPI_Assert(cv::util::holds_alternative<ImportPath>(infer_params.path));
  348. auto import_path = cv::util::get<ImportPath>(infer_params.path);
  349. pp.reset(new cv::gapi::ie::Params<cv::gapi::Generic>(call_params.name,
  350. import_path.blob,
  351. infer_params.device));
  352. }
  353. pp->pluginConfig(infer_params.config);
  354. pp->cfgInferMode(infer_params.mode);
  355. if (infer_params.out_precision) {
  356. pp->cfgOutputPrecision(infer_params.out_precision.value());
  357. }
  358. m_state->networks += cv::gapi::networks(*pp);
  359. addCall(call_params,
  360. InferCall{call_params.name,
  361. infer_params.input_layers,
  362. infer_params.output_layers});
  363. }
  364. void PipelineBuilder::addEdge(const Edge& edge) {
  365. const auto& src_it = m_state->calls_map.find(edge.src.name);
  366. if (src_it == m_state->calls_map.end()) {
  367. throw std::logic_error("Failed to find node: " + edge.src.name);
  368. }
  369. auto src_node = src_it->second;
  370. if (src_node->out_nodes.size() <= edge.src.port) {
  371. throw std::logic_error("Failed to access node: " + edge.src.name +
  372. " by out port: " + std::to_string(edge.src.port));
  373. }
  374. auto dst_it = m_state->calls_map.find(edge.dst.name);
  375. if (dst_it == m_state->calls_map.end()) {
  376. throw std::logic_error("Failed to find node: " + edge.dst.name);
  377. }
  378. auto dst_node = dst_it->second;
  379. if (dst_node->in_nodes.size() <= edge.dst.port) {
  380. throw std::logic_error("Failed to access node: " + edge.dst.name +
  381. " by in port: " + std::to_string(edge.dst.port));
  382. }
  383. auto out_data = src_node->out_nodes[edge.src.port];
  384. auto& in_data = dst_node->in_nodes[edge.dst.port];
  385. // NB: in_data != nullptr.
  386. if (!in_data.expired()) {
  387. throw std::logic_error("Node: " + edge.dst.name +
  388. " already connected by in port: " +
  389. std::to_string(edge.dst.port));
  390. }
  391. dst_node->in_nodes[edge.dst.port] = out_data;
  392. out_data->out_nodes.push_back(dst_node);
  393. }
  394. void PipelineBuilder::setSource(const std::string& name,
  395. std::shared_ptr<DummySource> src) {
  396. GAPI_Assert(!m_state->src && "Only single source pipelines are supported!");
  397. m_state->src = src;
  398. addCall(CallParams{name, 1u/*call_every_nth*/}, SourceCall{});
  399. }
  400. void PipelineBuilder::setMode(PLMode mode) {
  401. m_state->mode = mode;
  402. }
  403. void PipelineBuilder::setDumpFilePath(const std::string& dump) {
  404. m_state->compile_args.emplace_back(cv::graph_dump_path{dump});
  405. }
  406. void PipelineBuilder::setQueueCapacity(const size_t qc) {
  407. m_state->compile_args.emplace_back(cv::gapi::streaming::queue_capacity{qc});
  408. }
  409. void PipelineBuilder::setName(const std::string& name) {
  410. m_state->name = name;
  411. }
  412. void PipelineBuilder::setStopCriterion(StopCriterion::Ptr stop_criterion) {
  413. m_state->stop_criterion = std::move(stop_criterion);
  414. }
  415. static bool visit(Node::Ptr node,
  416. std::vector<Node::Ptr>& sorted,
  417. std::unordered_map<Node::Ptr, int>& visited) {
  418. if (!node) {
  419. throw std::logic_error("Found null node");
  420. }
  421. visited[node] = 1;
  422. for (auto in : node->in_nodes) {
  423. auto in_node = in.lock();
  424. if (visited[in_node] == 0) {
  425. if (visit(in_node, sorted, visited)) {
  426. return true;
  427. }
  428. } else if (visited[in_node] == 1) {
  429. return true;
  430. }
  431. }
  432. visited[node] = 2;
  433. sorted.push_back(node);
  434. return false;
  435. }
  436. static cv::optional<std::vector<Node::Ptr>>
  437. toposort(const std::vector<Node::Ptr> nodes) {
  438. std::vector<Node::Ptr> sorted;
  439. std::unordered_map<Node::Ptr, int> visited;
  440. for (auto n : nodes) {
  441. if (visit(n, sorted, visited)) {
  442. return cv::optional<std::vector<Node::Ptr>>{};
  443. }
  444. }
  445. return cv::util::make_optional(sorted);
  446. }
  447. Pipeline::Ptr PipelineBuilder::construct() {
  448. // NB: Unlike G-API, pipeline_builder_tool graph always starts with CALL node
  449. // (not data) that produce datas, so the call node which doesn't have
  450. // inputs is considered as "producer" node.
  451. //
  452. // Graph always starts with CALL node and ends with DATA node.
  453. // Graph example: [source] -> (source:0) -> [PP] -> (PP:0)
  454. //
  455. // The algorithm is quite simple:
  456. // 0. Verify that every call input node exists (connected).
  457. // 1. Sort all nodes by visiting only call nodes,
  458. // since there is no data nodes that's not connected with any call node,
  459. // it's guarantee that every node will be visited.
  460. // 2. Fillter call nodes.
  461. // 3. Go through every call node.
  462. // FIXME: Add toposort in case user passed nodes
  463. // in arbitrary order which is unlikely happened.
  464. // 4. Extract proto input from every input node
  465. // 5. Run call and get outputs
  466. // 6. If call node doesn't have inputs it means that it's "producer" node,
  467. // so collect all outputs to graph_inputs vector.
  468. // 7. Assign proto outputs to output data nodes,
  469. // so the next calls can use them as inputs.
  470. cv::GProtoArgs graph_inputs;
  471. cv::GProtoArgs graph_outputs;
  472. // 0. Verify that every call input node exists (connected).
  473. for (auto call_node : m_state->all_calls) {
  474. for (size_t i = 0; i < call_node->in_nodes.size(); ++i) {
  475. const auto& in_data_node = call_node->in_nodes[i];
  476. // NB: in_data_node == nullptr.
  477. if (in_data_node.expired()) {
  478. const auto& call = cv::util::get<CallNode>(call_node->kind);
  479. throw std::logic_error(
  480. "Node: " + call.params.name + " in Pipeline: " + m_state->name +
  481. " has dangling input by in port: " + std::to_string(i));
  482. }
  483. }
  484. }
  485. // (0) Sort all nodes;
  486. auto has_sorted = toposort(m_state->all_calls);
  487. if (!has_sorted) {
  488. throw std::logic_error(
  489. "Pipeline: " + m_state->name + " has cyclic dependencies") ;
  490. }
  491. auto& sorted = has_sorted.value();
  492. // (1). Fillter call nodes.
  493. std::vector<Node::Ptr> sorted_calls;
  494. for (auto n : sorted) {
  495. if (cv::util::holds_alternative<CallNode>(n->kind)) {
  496. sorted_calls.push_back(n);
  497. }
  498. }
  499. m_state->kernels.include<SubGraphCall::SubGraphImpl>();
  500. m_state->compile_args.emplace_back(m_state->networks);
  501. m_state->compile_args.emplace_back(m_state->kernels);
  502. // (2). Go through every call node.
  503. for (auto call_node : sorted_calls) {
  504. auto& call = cv::util::get<CallNode>(call_node->kind);
  505. cv::GProtoArgs outputs;
  506. cv::GProtoArgs inputs;
  507. for (size_t i = 0; i < call_node->in_nodes.size(); ++i) {
  508. auto in_node = call_node->in_nodes.at(i);
  509. auto in_data = cv::util::get<DataNode>(in_node.lock()->kind);
  510. if (!in_data.arg.has_value()) {
  511. throw std::logic_error("data hasn't been provided");
  512. }
  513. // (3). Extract proto input from every input node.
  514. inputs.push_back(in_data.arg.value());
  515. }
  516. // NB: If node shouldn't be called on each iterations,
  517. // it should be wrapped into subgraph which is able to skip calling.
  518. if (call.params.call_every_nth != 1u) {
  519. // FIXME: Limitation of the subgraph operation (<GMat(GMat)>).
  520. // G-API doesn't support dynamic number of inputs/outputs.
  521. if (inputs.size() > 1u) {
  522. throw std::logic_error(
  523. "skip_frame_nth is supported only for single input subgraphs\n"
  524. "Current subgraph has " + std::to_string(inputs.size()) + " inputs");
  525. }
  526. if (outputs.size() > 1u) {
  527. throw std::logic_error(
  528. "skip_frame_nth is supported only for single output subgraphs\n"
  529. "Current subgraph has " + std::to_string(inputs.size()) + " outputs");
  530. }
  531. // FIXME: Should be generalized.
  532. // Now every subgraph contains only single node
  533. // which has single input/output.
  534. GAPI_Assert(cv::util::holds_alternative<cv::GMat>(inputs[0]));
  535. cv::GProtoArgs subgr_inputs{cv::GProtoArg{cv::GMat()}};
  536. cv::GProtoArgs subgr_outputs;
  537. call.run(subgr_inputs, subgr_outputs);
  538. auto comp = cv::GComputation(cv::GProtoInputArgs{subgr_inputs},
  539. cv::GProtoOutputArgs{subgr_outputs});
  540. call = CallNode{CallParams{call.params.name, 1u/*call_every_nth*/},
  541. SubGraphCall{std::move(comp),
  542. m_state->compile_args,
  543. call.params.call_every_nth}};
  544. }
  545. // (4). Run call and get outputs.
  546. call.run(inputs, outputs);
  547. // (5) If call node doesn't have inputs
  548. // it means that it's input producer node (Source).
  549. if (call_node->in_nodes.empty()) {
  550. for (auto out : outputs) {
  551. graph_inputs.push_back(out);
  552. }
  553. }
  554. // (6). Assign proto outputs to output data nodes,
  555. // so the next calls can use them as inputs.
  556. GAPI_Assert(outputs.size() == call_node->out_nodes.size());
  557. for (size_t i = 0; i < outputs.size(); ++i) {
  558. auto out_node = call_node->out_nodes[i];
  559. auto& out_data = cv::util::get<DataNode>(out_node->kind);
  560. out_data.arg = cv::util::make_optional(outputs[i]);
  561. if (out_node->out_nodes.empty()) {
  562. graph_outputs.push_back(out_data.arg.value());
  563. }
  564. }
  565. }
  566. GAPI_Assert(m_state->stop_criterion);
  567. GAPI_Assert(graph_inputs.size() == 1);
  568. GAPI_Assert(cv::util::holds_alternative<cv::GMat>(graph_inputs[0]));
  569. // FIXME: Handle GFrame when NV12 comes.
  570. const auto& graph_input = cv::util::get<cv::GMat>(graph_inputs[0]);
  571. graph_outputs.emplace_back(
  572. cv::gapi::streaming::timestamp(graph_input).strip());
  573. graph_outputs.emplace_back(
  574. cv::gapi::streaming::seq_id(graph_input).strip());
  575. if (m_state->mode == PLMode::STREAMING) {
  576. return std::make_shared<StreamingPipeline>(std::move(m_state->name),
  577. cv::GComputation(
  578. cv::GProtoInputArgs{graph_inputs},
  579. cv::GProtoOutputArgs{graph_outputs}),
  580. std::move(m_state->src),
  581. std::move(m_state->stop_criterion),
  582. std::move(m_state->compile_args),
  583. graph_outputs.size());
  584. }
  585. GAPI_Assert(m_state->mode == PLMode::REGULAR);
  586. return std::make_shared<RegularPipeline>(std::move(m_state->name),
  587. cv::GComputation(
  588. cv::GProtoInputArgs{graph_inputs},
  589. cv::GProtoOutputArgs{graph_outputs}),
  590. std::move(m_state->src),
  591. std::move(m_state->stop_criterion),
  592. std::move(m_state->compile_args),
  593. graph_outputs.size());
  594. }
  595. Pipeline::Ptr PipelineBuilder::build() {
  596. auto pipeline = construct();
  597. m_state.reset(new State{});
  598. return pipeline;
  599. }
  600. #endif // OPENCV_GAPI_PIPELINE_MODELING_TOOL_PIPELINE_BUILDER_HPP