First attempt
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
#include <kpn/kpn.hpp>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
// A minimal linear pipeline: producer → double → print
|
||||
//
|
||||
// [produce] --int--> [double_it] --int--> [print_it]
|
||||
|
||||
static int produce() { return 42; }
|
||||
static int double_it(int x) { return x * 2; }
|
||||
static void print_it(int x) { std::cout << "result: " << x << '\n'; }
|
||||
|
||||
int main() {
|
||||
using namespace kpn;
|
||||
|
||||
auto src = make_node<produce>(5);
|
||||
auto dbl = make_node<double_it>(5);
|
||||
auto sink = make_node<print_it>(5);
|
||||
|
||||
// Wire channels
|
||||
auto& dbl_in = dbl.input_channel<0>();
|
||||
auto& sink_in = sink.input_channel<0>();
|
||||
src.set_output_channel<0>(&dbl_in);
|
||||
dbl.set_output_channel<0>(&sink_in);
|
||||
|
||||
Network net;
|
||||
net.add("src", src)
|
||||
.add("dbl", dbl)
|
||||
.add("sink", sink)
|
||||
.connect("src", src.output<0>(), "dbl", dbl.input<0>())
|
||||
.connect("dbl", dbl.output<0>(), "sink", sink.input<0>())
|
||||
.build();
|
||||
|
||||
net.start();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
net.stop();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#include <kpn/kpn.hpp>
|
||||
// TODO: implement example
|
||||
int main() { return 0; }
|
||||
@@ -0,0 +1,3 @@
|
||||
#include <kpn/kpn.hpp>
|
||||
// TODO: implement example
|
||||
int main() { return 0; }
|
||||
@@ -0,0 +1,3 @@
|
||||
#include <kpn/kpn.hpp>
|
||||
// TODO: implement example
|
||||
int main() { return 0; }
|
||||
@@ -0,0 +1,3 @@
|
||||
#include <kpn/kpn.hpp>
|
||||
// TODO: implement example
|
||||
int main() { return 0; }
|
||||
@@ -0,0 +1,3 @@
|
||||
#include <kpn/kpn.hpp>
|
||||
// TODO: implement example
|
||||
int main() { return 0; }
|
||||
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
07_python_network — hello pipeline with a Python node in the middle.
|
||||
|
||||
Graph:
|
||||
[ProduceNode] --int--> [py_double] --int--> [PrintItNode]
|
||||
|
||||
ProduceNode and PrintItNode are C++ nodes wrapped in VariantNodeWrapper.
|
||||
py_double is a pure Python callable — doubles its input using Python arithmetic.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
sys.path.insert(0, "build/python")
|
||||
|
||||
import kpn_python as kpn
|
||||
|
||||
def py_double(x: int) -> int:
|
||||
return x * 2
|
||||
|
||||
net = kpn.Network()
|
||||
|
||||
net.add("src", kpn.make_produce())
|
||||
net.add_node("dbl", py_double, inputs=["int"], outputs=["int"])
|
||||
net.add("sink", kpn.make_print_it())
|
||||
|
||||
net.connect("src", 0, "dbl", 0)
|
||||
net.connect("dbl", 0, "sink", 0)
|
||||
|
||||
net.build()
|
||||
net.start()
|
||||
time.sleep(0.1)
|
||||
net.stop()
|
||||
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
08_python_subport — tap a C++ node's output from Python using net.read().
|
||||
|
||||
Graph:
|
||||
[ProduceNode] --int--> [DoubleItNode] --int--> (tapped by net.read())
|
||||
|
||||
The sink is Python: instead of connecting a PrintItNode, we call net.read()
|
||||
to pull values out of DoubleItNode's output directly into Python.
|
||||
We also demonstrate net.write() by injecting a value into DoubleItNode's input.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
sys.path.insert(0, "build/python")
|
||||
|
||||
import kpn_python as kpn
|
||||
|
||||
net = kpn.Network()
|
||||
|
||||
net.add("src", kpn.make_produce())
|
||||
net.add("dbl", kpn.make_double_it())
|
||||
|
||||
net.connect("src", 0, "dbl", 0)
|
||||
net.build()
|
||||
net.start()
|
||||
|
||||
# Collect a few values from DoubleItNode's output via Python tap
|
||||
results = []
|
||||
for _ in range(5):
|
||||
val = net.read("dbl", 0)
|
||||
results.append(val)
|
||||
|
||||
net.stop()
|
||||
|
||||
print("values read from C++ DoubleItNode output:", results)
|
||||
assert all(v == 84 for v in results), f"expected all 84, got {results}"
|
||||
print("all correct (42 * 2 = 84)")
|
||||
@@ -0,0 +1,53 @@
|
||||
#include <opencv2/videoio.hpp>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
|
||||
static void bench(const char* name, int device, int backend, int W, int H, int frames) {
|
||||
cv::VideoCapture cap(device, backend);
|
||||
if (!cap.isOpened()) {
|
||||
std::printf("%-12s failed to open /dev/video%d\n", name, device);
|
||||
return;
|
||||
}
|
||||
cap.set(cv::CAP_PROP_FRAME_WIDTH, W);
|
||||
cap.set(cv::CAP_PROP_FRAME_HEIGHT, H);
|
||||
|
||||
// Warm up
|
||||
for (int i = 0; i < 5; ++i) cap.grab();
|
||||
|
||||
std::vector<double> times;
|
||||
times.reserve(frames);
|
||||
for (int i = 0; i < frames; ++i) {
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
bool ok = cap.grab();
|
||||
double ms = std::chrono::duration<double, std::milli>(
|
||||
std::chrono::steady_clock::now() - t0).count();
|
||||
if (ok) times.push_back(ms);
|
||||
}
|
||||
cap.release();
|
||||
|
||||
if (times.empty()) {
|
||||
std::printf("%-12s no frames captured\n", name);
|
||||
return;
|
||||
}
|
||||
std::sort(times.begin(), times.end());
|
||||
double avg = std::accumulate(times.begin(), times.end(), 0.0) / times.size();
|
||||
double mn = times.front();
|
||||
double mx = times.back();
|
||||
double p95 = times[times.size() * 95 / 100];
|
||||
std::printf("%-12s avg=%6.1fms min=%5.1fms p95=%6.1fms max=%6.1fms (%zu frames)\n",
|
||||
name, avg, mn, p95, mx, times.size());
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int device = argc > 1 ? std::atoi(argv[1]) : 0;
|
||||
int frames = argc > 2 ? std::atoi(argv[2]) : 60;
|
||||
int W = 1920, H = 1080;
|
||||
|
||||
std::printf("Benchmarking /dev/video%d at %dx%d, %d frames each\n\n", device, W, H, frames);
|
||||
bench("V4L2", device, cv::CAP_V4L2, W, H, frames);
|
||||
bench("GStreamer", device, cv::CAP_GSTREAMER, W, H, frames);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
#include <kpn/kpn.hpp>
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <opencv2/highgui.hpp>
|
||||
#include <opencv2/videoio.hpp>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
// ── Cell-shading pipeline ─────────────────────────────────────────────────────
|
||||
//
|
||||
// [capture] --"colour"--> [quantise] ──────────────────────────┐
|
||||
// ├──> [composite] ──"result"──┐
|
||||
// [capture] --"grey"---> [to_gray] --> [edges] ──"edges"───────┘ │
|
||||
// └──────────────────────────────"edges"──────────┴──> [display]
|
||||
//
|
||||
// DisplayNode derives from MainThreadNode<> — two inputs (composite + raw edges),
|
||||
// two windows opened in the constructor. step() is called on the main thread.
|
||||
|
||||
// ── Gradient base for synthetic pattern ──────────────────────────────────────
|
||||
|
||||
static cv::Mat make_gradient(int W, int H) {
|
||||
cv::Mat xr(H, W, CV_8UC1), yg(H, W, CV_8UC1), b(H, W, CV_8UC1, cv::Scalar(128));
|
||||
for (int x = 0; x < W; ++x) xr.col(x).setTo(x * 255 / W);
|
||||
for (int y = 0; y < H; ++y) yg.row(y).setTo(y * 255 / H);
|
||||
cv::Mat channels[3] = {b, yg, xr};
|
||||
cv::Mat grad;
|
||||
cv::merge(channels, 3, grad);
|
||||
return grad;
|
||||
}
|
||||
|
||||
// ── Pipeline functions ────────────────────────────────────────────────────────
|
||||
|
||||
static std::tuple<cv::Mat, cv::Mat> capture() {
|
||||
constexpr int W = 640, H = 480;
|
||||
static cv::VideoCapture cap;
|
||||
static bool opened = false;
|
||||
if (!opened) {
|
||||
opened = true;
|
||||
cap.open(0, cv::CAP_V4L2);
|
||||
if (cap.isOpened()) {
|
||||
cap.set(cv::CAP_PROP_FRAME_WIDTH, W);
|
||||
cap.set(cv::CAP_PROP_FRAME_HEIGHT, H);
|
||||
} else {
|
||||
std::cerr << "[capture] no webcam — using synthetic animated pattern\n";
|
||||
}
|
||||
}
|
||||
|
||||
cv::Mat frame;
|
||||
if (cap.isOpened()) {
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
cap >> frame;
|
||||
auto elapsed = std::chrono::steady_clock::now() - t0;
|
||||
if (elapsed < std::chrono::milliseconds(20))
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(33) - elapsed);
|
||||
if (frame.empty()) frame = cv::Mat::zeros(H, W, CV_8UC3);
|
||||
} else {
|
||||
static int tick = 0;
|
||||
static cv::Mat grad = make_gradient(W, H);
|
||||
++tick;
|
||||
frame = grad.clone();
|
||||
int r = 150 + (tick % 80) * 4;
|
||||
cv::circle(frame, {W/2, H/2}, r, {255, 200, 0}, -1);
|
||||
cv::circle(frame, {W/2, H/2}, r / 2, { 0, 128, 255}, -1);
|
||||
cv::circle(frame, {W*2/5, H*2/5}, r / 3, {200, 0, 200}, -1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(33));
|
||||
}
|
||||
return {frame.clone(), frame.clone()};
|
||||
}
|
||||
|
||||
static cv::Mat to_gray(cv::Mat bgr) {
|
||||
cv::Mat gray;
|
||||
cv::cvtColor(bgr, gray, cv::COLOR_BGR2GRAY);
|
||||
return gray;
|
||||
}
|
||||
|
||||
static cv::Mat edges_fn(cv::Mat gray) {
|
||||
cv::Mat blurred, mask;
|
||||
cv::GaussianBlur(gray, blurred, {5, 5}, 0);
|
||||
cv::Canny(blurred, mask, 50, 150);
|
||||
return mask;
|
||||
}
|
||||
|
||||
static cv::Mat quantise(cv::Mat bgr) {
|
||||
constexpr int levels = 4;
|
||||
constexpr double step = 256.0 / levels;
|
||||
static const cv::Mat lut = []() {
|
||||
cv::Mat l(1, 256, CV_8UC1);
|
||||
for (int i = 0; i < 256; ++i)
|
||||
l.at<uchar>(i) = cv::saturate_cast<uchar>(
|
||||
std::floor(i / step) * step + step / 2.0);
|
||||
return l;
|
||||
}();
|
||||
cv::Mat out;
|
||||
cv::LUT(bgr, lut, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Returns both the composite frame and the original edge mask so downstream
|
||||
// nodes (display) can receive both without fan-out on the edges channel.
|
||||
static std::tuple<cv::Mat, cv::Mat> composite(cv::Mat edge_mask, cv::Mat colour) {
|
||||
cv::Mat result = colour.clone();
|
||||
result.setTo(cv::Scalar(0, 0, 0), edge_mask);
|
||||
return {result, edge_mask};
|
||||
}
|
||||
|
||||
// ── DisplayNode ───────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Two inputs: the cell-shaded composite frame and the raw edge mask.
|
||||
// MainThreadNode<> provides channel ownership, INode boilerplate, and stats.
|
||||
// The constructor opens both windows on the main thread (Wayland requirement).
|
||||
// operator() is called by step() whenever both channels have a frame ready.
|
||||
|
||||
class DisplayNode : public kpn::MainThreadNode<DisplayNode,
|
||||
kpn::in<"composite", "edges">,
|
||||
cv::Mat, cv::Mat> {
|
||||
public:
|
||||
DisplayNode() : MainThreadNode(8) {
|
||||
cv::namedWindow("Cell Shade", cv::WINDOW_NORMAL);
|
||||
cv::namedWindow("Edge Mask", cv::WINDOW_NORMAL);
|
||||
cv::resizeWindow("Cell Shade", 1280, 720);
|
||||
cv::resizeWindow("Edge Mask", 640, 360);
|
||||
}
|
||||
|
||||
~DisplayNode() { cv::destroyAllWindows(); }
|
||||
|
||||
bool operator()(cv::Mat composite, cv::Mat edges) {
|
||||
cv::imshow("Cell Shade", composite);
|
||||
cv::Mat edges_bgr;
|
||||
cv::cvtColor(edges, edges_bgr, cv::COLOR_GRAY2BGR);
|
||||
cv::imshow("Edge Mask", edges_bgr);
|
||||
int key = cv::waitKey(1);
|
||||
if (key == 'q' || key == 27) return false;
|
||||
return window_open("Cell Shade") && window_open("Edge Mask");
|
||||
}
|
||||
|
||||
private:
|
||||
static bool window_open(const char* name) {
|
||||
try { return cv::getWindowProperty(name, cv::WND_PROP_VISIBLE) >= 1; }
|
||||
catch (const cv::Exception&) { return false; }
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
int main() {
|
||||
using namespace kpn;
|
||||
|
||||
auto src = make_node<capture> (out<"colour","grey">{}, 8);
|
||||
auto gray_node = make_node<to_gray> (in<"bgr">{}, out<"gray">{}, 8);
|
||||
auto edge_node = make_node<edges_fn> (in<"gray">{}, out<"edges">{}, 8);
|
||||
auto quant = make_node<quantise> (in<"bgr">{}, out<"quantised">{}, 8);
|
||||
auto comp = make_node<composite>(in<"edges","colour">{}, out<"result","edges">{}, 8);
|
||||
|
||||
// DisplayNode: two windows opened in constructor, step() drives main thread.
|
||||
DisplayNode disp;
|
||||
|
||||
Network net;
|
||||
net.add("src", src)
|
||||
.add("gray", gray_node)
|
||||
.add("edges", edge_node)
|
||||
.add("quant", quant)
|
||||
.add("comp", comp)
|
||||
.add("display", disp)
|
||||
.connect("src", src.template output<"colour">(), "quant", quant.template input<"bgr">())
|
||||
.connect("quant", quant.template output<"quantised">(), "comp", comp.template input<"colour">())
|
||||
.connect("src", src.template output<"grey">(), "gray", gray_node.template input<"bgr">())
|
||||
.connect("gray", gray_node.template output<"gray">(), "edges", edge_node.template input<"gray">())
|
||||
.connect("edges", edge_node.template output<"edges">(), "comp", comp.template input<"edges">())
|
||||
.connect("comp", comp.template output<"result">(), "display", disp.template input<"composite">())
|
||||
.connect("comp", comp.template output<"edges">(), "display", disp.template input<"edges">())
|
||||
.build();
|
||||
|
||||
net.set_watchdog_interval(std::chrono::milliseconds(5000));
|
||||
net.set_web_debug_port(9090);
|
||||
|
||||
std::cout << "Cell-shading pipeline running. Press 'q' to stop.\n";
|
||||
std::cout << "Web debug UI: http://localhost:9090\n";
|
||||
|
||||
net.start();
|
||||
|
||||
// Main thread drives display — imshow/waitKey stay on the GUI thread.
|
||||
// step() returns false when operator() returns false (q pressed / window closed).
|
||||
while (disp.step())
|
||||
cv::waitKey(8); // yield event loop when no frame ready
|
||||
|
||||
net.stop();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
function(kpn_example name)
|
||||
add_executable(${name} ${name}/main.cpp)
|
||||
target_link_libraries(${name} PRIVATE kpn)
|
||||
endfunction()
|
||||
|
||||
kpn_example(01_hello_pipeline)
|
||||
kpn_example(02_named_ports)
|
||||
kpn_example(03_multi_output)
|
||||
kpn_example(04_storage_policy)
|
||||
kpn_example(05_error_handling)
|
||||
kpn_example(06_watchdog)
|
||||
# 07 and 08 require the Python bindings — only add if built
|
||||
if(KPN_BUILD_PYTHON)
|
||||
# These are Python scripts, not compiled targets — installed alongside kpn_python
|
||||
endif()
|
||||
|
||||
# 09 requires OpenCV — only build if found
|
||||
find_package(OpenCV QUIET COMPONENTS core imgproc highgui videoio)
|
||||
if(OpenCV_FOUND)
|
||||
add_executable(09_opencv_cellshade 09_opencv_cellshade/main.cpp)
|
||||
target_link_libraries(09_opencv_cellshade PRIVATE kpn ${OpenCV_LIBS})
|
||||
if(KPN_WEB_DEBUG)
|
||||
kpn_target_enable_web_debug(09_opencv_cellshade)
|
||||
endif()
|
||||
message(STATUS "KPN++ example 09_opencv_cellshade: OpenCV ${OpenCV_VERSION} found — building")
|
||||
else()
|
||||
message(STATUS "KPN++ example 09_opencv_cellshade: OpenCV not found — skipping")
|
||||
endif()
|
||||
Reference in New Issue
Block a user