{ "app_name": "Applications/Example_Application.json", "ops": [ { "id": "69bd3deeff031ee6e72c0a9d-1776345526320", "name": "pluto_source", "type": "source", "description": "PlutoSDR RX source operator. Acquires raw IQ samples from an Analog Devices PlutoSDR (AD9361) via libiio and emits them as shared complex vectors.", "position": { "x": 68.5360685699228, "y": 357.7951264299131 }, "class_name": "PlutoSourceOp", "is_builtin": false, "inputs": [], "outputs": [ { "name": "iq_samples", "type": "std::shared_ptr>" } ], "specs": [ { "parameter": "ip_addr_", "key": "ip_addr", "headline": "Pluto IP Address", "description": "IP address of the PlutoSDR device", "type": "std::string", "default": "192.168.3.1" }, { "parameter": "bandwidth_mhz_", "key": "bandwidth_mhz", "headline": "RF bandwidth", "description": "RF analog bandwidth in MHz", "type": "double", "default": 2 }, { "parameter": "sample_rate_mhz_", "key": "sample_rate_mhz", "headline": "RX sample rate", "description": "RX baseband sample rate in MS/s", "type": "double", "default": 2.5 }, { "parameter": "lo_frequency_ghz_", "key": "lo_frequency_ghz", "headline": "RX LO Frequency", "description": "RF local oscillator frequency in GHz", "type": "double", "default": 0.0999 }, { "parameter": "rx_gain_db_", "key": "rx_gain_db", "headline": "RX Gain", "description": "Receiver gain in dB", "type": "double", "default": 20 }, { "parameter": "rf_port_", "key": "rf_port", "headline": "RF port", "description": "RX channel index", "type": "int", "default": 0 }, { "parameter": "buffer_size_", "key": "buffer_size", "headline": "Buffer size", "description": "Number of IQ sample pairs per buffer", "type": "int", "default": 4096 }, { "parameter": "poll_rate_ms_", "key": "poll_rate_ms", "headline": "Poll Rate", "description": "Minimum milliseconds between buffer refills", "type": "int", "default": 10 } ], "dependencies": [ "#include ", "#include ", "#include ", "#include " ], "private_members": [ "struct iio_context* context_ = nullptr;", "struct iio_device* rx_device_ = nullptr;", "struct iio_channel* ch_i_ = nullptr;", "struct iio_channel* ch_q_ = nullptr;", "struct iio_buffer* buffer_ = nullptr;", "std::chrono::high_resolution_clock::time_point last_read_time_;", "std::atomic runtime_rx_freq_ghz_{3.425};", "std::atomic runtime_rx_gain_db_{30.0};", "std::atomic rx_params_dirty_{false};" ], "has_initialize": true, "initialize_body": "HOLOSCAN_LOG_INFO(\"Initializing Pluto RX stream\");\n Operator::initialize();\n runtime_rx_freq_ghz_.store(lo_frequency_ghz_.get());\n runtime_rx_gain_db_.store(rx_gain_db_.get());\n struct stream_cfg rxcfg {};\n rxcfg.bw_hz = MHZ(bandwidth_mhz_.get());\n rxcfg.fs_hz = MHZ(sample_rate_mhz_.get());\n rxcfg.lo_hz = GHZ(lo_frequency_ghz_.get());\n rxcfg.rfport = \"A_BALANCED\";\n const int port_index = rf_port_.get();\n std::string uri = \"ip:\" + ip_addr_.get();\n context_ = iio_create_context_from_uri(uri.c_str());\n if (!context_) return;\n if (!get_ad9361_stream_dev(context_, RX, &rx_device_)) return;\n if (!cfg_ad9361_streaming_ch(context_, &rxcfg, RX, port_index)) return;\n get_ad9361_stream_ch(RX, rx_device_, port_index, &ch_i_);\n get_ad9361_stream_ch(RX, rx_device_, port_index + 1, &ch_q_);\n iio_channel_enable(ch_i_);\n iio_channel_enable(ch_q_);\n buffer_ = iio_device_create_buffer(rx_device_, buffer_size_.get(), false);", "compute_body": "auto samples_ptr = std::make_shared>();\n samples_ptr->reserve(buffer_size_.get());\n if (context_ && buffer_) {\n iio_buffer_refill(buffer_);\n const size_t step = iio_buffer_step(buffer_);\n const char* start = static_cast(iio_buffer_first(buffer_, ch_i_));\n const char* end = static_cast(iio_buffer_end(buffer_));\n for (const char* p = start; p < end; p += step) {\n const auto raw = reinterpret_cast(p);\n samples_ptr->emplace_back(raw[0], raw[1]);\n }\n } else {\n for (int i = 0; i < buffer_size_.get(); ++i) {\n samples_ptr->emplace_back(static_cast(rand()%100), static_cast(rand()%100));\n }\n }\n op_output.emit(samples_ptr, \"iq_samples\");", "class": "", "compute": "" }, { "id": "69bd3deeff031ee6e72c0aa0-1776345543621", "name": "preprocessor", "type": "preprocessing", "description": "Converts complex IQ samples into a GXF tensor suitable for ML inference. Separates real and imaginary components into interleaved float format with shape [1, 2, N].", "position": { "x": 414.64197818208436, "y": 544.7366288284782 }, "class_name": "PreprocessorOp", "is_builtin": false, "inputs": [ { "name": "iq_samples", "type": "std::shared_ptr>" } ], "outputs": [ { "name": "in_tensor", "type": "gxf::Entity" } ], "specs": [ { "parameter": "allocator_", "key": "allocator", "headline": "Alloc", "description": "Memory allocator for tensor creation", "type": "std::shared_ptr", "default": "pool_resource" }, { "parameter": "in_tensor_name_", "key": "in_tensor_name", "headline": "Name", "description": "Name of the output tensor in the GXF entity", "type": "std::string", "default": "input" } ], "dependencies": [ "#include " ], "private_members": [], "has_initialize": false, "initialize_body": null, "compute_body": "auto maybe_s = op_input.receive>>(\"iq_samples\"); if(!maybe_s) return; const auto& s = *maybe_s.value(); auto [re,im] = convert_and_normalize_separate(s); auto ent = nvidia::gxf::Entity::New(context.context()).value(); auto tensor = ent.add(in_tensor_name_.get().c_str()).value(); auto alloc = nvidia::gxf::Handle::Create(context.context(), allocator_->gxf_cid()).value(); tensor->template reshape(nvidia::gxf::Shape({1,2,static_cast(s.size())}), nvidia::gxf::MemoryStorageType::kHost, alloc); float* d = tensor->template data().value(); for(size_t i=0;i>" } ], "outputs": [ { "name": "new_rows", "type": "std::shared_ptr>" }, { "name": "spectrogram_width", "type": "int" }, { "name": "spectrogram_height", "type": "int" }, { "name": "offset_row", "type": "int" }, { "name": "num_new_rows", "type": "int" } ], "specs": [ { "parameter": "fft_size_", "key": "fft_size", "headline": "FFT Size", "description": "Number of FFT points", "type": "int", "default": 2048 }, { "parameter": "history_depth_", "key": "history_depth", "headline": "History Depth", "description": "Number of rows in the circular waterfall buffer", "type": "int", "default": 256 }, { "parameter": "hop_size_", "key": "hop_size", "headline": "Hop Size", "description": "Samples between successive FFT frames", "type": "int", "default": 2048 }, { "parameter": "db_min_", "key": "db_min", "headline": "Min dB", "description": "Floor of the dB dynamic range for colormap mapping", "type": "float", "default": -80 }, { "parameter": "db_max_", "key": "db_max", "headline": "Max dB", "description": "Ceiling of the dB dynamic range for colormap mapping", "type": "float", "default": 0 }, { "parameter": "input_scale_", "key": "input_scale", "headline": "Input Scale", "description": "Scale factor for raw input samples, baked into window coefficients (e.g. 1/2048 for PlutoSDR int16, 1.0 for normalized float)", "type": "float", "default": 0.000488281 } ], "dependencies": [ "#include ", "#include ", "#include ", "#include ", "#include ", "#define M_PI 3.14159265358979323846", "#include " ], "private_members": [ "fftwf_plan fft_plan_ = nullptr;", "fftwf_complex* fft_in_ = nullptr;", "fftwf_complex* fft_out_ = nullptr;", "std::vector window_coeffs_;", "std::vector texture_data_;", "std::vector colormap_lut_;", "int write_row_ = 0;", "static float fast_log2f(float x) { union { float f; uint32_t i; } u; u.f = x; return (float)((int32_t)u.i - 1064866805) * 8.2629582881927490e-8f; }", "int prev_write_row_ = 0;" ], "has_initialize": true, "initialize_body": "holoscan::Operator::initialize(); int n = fft_size_.get(); int depth = history_depth_.get(); fft_in_ = fftwf_alloc_complex(n); fft_out_ = fftwf_alloc_complex(n); fft_plan_ = fftwf_plan_dft_1d(n, fft_in_, fft_out_, FFTW_FORWARD, FFTW_MEASURE); window_coeffs_.resize(n); const float adc_scale = input_scale_.get(); for (int i = 0; i < n; ++i) { window_coeffs_[i] = 0.5f * (1.0f - std::cos(2.0f * M_PI * i / (n - 1))) * adc_scale; } write_row_ = 0; /* Purple-to-yellow (Plasma) colormap */ colormap_lut_.resize(1024); const float stops[][4] = { {0.00f, 13.0f, 8.0f, 135.0f}, {0.25f, 126.0f, 3.0f, 168.0f}, {0.50f, 203.0f, 70.0f, 121.0f}, {0.75f, 249.0f, 149.0f, 64.0f}, {1.00f, 240.0f, 249.0f, 33.0f} }; const int ns = 5; for (int i = 0; i < 1024; ++i) { float t = i / 1023.0f; int seg = 0; for (int s = 0; s < ns - 1; ++s) { if (t >= stops[s][0]) seg = s; } float lt = (t - stops[seg][0]) / (stops[seg+1][0] - stops[seg][0]); lt = std::clamp(lt, 0.0f, 1.0f); uint8_t r = static_cast(stops[seg][1] + (stops[seg+1][1] - stops[seg][1]) * lt); uint8_t g = static_cast(stops[seg][2] + (stops[seg+1][2] - stops[seg][2]) * lt); uint8_t b = static_cast(stops[seg][3] + (stops[seg+1][3] - stops[seg][3]) * lt); colormap_lut_[i] = (255u << 24) | (b << 16) | (g << 8) | r; } texture_data_.assign(n * depth * 4, 0); HOLOSCAN_LOG_INFO(\"FFTW plan created for N={}\", n);", "compute_body": "auto s_in = op_input.receive>>(\"iq_samples\"); if (!s_in || s_in.value()->empty()) return; const auto& samples = *s_in.value(); const int n = fft_size_.get(); const int hop = hop_size_.get(); const int depth = history_depth_.get(); const float db_min = db_min_.get(); const float db_range = db_max_.get() - db_min; const float inv_n2 = 1.0f / (float)(n * n); const float log2_to_db = 3.01029995663981f; prev_write_row_ = write_row_; for (size_t start = 0; start + n <= samples.size(); start += hop) { for (int i = 0; i < n; ++i) { fft_in_[i][0] = samples[start + i].real() * window_coeffs_[i]; fft_in_[i][1] = samples[start + i].imag() * window_coeffs_[i]; } fftwf_execute(fft_plan_); uint32_t* row_ptr = reinterpret_cast(texture_data_.data()) + (write_row_ * n); for (int i = 0; i < n; ++i) { int si = (i + n/2) % n; float re = fft_out_[si][0], im = fft_out_[si][1]; float mag_sq = (re * re + im * im) * inv_n2; float db = log2_to_db * fast_log2f(mag_sq + 1e-20f); float norm = std::clamp((db - db_min) / db_range, 0.0f, 1.0f); row_ptr[i] = colormap_lut_[static_cast(norm * 1023.0f)]; } write_row_ = (write_row_ + 1) % depth; } /* Emit only the new rows */ int num_new = (write_row_ - prev_write_row_ + depth) % depth; if (num_new > 0) { auto new_rows = std::make_shared>(num_new * n * 4); for (int r = 0; r < num_new; ++r) { int src_row = (prev_write_row_ + r) % depth; std::memcpy(new_rows->data() + r * n * 4, texture_data_.data() + src_row * n * 4, n * 4); } op_output.emit(new_rows, \"new_rows\"); op_output.emit(n, \"spectrogram_width\"); op_output.emit(depth, \"spectrogram_height\"); op_output.emit(write_row_, \"offset_row\"); op_output.emit(num_new, \"num_new_rows\"); }", "class": "", "compute": "" }, { "id": "69bd3deeff031ee6e72c0a9b-1776345577288", "name": "inference_builtin", "type": "inference", "description": "Built-in Holoscan InferenceOp. Runs ONNX Runtime or TensorRT inference on GXF tensor entities.", "position": { "x": 651.8201808383262, "y": 542.6597535841762 }, "class_name": "InferenceOp", "is_builtin": true, "inputs": [ { "name": "receivers", "type": "gxf::Entity" } ], "outputs": [ { "name": "transmitter", "type": "gxf::Entity" } ], "specs": [ { "parameter": "backend", "key": "backend", "headline": "Backend", "description": "Inference backend: onnxrt or trt", "type": "std::string", "default": "onnxrt" }, { "parameter": "model_path_map_", "key": "model_path_map", "headline": "Model Path Map", "description": "Map of model name to ONNX/TRT model file path", "type": "std::map", "default": "model.onnx" }, { "parameter": "pre_processor_map_", "key": "pre_processor_map", "headline": "Pre-processor Map", "description": "Map of model name to input tensor names", "type": "std::map>", "default": "input" }, { "parameter": "inference_map_", "key": "inference_map", "headline": "Inference Map", "description": "Map of model name to output tensor names", "type": "std::map>", "default": "output" }, { "parameter": "input_on_cuda", "key": "input_on_cuda", "headline": "Input on CUDA", "description": null, "type": "bool", "default": false }, { "parameter": "output_on_cuda", "key": "output_on_cuda", "headline": "Output on CUDA", "description": null, "type": "bool", "default": false }, { "parameter": "transmit_on_cuda", "key": "transmit_on_cuda", "headline": "Transmit on CUDA", "description": null, "type": "bool", "default": false }, { "parameter": "allocator", "key": "allocator", "headline": "Allocator", "description": null, "type": "std::shared_ptr", "default": "pool_resource" } ], "dependencies": [], "private_members": [], "has_initialize": false, "initialize_body": null, "compute_body": "", "class": "", "compute": "" }, { "id": "69bd3deeff031ee6e72c0a9f-1776345584342", "name": "postprocessor", "type": "postprocessing", "description": "Extracts classification logits from inference output tensor, applies softmax, and emits probabilities as a shared float vector. Supports both CPU and CUDA tensors.", "position": { "x": 1008.3165490811291, "y": 545.574956436883 }, "class_name": "PostprocessorOp", "is_builtin": false, "inputs": [ { "name": "out_tensor", "type": "gxf::Entity" } ], "outputs": [ { "name": "predictions", "type": "std::shared_ptr>" } ], "specs": [ { "parameter": "out_tensor_name_", "key": "out_tensor_name", "headline": "Output Tensor Name", "description": "Name of the output tensor to extract from the GXF entity", "type": "std::string", "default": "output" } ], "dependencies": [ "#include ", "#include ", "#include ", "#include ", "#include " ], "private_members": [], "has_initialize": false, "initialize_body": null, "compute_body": "auto maybe_in_message = op_input.receive(\"out_tensor\");\n if (!maybe_in_message) return;\n auto maybe_tensor = maybe_in_message.value().get(out_tensor_name_.get().c_str());\n if (!maybe_tensor) { HOLOSCAN_LOG_ERROR(\"Output tensor '{}' not found\", out_tensor_name_.get()); return; }\n const auto& shape = maybe_tensor->shape();\n if (shape.empty()) { HOLOSCAN_LOG_ERROR(\"Invalid tensor shape: empty\"); return; }\n const auto classes_dim = shape.back();\n if (classes_dim <= 0 || classes_dim > 65536) { HOLOSCAN_LOG_ERROR(\"Invalid class dimension: {}\", classes_dim); return; }\n const size_t num_classes = static_cast(classes_dim);\n const size_t needed_bytes = num_classes * sizeof(float);\n if (maybe_tensor->nbytes() < needed_bytes) {\n HOLOSCAN_LOG_ERROR(\"Output tensor '{}' too small: {} bytes (need {})\", out_tensor_name_.get(), maybe_tensor->nbytes(), needed_bytes);\n return;\n }\n DLDevice dev = maybe_tensor->device();\n DLDataType dtype = maybe_tensor->dtype();\n if (dtype.code != kDLFloat || dtype.bits != 32) {\n HOLOSCAN_LOG_ERROR(\"Output tensor '{}' is not float32 (code={}, bits={})\", out_tensor_name_.get(), static_cast(dtype.code), static_cast(dtype.bits));\n return;\n }\n const void* raw_ptr = maybe_tensor->data();\n if (!raw_ptr) { HOLOSCAN_LOG_ERROR(\"Null tensor data pointer\"); return; }\n std::vector host_logits(num_classes);\n if (dev.device_type == kDLCPU) {\n std::memcpy(host_logits.data(), raw_ptr, needed_bytes);\n } else if (dev.device_type == kDLCUDA) {\n cudaError_t err = cudaMemcpy(host_logits.data(), raw_ptr, needed_bytes, cudaMemcpyDeviceToHost);\n if (err != cudaSuccess) {\n HOLOSCAN_LOG_ERROR(\"cudaMemcpy DeviceToHost failed for '{}': {}\", out_tensor_name_.get(), cudaGetErrorString(err));\n return;\n }\n } else {\n HOLOSCAN_LOG_ERROR(\"Unsupported output tensor device type {} for '{}'\", static_cast(dev.device_type), out_tensor_name_.get());\n return;\n }\n auto probs = std::make_shared>(softmax(host_logits.data(), num_classes));\n if (probs->empty()) { HOLOSCAN_LOG_ERROR(\"Softmax output is empty\"); return; }\n op_output.emit(probs, \"predictions\");", "class": "", "compute": "" }, { "id": "69bd3deeff031ee6e72c0aa2-1776345597091", "name": "spectrogram_dashboard", "type": "sink", "description": "Flexible HTTP dashboard server. Accepts optional spectrogram RGBA rows, prediction maps, and emits TX control messages. Serves spectrogram data and status JSON via a POSIX HTTP server. All inputs are optional — connect only what you need.", "position": { "x": 1534.7404760043617, "y": 27.35610520025739 }, "class_name": "SpectrogramDashboardOp", "is_builtin": false, "inputs": [ { "name": "new_rows", "type": "std::shared_ptr>" }, { "name": "spectrogram_width", "type": "int" }, { "name": "spectrogram_height", "type": "int" }, { "name": "offset_row", "type": "int" }, { "name": "num_new_rows", "type": "int" }, { "name": "predictions", "type": "std::map" } ], "outputs": [ { "name": "tx_control_message", "type": "std::shared_ptr" } ], "specs": [ { "parameter": "websocket_port_", "key": "websocket_port", "headline": "HTTP Port", "description": "Port number for the HTTP data server", "type": "int", "default": 8080 }, { "parameter": "web_root_path_", "key": "web_root_path", "headline": "Web Root Path", "description": "Root directory for web assets (used for file-based data export)", "type": "std::string", "default": "workspace/app/web" }, { "parameter": "update_rate_hz_", "key": "update_rate_hz", "headline": "Update Rate (Hz)", "description": "Target update rate for the dashboard data", "type": "int", "default": 30 } ], "dependencies": [ "#include ", "#include ", "#include ", "#include ", "#include ", "#include ", "#include ", "#include ", "#include ", "#include ", "#include ", "#include ", "#include ", "#include ", "#include \"../utils/utils.hpp\"" ], "private_members": [ "int server_socket_ = -1;", "std::thread server_thread_;", "std::atomic server_running_{false};", "std::map smoothed_predictions_;", "std::vector current_rgba_buffer_;", "int current_width_ = 0;", "int current_height_ = 0;", "int current_offset_ = 0;", "std::mutex data_mutex_;", "double tx_center_freq_mhz_ = 3425.0;", "double tx_gain_db_ = -10.0;", "bool tx_enabled_ = false;", "int tx_signal_type_ = 6;", "std::vector> pending_msgs_;", "std::chrono::steady_clock::time_point last_update_;", "const std::chrono::milliseconds update_interval_{33};", "void cleanup() { if (server_running_.load()) { server_running_.store(false); if (server_socket_ >= 0) { close(server_socket_); } if (server_thread_.joinable()) { server_thread_.join(); } } }", "std::string generate_json_response() { std::stringstream ss; ss << \"{\\\"width\\\":\" << current_width_ << \",\"; ss << \"\\\"height\\\":\" << current_height_ << \",\"; ss << \"\\\"offset_row\\\":\" << current_offset_ << \",\"; ss << \"\\\"tx_enabled\\\":\" << (tx_enabled_ ? \"true\" : \"false\") << \",\"; ss << \"\\\"tx_freq_mhz\\\":\" << tx_center_freq_mhz_ << \",\"; ss << \"\\\"tx_gain_db\\\":\" << tx_gain_db_ << \",\"; ss << \"\\\"tx_signal_type\\\":\" << tx_signal_type_ << \",\"; ss << \"\\\"predictions\\\":{\"; bool first = true; for (const auto& [label, prob] : smoothed_predictions_) { if (!first) ss << \",\"; ss << \"\\\"\" << label << \"\\\":\" << prob; first = false; } ss << \"}}\"; return ss.str(); }", "void handle_client(int client_socket) { char req_buf[4096] = {0}; int bytes_read = ::read(client_socket, req_buf, sizeof(req_buf) - 1); if (bytes_read <= 0) { close(client_socket); return; } std::string request(req_buf, bytes_read); if (request.find(\"OPTIONS \") == 0) { std::string resp = \"HTTP/1.1 204 No Content\\r\\nAccess-Control-Allow-Origin: *\\r\\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\\r\\nAccess-Control-Allow-Headers: Content-Type\\r\\n\\r\\n\"; send(client_socket, resp.c_str(), resp.length(), MSG_NOSIGNAL); return; } if (request.find(\"POST /tx_control\") != std::string::npos) { std::string body; auto bp = request.find(\"\\r\\n\\r\\n\"); if (bp != std::string::npos) body = request.substr(bp + 4); { std::lock_guard lock(data_mutex_); if (body.find(\"\\\"toggle_tx\\\"\") != std::string::npos) { tx_enabled_ = !tx_enabled_; pending_msgs_.push_back({tx_enabled_ ? 4 : 5, 0.0}); } if (body.find(\"\\\"set_signal_type\\\"\") != std::string::npos) { auto vp = body.find(\"\\\"value\\\":\"); if (vp != std::string::npos) { tx_signal_type_ = std::atoi(body.c_str() + vp + 8); pending_msgs_.push_back({7, static_cast(tx_signal_type_)}); } } if (body.find(\"\\\"set_freq\\\"\") != std::string::npos) { auto vp = body.find(\"\\\"value\\\":\"); if (vp != std::string::npos) { double fv = std::atof(body.c_str() + vp + 8); tx_center_freq_mhz_ = fv; pending_msgs_.push_back({1, fv}); } } if (body.find(\"\\\"set_gain\\\"\") != std::string::npos) { auto vp = body.find(\"\\\"value\\\":\"); if (vp != std::string::npos) { double gv = std::atof(body.c_str() + vp + 8); tx_gain_db_ = gv; pending_msgs_.push_back({3, gv}); } } } std::string rb; { std::lock_guard lock(data_mutex_); rb = generate_json_response(); } std::string resp = \"HTTP/1.1 200 OK\\r\\nContent-Type: application/json\\r\\nAccess-Control-Allow-Origin: *\\r\\nContent-Length: \" + std::to_string(rb.size()) + \"\\r\\n\\r\\n\" + rb; send(client_socket, resp.c_str(), resp.length(), MSG_NOSIGNAL); return; } bool is_raw = (request.find(\"GET /spectrogram.raw\") != std::string::npos); std::lock_guard lock(data_mutex_); if (is_raw && !current_rgba_buffer_.empty()) { std::string header = \"HTTP/1.1 200 OK\\r\\nContent-Type: application/octet-stream\\r\\nAccess-Control-Allow-Origin: *\\r\\nCache-Control: no-store\\r\\nContent-Length: \" + std::to_string(current_rgba_buffer_.size()) + \"\\r\\n\\r\\n\"; send(client_socket, header.c_str(), header.length(), MSG_NOSIGNAL); size_t total_sent = 0; while (total_sent < current_rgba_buffer_.size()) { ssize_t sent = send(client_socket, current_rgba_buffer_.data() + total_sent, current_rgba_buffer_.size() - total_sent, MSG_NOSIGNAL); if (sent <= 0) break; total_sent += sent; } } else { std::string body = generate_json_response(); std::string response = \"HTTP/1.1 200 OK\\r\\nContent-Type: application/json\\r\\nAccess-Control-Allow-Origin: *\\r\\nCache-Control: no-store\\r\\nContent-Length: \" + std::to_string(body.size()) + \"\\r\\n\\r\\n\" + body; send(client_socket, response.c_str(), response.length(), MSG_NOSIGNAL); } }", "void run_server() { while (server_running_.load()) { struct sockaddr_in address; int addrlen = sizeof(address); int client_socket = accept(server_socket_, (struct sockaddr*)&address, (socklen_t*)&addrlen); if (client_socket >= 0) { handle_client(client_socket); close(client_socket); } } }", "void write_data_file() { std::lock_guard lock(data_mutex_); if (!current_rgba_buffer_.empty() && !web_root_path_.get().empty()) { std::string path = web_root_path_.get() + \"/public/spectrogram.raw\"; std::ofstream file(path, std::ios::binary); file.write(reinterpret_cast(current_rgba_buffer_.data()), current_rgba_buffer_.size()); } }" ], "has_initialize": true, "initialize_body": "holoscan::Operator::initialize();\n \n server_socket_ = socket(AF_INET, SOCK_STREAM, 0);\n if (server_socket_ < 0) {\n HOLOSCAN_LOG_ERROR(\"Failed to create socket\");\n return;\n }\n \n int opt = 1;\n setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));\n \n struct sockaddr_in address;\n address.sin_family = AF_INET;\n address.sin_addr.s_addr = INADDR_ANY;\n address.sin_port = htons(websocket_port_.get());\n \n if (bind(server_socket_, (struct sockaddr*)&address, sizeof(address)) < 0) {\n HOLOSCAN_LOG_ERROR(\"Failed to bind socket to port {}\", websocket_port_.get());\n close(server_socket_);\n return;\n }\n \n if (listen(server_socket_, 3) < 0) {\n HOLOSCAN_LOG_ERROR(\"Failed to listen on socket\");\n close(server_socket_);\n return;\n }\n \n server_running_.store(true);\n server_thread_ = std::thread([this]() { run_server(); });\n \n last_update_ = std::chrono::steady_clock::now();\n HOLOSCAN_LOG_INFO(\"HTTP server started on port {}\", websocket_port_.get());", "compute_body": "auto spec_in = op_input.receive>>(\"new_rows\");\n auto w_in = op_input.receive(\"spectrogram_width\");\n auto h_in = op_input.receive(\"spectrogram_height\");\n auto offset_in = op_input.receive(\"offset_row\");\n auto nr_in = op_input.receive(\"num_new_rows\");\n auto p_in = op_input.receive>(\"predictions\");\n \n {\n std::lock_guard lock(data_mutex_);\n if (spec_in && w_in && h_in && offset_in && nr_in) {\n int w = w_in.value();\n int h = h_in.value();\n int num_new = nr_in.value();\n size_t buf_size = static_cast(w) * h * 4;\n if (current_rgba_buffer_.size() != buf_size) current_rgba_buffer_.resize(buf_size, 0);\n current_width_ = w;\n current_height_ = h;\n current_offset_ = offset_in.value();\n if (num_new > 0) {\n const auto& new_data = *spec_in.value();\n int row_bytes = w * 4;\n int start_row = (current_offset_ - num_new + h) % h;\n for (int r = 0; r < num_new; ++r) {\n int dst_row = (start_row + r) % h;\n std::memcpy(current_rgba_buffer_.data() + dst_row * row_bytes,\n new_data.data() + r * row_bytes, row_bytes);\n }\n }\n }\n if (p_in) {\n const float alpha = 0.15f; auto raw = p_in.value(); if (smoothed_predictions_.empty()) { smoothed_predictions_ = raw; } else { for (auto& [label, val] : raw) { smoothed_predictions_[label] = alpha * val + (1.0f - alpha) * smoothed_predictions_[label]; } }\n }\n }\n \n { std::lock_guard lock2(data_mutex_);\n if (!pending_msgs_.empty()) {\n auto [t, v] = pending_msgs_.front();\n pending_msgs_.erase(pending_msgs_.begin());\n auto msg = std::make_shared();\n msg->type = static_cast(t);\n msg->value = v;\n op_output.emit(msg, \"tx_control_message\");\n }\n }", "class": "", "compute": "" }, { "id": "69bd3deeff031ee6e72c0a9c-1776345675756", "name": "model_mapper", "type": "postprocessing", "description": "Maps numeric prediction probabilities to human-readable class labels. Accepts a shared float vector and emits a label-to-probability map.", "position": { "x": 1319.1869982595924, "y": 551.0619693437972 }, "class_name": "ModelMapperOp", "is_builtin": false, "inputs": [ { "name": "predictions", "type": "std::shared_ptr>" } ], "outputs": [ { "name": "labeled_predictions", "type": "std::map" } ], "specs": [ { "parameter": "labels_", "key": "labels", "headline": "Class Labels", "description": "Ordered list of class label strings corresponding to prediction indices", "type": "std::vector", "default": "[\"Bleedover\", \"Collisions\", \"Parallel\", \"Birdies\", \"CTNB\", \"LFM\", \"None\", \"Jamming\"]" } ], "dependencies": [ "#include ", "#include ", "#include ", "#include " ], "private_members": [], "has_initialize": false, "initialize_body": null, "compute_body": "auto p = op_input.receive>>(\"predictions\"); if(!p) return; const auto& probs = *p.value(); size_t n = probs.size(); std::map r; for(size_t i=0;i>>" } ], "outputs": [], "specs": [ { "parameter": "uri_", "key": "uri", "headline": "Pluto URI", "description": null, "type": "std::string", "default": "192.168.3.1" }, { "parameter": "sample_rate_", "key": "sample_rate", "headline": "TX sample rate in Hz", "description": null, "type": "int64_t", "default": 2500000 }, { "parameter": "center_freq_", "key": "center_freq", "headline": "TX center frequency in Hz", "description": null, "type": "int64_t", "default": "2415500000" }, { "parameter": "bandwidth_", "key": "bandwidth", "headline": "TX RF bandwidth in Hz", "description": null, "type": "int64_t", "default": 2000000 }, { "parameter": "attenuation_", "key": "attenuation", "headline": "TX attenuation in mdB (negative)", "description": null, "type": "int64_t", "default": "0" } ], "dependencies": [ "#include ", "#include ", "#include ", "#include " ], "private_members": [ "struct iio_context* ctx_ = nullptr;", "struct iio_device* phy_ = nullptr;", "struct iio_device* txdev_ = nullptr;", "struct iio_channel* tx0_i_ = nullptr;", "struct iio_channel* tx0_q_ = nullptr;", "struct iio_buffer* txbuf_ = nullptr;" ], "has_initialize": true, "initialize_body": "ria::Operator::initialize();\nctx_ = iio_create_context_from_uri(uri_.get().c_str());\nif (!ctx_) { std::cerr << \"Cannot connect to Pluto at \" << uri_.get() << std::endl; return; }\nphy_ = iio_context_find_device(ctx_, \"ad9361-phy\");\niio_channel_attr_write_longlong(iio_device_find_channel(phy_, \"altvoltage1\", true), \"frequency\", center_freq_.get());\niio_channel_attr_write_longlong(iio_device_find_channel(phy_, \"voltage0\", true), \"sampling_frequency\", sample_rate_.get());\niio_channel_attr_write_longlong(iio_device_find_channel(phy_, \"voltage0\", true), \"rf_bandwidth\", bandwidth_.get());\niio_channel_attr_write_longlong(iio_device_find_channel(phy_, \"voltage0\", true), \"hardwaregain\", attenuation_.get());\ntxdev_ = iio_context_find_device(ctx_, \"cf-ad9361-dds-core-lpc\");\ntx0_i_ = iio_device_find_channel(txdev_, \"voltage0\", true);\ntx0_q_ = iio_device_find_channel(txdev_, \"voltage1\", true);\niio_channel_enable(tx0_i_);\niio_channel_enable(tx0_q_);\ntxbuf_ = iio_device_create_buffer(txdev_, 1024, false);", "compute_body": "auto signal = input.receive>>>(\"signal_in\").value();\nint16_t* buf = static_cast(iio_buffer_first(txbuf_, tx0_i_));\nsize_t n = std::min(signal->size(), static_cast(1024));\nfor (size_t i = 0; i < n; ++i) {\n buf[2*i] = static_cast((*signal)[i].real() * 2048.0f);\n buf[2*i+1] = static_cast((*signal)[i].imag() * 2048.0f);\n}\niio_buffer_push(txbuf_);", "class": "", "compute": "" } ], "flows": [ { "upstream": "69bd3deeff031ee6e72c0a9d-1776345526320", "downstream": "69bd3deeff031ee6e72c0aa0-1776345543621", "port_pairs": { "iq_samples": "iq_samples" }, "sourcePosition": "right", "targetPosition": "left", "label": "iq_samples → iq_samples", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0aa0-1776345543621", "downstream": "69bd3deeff031ee6e72c0a9b-1776345577288", "port_pairs": { "in_tensor": "receivers" }, "sourcePosition": "right", "targetPosition": "left", "label": "in_tensor → receivers", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0a9b-1776345577288", "downstream": "69bd3deeff031ee6e72c0a9f-1776345584342", "port_pairs": { "transmitter": "out_tensor" }, "sourcePosition": "right", "targetPosition": "left", "label": "transmitter → out_tensor", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0aa1-1776345548184", "downstream": "69bd3deeff031ee6e72c0aa2-1776345597091", "port_pairs": { "num_new_rows": "num_new_rows" }, "sourcePosition": "right", "targetPosition": "left", "label": "num_new_rows → num_new_rows", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0aa1-1776345548184", "downstream": "69bd3deeff031ee6e72c0aa2-1776345597091", "port_pairs": { "offset_row": "offset_row" }, "sourcePosition": "right", "targetPosition": "left", "label": "offset_row → offset_row", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0aa1-1776345548184", "downstream": "69bd3deeff031ee6e72c0aa2-1776345597091", "port_pairs": { "spectrogram_height": "spectrogram_height" }, "sourcePosition": "right", "targetPosition": "left", "label": "spectrogram_height → spectrogram_height", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0aa1-1776345548184", "downstream": "69bd3deeff031ee6e72c0aa2-1776345597091", "port_pairs": { "spectrogram_width": "spectrogram_width" }, "sourcePosition": "right", "targetPosition": "left", "label": "spectrogram_width → spectrogram_width", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0aa1-1776345548184", "downstream": "69bd3deeff031ee6e72c0aa2-1776345597091", "port_pairs": { "new_rows": "new_rows" }, "sourcePosition": "right", "targetPosition": "left", "label": "new_rows → new_rows", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0a9f-1776345584342", "downstream": "69bd3deeff031ee6e72c0a9c-1776345675756", "port_pairs": { "predictions": "predictions" }, "sourcePosition": "right", "targetPosition": "left", "label": "predictions → predictions", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0a9c-1776345675756", "downstream": "69bd3deeff031ee6e72c0aa2-1776345597091", "port_pairs": { "labeled_predictions": "predictions" }, "sourcePosition": "right", "targetPosition": "left", "label": "labeled_predictions → predictions", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0aa2-1776345597091", "downstream": "69d80d08aabdb365e3b84e14-1776361788176", "port_pairs": { "tx_control_message": "signal_in" }, "sourcePosition": "right", "targetPosition": "left", "label": "tx_control_message → signal_in", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } }, { "upstream": "69bd3deeff031ee6e72c0a9d-1776345526320", "downstream": "69bd3deeff031ee6e72c0aa1-1776345548184", "port_pairs": { "iq_samples": "iq_samples" }, "sourcePosition": "right", "targetPosition": "left", "label": "iq_samples → iq_samples", "animated": false, "style": { "stroke": "#4183c4", "strokeWidth": 1.5, "strokeDasharray": "5 4", "opacity": 0.7 } } ] }