Add Applications/Demo_App.json
This commit is contained in:
parent
b79a89cb02
commit
3e138ca562
869
Applications/Demo_App.json
Normal file
869
Applications/Demo_App.json
Normal file
|
|
@ -0,0 +1,869 @@
|
|||
{
|
||||
"app_name": "new_blocks.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<std::vector<ComplexType>>"
|
||||
}
|
||||
],
|
||||
"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 <iio.h>",
|
||||
"#include <iostream>",
|
||||
"#include <cmath>",
|
||||
"#include <cstdlib>"
|
||||
],
|
||||
"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<double> runtime_rx_freq_ghz_{3.425};",
|
||||
"std::atomic<double> runtime_rx_gain_db_{30.0};",
|
||||
"std::atomic<bool> 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<std::vector<ComplexType>>();\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<char*>(iio_buffer_first(buffer_, ch_i_));\n const char* end = static_cast<char*>(iio_buffer_end(buffer_));\n for (const char* p = start; p < end; p += step) {\n const auto raw = reinterpret_cast<const int16_t*>(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<float>(rand()%100), static_cast<float>(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<std::vector<ComplexType>>"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "in_tensor",
|
||||
"type": "gxf::Entity"
|
||||
}
|
||||
],
|
||||
"specs": [
|
||||
{
|
||||
"parameter": "allocator_",
|
||||
"key": "allocator",
|
||||
"headline": "Alloc",
|
||||
"description": "Memory allocator for tensor creation",
|
||||
"type": "std::shared_ptr<holoscan::Allocator>",
|
||||
"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 <gxf/std/tensor.hpp>"
|
||||
],
|
||||
"private_members": [],
|
||||
"has_initialize": false,
|
||||
"initialize_body": null,
|
||||
"compute_body": "auto maybe_s = op_input.receive<std::shared_ptr<std::vector<ComplexType>>>(\"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<nvidia::gxf::Tensor>(in_tensor_name_.get().c_str()).value(); auto alloc = nvidia::gxf::Handle<nvidia::gxf::Allocator>::Create(context.context(), allocator_->gxf_cid()).value(); tensor->template reshape<float>(nvidia::gxf::Shape({1,2,static_cast<int>(s.size())}), nvidia::gxf::MemoryStorageType::kHost, alloc); float* d = tensor->template data<float>().value(); for(size_t i=0;i<s.size();++i){ d[2*i]=re[i]; d[2*i+1]=im[i]; } op_output.emit(ent);",
|
||||
"class": "",
|
||||
"compute": ""
|
||||
},
|
||||
{
|
||||
"id": "69bd3deeff031ee6e72c0aa1-1776345548184",
|
||||
"name": "spectrogram",
|
||||
"type": "preprocessing",
|
||||
"description": "FFT-based spectrogram generator using FFTW3. Produces RGBA waterfall rows with configurable colormap, windowing, and ADC input scaling. Emits only new rows for efficient incremental rendering.",
|
||||
"position": {
|
||||
"x": 503.6426850135131,
|
||||
"y": 41.98771759801264
|
||||
},
|
||||
"class_name": "SpectrogramOp",
|
||||
"is_builtin": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "iq_samples",
|
||||
"type": "std::shared_ptr<std::vector<ComplexType>>"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "new_rows",
|
||||
"type": "std::shared_ptr<std::vector<uint8_t>>"
|
||||
},
|
||||
{
|
||||
"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 <complex>",
|
||||
"#include <vector>",
|
||||
"#include <cmath>",
|
||||
"#include <algorithm>",
|
||||
"#include <cstring>",
|
||||
"#define M_PI 3.14159265358979323846",
|
||||
"#include <fftw3.h>"
|
||||
],
|
||||
"private_members": [
|
||||
"fftwf_plan fft_plan_ = nullptr;",
|
||||
"fftwf_complex* fft_in_ = nullptr;",
|
||||
"fftwf_complex* fft_out_ = nullptr;",
|
||||
"std::vector<float> window_coeffs_;",
|
||||
"std::vector<uint8_t> texture_data_;",
|
||||
"std::vector<uint32_t> 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<uint8_t>(stops[seg][1] + (stops[seg+1][1] - stops[seg][1]) * lt); uint8_t g = static_cast<uint8_t>(stops[seg][2] + (stops[seg+1][2] - stops[seg][2]) * lt); uint8_t b = static_cast<uint8_t>(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<std::shared_ptr<std::vector<ComplexType>>>(\"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<uint32_t*>(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<int>(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<std::vector<uint8_t>>(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<std::string, std::string>",
|
||||
"default": "interference_recognition_fp32.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<std::string, std::vector<std::string>>",
|
||||
"default": "input"
|
||||
},
|
||||
{
|
||||
"parameter": "inference_map_",
|
||||
"key": "inference_map",
|
||||
"headline": "Inference Map",
|
||||
"description": "Map of model name to output tensor names",
|
||||
"type": "std::map<std::string, std::vector<std::string>>",
|
||||
"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<holoscan::Allocator>",
|
||||
"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<std::vector<float>>"
|
||||
}
|
||||
],
|
||||
"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 <gxf/std/tensor.hpp>",
|
||||
"#include <cstring>",
|
||||
"#include <vector>",
|
||||
"#include <memory>",
|
||||
"#include <cuda_runtime_api.h>"
|
||||
],
|
||||
"private_members": [],
|
||||
"has_initialize": false,
|
||||
"initialize_body": null,
|
||||
"compute_body": "auto maybe_in_message = op_input.receive<gxf::Entity>(\"out_tensor\");\n if (!maybe_in_message) return;\n auto maybe_tensor = maybe_in_message.value().get<holoscan::Tensor>(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<size_t>(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<int>(dtype.code), static_cast<int>(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<float> 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<int>(dev.device_type), out_tensor_name_.get());\n return;\n }\n auto probs = std::make_shared<std::vector<float>>(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<std::vector<uint8_t>>"
|
||||
},
|
||||
{
|
||||
"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<std::string, float>"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "tx_control_message",
|
||||
"type": "std::shared_ptr<RadioControlMessage>"
|
||||
}
|
||||
],
|
||||
"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 <sys/socket.h>",
|
||||
"#include <netinet/in.h>",
|
||||
"#include <arpa/inet.h>",
|
||||
"#include <unistd.h>",
|
||||
"#include <iomanip>",
|
||||
"#include <sstream>",
|
||||
"#include <algorithm>",
|
||||
"#include <cmath>",
|
||||
"#include <thread>",
|
||||
"#include <atomic>",
|
||||
"#include <mutex>",
|
||||
"#include <set>",
|
||||
"#include <chrono>",
|
||||
"#include <fstream>",
|
||||
"#include \"../utils/utils.hpp\""
|
||||
],
|
||||
"private_members": [
|
||||
"int server_socket_ = -1;",
|
||||
"std::thread server_thread_;",
|
||||
"std::atomic<bool> server_running_{false};",
|
||||
"std::map<std::string, float> smoothed_predictions_;",
|
||||
"std::vector<uint8_t> 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<std::pair<int,double>> 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<std::mutex> 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<double>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<const char*>(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<std::shared_ptr<std::vector<uint8_t>>>(\"new_rows\");\n auto w_in = op_input.receive<int>(\"spectrogram_width\");\n auto h_in = op_input.receive<int>(\"spectrogram_height\");\n auto offset_in = op_input.receive<int>(\"offset_row\");\n auto nr_in = op_input.receive<int>(\"num_new_rows\");\n auto p_in = op_input.receive<std::map<std::string, float>>(\"predictions\");\n \n {\n std::lock_guard<std::mutex> 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<size_t>(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<std::mutex> 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<RadioControlMessage>();\n msg->type = static_cast<RadioControlMessage::Type>(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<std::vector<float>>"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "labeled_predictions",
|
||||
"type": "std::map<std::string, float>"
|
||||
}
|
||||
],
|
||||
"specs": [
|
||||
{
|
||||
"parameter": "labels_",
|
||||
"key": "labels",
|
||||
"headline": "Class Labels",
|
||||
"description": "Ordered list of class label strings corresponding to prediction indices",
|
||||
"type": "std::vector<std::string>",
|
||||
"default": "[\"Bleedover\", \"Collisions\", \"Parallel\", \"Birdies\", \"CTNB\", \"LFM\", \"None\", \"Jamming\"]"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
"#include <map>",
|
||||
"#include <string>",
|
||||
"#include <vector>",
|
||||
"#include <memory>"
|
||||
],
|
||||
"private_members": [],
|
||||
"has_initialize": false,
|
||||
"initialize_body": null,
|
||||
"compute_body": "auto p = op_input.receive<std::shared_ptr<std::vector<float>>>(\"predictions\"); if(!p) return; const auto& probs = *p.value(); size_t n = probs.size(); std::map<std::string,float> r; for(size_t i=0;i<n;++i){ r[(i<labels_.get().size())?labels_.get()[i]:std::to_string(i)] = probs[i]; } op_output.emit(r, \"labeled_predictions\");",
|
||||
"class": "",
|
||||
"compute": ""
|
||||
},
|
||||
{
|
||||
"id": "69d80d08aabdb365e3b84e14-1776361788176",
|
||||
"name": "PlutoTXOp",
|
||||
"type": "sink",
|
||||
"description": "Transmits IQ samples through an ADALM-Pluto SDR via libiio.",
|
||||
"position": {
|
||||
"x": 2040.5281414494543,
|
||||
"y": 50.98952706451257
|
||||
},
|
||||
"class_name": "PlutoTXOp",
|
||||
"is_builtin": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "signal_in",
|
||||
"type": "std::shared_ptr<std::vector<std::complex<float>>>"
|
||||
}
|
||||
],
|
||||
"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 <ria/operator.hpp>",
|
||||
"#include <iio.h>",
|
||||
"#include <complex>",
|
||||
"#include <vector>"
|
||||
],
|
||||
"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<std::shared_ptr<std::vector<std::complex<float>>>>(\"signal_in\").value();\nint16_t* buf = static_cast<int16_t*>(iio_buffer_first(txbuf_, tx0_i_));\nsize_t n = std::min(signal->size(), static_cast<size_t>(1024));\nfor (size_t i = 0; i < n; ++i) {\n buf[2*i] = static_cast<int16_t>((*signal)[i].real() * 2048.0f);\n buf[2*i+1] = static_cast<int16_t>((*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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user