From ac55a4c1e52bce6d19bc76e8a8108f0baeaddcbe Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Thu, 22 May 2025 12:55:25 -0700 Subject: [PATCH 01/13] Updated to use pack function --- .gitignore | 4 +- .pre-commit-config.yaml | 45 ++ CMakeLists.txt | 22 +- Dockerfile | 4 +- README.md | 13 + cmake/protos.cmake | 37 +- config/simulator_32ch.json | 12 +- external/sciencecorp/synapse-api | 2 +- proto/example_app.proto | 26 ++ src/fixed_weight_decoder.cpp | 708 ++++++++++++++++--------------- src/fixed_weight_decoder.hpp | 159 +++---- 11 files changed, 592 insertions(+), 440 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 proto/example_app.proto diff --git a/.gitignore b/.gitignore index 0728111..16d4cbc 100644 --- a/.gitignore +++ b/.gitignore @@ -76,4 +76,6 @@ Testing/ *.deb # cached permissions -.synapse_deploy_cache.json \ No newline at end of file +.synapse_deploy_cache.json +app-sdk/ +vcpkg_installed/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f5150be --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,45 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: check-added-large-files + - id: check-executables-have-shebangs + - id: check-json + - id: check-shebang-scripts-are-executable + - id: check-yaml + - id: detect-private-key + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace + +- repo: https://github.com/pocc/pre-commit-hooks + rev: v1.3.5 + hooks: + - id: clang-format + args: + - --style=file + - -i + - id: cppcheck + name: cppcheck + language: system + entry: cppcheck + args: [ + "--enable=style,performance,warning", + "--check-level=exhaustive", + "--suppress=missingInclude", + "--suppress=missingIncludeSystem", + "--suppress=unusedFunction", + "--suppress=useStlAlgorithm", + "--inconclusive", + "--error-exitcode=1", + "--std=c++20", + "--platform=unix64", + "--language=c++" + ] + files: \.(c|cpp)$ + +- repo: https://github.com/koalaman/shellcheck-precommit + rev: v0.10.0 + hooks: + - id: shellcheck diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bf0699..1a40072 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,7 @@ include("cmake/protos.cmake") option(BUILD_FOR_ARM64 "Build for ARM64 architecture" OFF) option(USE_LOCAL_SDK "Use locally built SDK instead of system installation" OFF) -set(LOCAL_SDK_PATH "app-sdk" CACHE PATH "Path to local SDK build directory") - +set(LOCAL_SDK_PATH "app-sdk") if (BUILD_FOR_ARM64 AND NOT CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") message(STATUS "Cross compiling for ARM64") set(CMAKE_SYSTEM_NAME Linux) @@ -22,18 +21,17 @@ find_package(Protobuf REQUIRED) find_package(spdlog REQUIRED CONFIG) find_package(iir REQUIRED CONFIG) -if(USE_LOCAL_SDK) +if(true) if(NOT LOCAL_SDK_PATH) message(FATAL_ERROR "USE_LOCAL_SDK is ON but LOCAL_SDK_PATH is not set") endif() message(STATUS "Using local SDK from: ${LOCAL_SDK_PATH}") - + set(SDK_LIB_PATH "${LOCAL_SDK_PATH}") set(SDK_LIB_NAME "synapse-app-sdk") link_directories(${SDK_LIB_PATH}) - + set(SYNAPSE_APP_SDK_LIB ${SDK_LIB_NAME}) - include_directories("${LOCAL_SDK_PATH}/include") else() find_library(SYNAPSE_APP_SDK_LIB NAMES synapse-app-sdk libsynapse-app-sdk.so.0.1.0) @@ -45,9 +43,18 @@ add_executable(synapse-example-app ${CMAKE_CURRENT_SOURCE_DIR}/src/fixed_weight_decoder.cpp ) +# Generate synapse sdk api protobufs generate_protobufs( TARGET synapse-example-app OUT_PROTO_DIR PROTO_OUT_DIR + PROTO_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/external/sciencecorp/synapse-api" +) + +# And also generate your custom app configuration protobuf +generate_protobufs( + TARGET synapse-example-app + OUT_PROTO_DIR APP_PROTO_OUT_DIR + PROTO_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/proto" ) # Be strict about the standard @@ -61,8 +68,9 @@ set_target_properties(synapse-example-app PROPERTIES target_include_directories(synapse-example-app PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src - PRIVATE + PRIVATE ${PROTO_OUT_DIR} + ${APP_PROTO_OUT_DIR} ) # Link dependencies diff --git a/Dockerfile b/Dockerfile index 65db546..04b5679 100644 --- a/Dockerfile +++ b/Dockerfile @@ -98,7 +98,7 @@ RUN git clone https://github.com/microsoft/vcpkg.git "${VCPKG_ROOT}" && \ # copy project-specific ports and manifest before installing COPY vcpkg.json "${VCPKG_ROOT}/vcpkg.json" -COPY external "${VCPKG_ROOT}/external/" +COPY external/sciencecorp/vcpkg "${VCPKG_ROOT}/external/sciencecorp/vcpkg" RUN cd "${VCPKG_ROOT}" && \ ./vcpkg install \ @@ -128,4 +128,4 @@ ENV VCPKG_INSTALLED_DIR="${VCPKG_ROOT}/build/host/vcpkg_installed" # Final workspace & entrypoint # ----------------------------------------------------------------------------- WORKDIR /home/workspace -CMD ["/bin/bash"] \ No newline at end of file +CMD ["/bin/bash"] diff --git a/README.md b/README.md index 5946c7e..0cd5c36 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,16 @@ To stop the app: ```bash synapsectl -u "your-device-identifier" stop ``` + +## Development +If you want, it is recommended to install and configure pre-commit to auto lint your files. + +```bash +pip install pre-commit + +pre-commit install + +# Now this will be run when you commit +# However, you can also run it manually like this +pre-commit run +``` diff --git a/cmake/protos.cmake b/cmake/protos.cmake index cbe1d06..1991038 100644 --- a/cmake/protos.cmake +++ b/cmake/protos.cmake @@ -2,7 +2,7 @@ function(generate_protobufs) cmake_parse_arguments(PARSE_ARGV 0 "arg" "" "TARGET;OUT_PROTO_DIR" - "" + "PROTO_DIRS;PROTO_FILES" ) if(DEFINED arg_UNPARSED_ARGUMENTS) @@ -14,15 +14,44 @@ function(generate_protobufs) if(NOT DEFINED arg_TARGET) message(FATAL_ERROR "TARGET must be specified.") endif() + if(NOT DEFINED arg_PROTO_DIRS AND NOT DEFINED arg_PROTO_FILES) + message(FATAL_ERROR "At least one of PROTO_DIRS or PROTO_FILES must be specified.") + endif() set(PROTO_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/include) file(MAKE_DIRECTORY ${PROTO_OUT_DIR}) + # Initialize empty lists for proto include dirs and proto files + set(PROTO_INCLUDE_DIRS "") + set(PROTOS "") + + # Include Synapse API proto directory in the include path get_filename_component(SYNAPSE_PROTO_INCLUDE_DIR ./external/sciencecorp/synapse-api REALPATH) - file(GLOB_RECURSE SYNAPSE_PROTOS ${SYNAPSE_PROTO_INCLUDE_DIR}/*.proto) + list(APPEND PROTO_INCLUDE_DIRS ${SYNAPSE_PROTO_INCLUDE_DIR}) + if(DEFINED SCIFI_PROTO_INCLUDE_DIR) + list(APPEND PROTO_INCLUDE_DIRS ${SCIFI_PROTO_INCLUDE_DIR}) + endif() - set(PROTO_INCLUDE_DIRS ${SYNAPSE_PROTO_INCLUDE_DIR} ${SCIFI_PROTO_INCLUDE_DIR}) - set(PROTOS ${SYNAPSE_PROTOS} ${SCIFI_PROTOS}) + # Add custom proto directories and files if provided + if(DEFINED arg_PROTO_DIRS) + foreach(DIR ${arg_PROTO_DIRS}) + get_filename_component(ABS_DIR ${DIR} REALPATH) + list(APPEND PROTO_INCLUDE_DIRS ${ABS_DIR}) + # If specific files not provided, include all protos in the directory + if(NOT DEFINED arg_PROTO_FILES) + file(GLOB_RECURSE DIR_PROTOS ${ABS_DIR}/*.proto) + list(APPEND PROTOS ${DIR_PROTOS}) + endif() + endforeach() + endif() + + # Add specific proto files if provided + if(DEFINED arg_PROTO_FILES) + foreach(PROTO_FILE ${arg_PROTO_FILES}) + get_filename_component(ABS_PROTO_FILE ${PROTO_FILE} REALPATH) + list(APPEND PROTOS ${ABS_PROTO_FILE}) + endforeach() + endif() protobuf_generate( TARGET ${arg_TARGET} diff --git a/config/simulator_32ch.json b/config/simulator_32ch.json index 41a3b02..4d9bf26 100644 --- a/config/simulator_32ch.json +++ b/config/simulator_32ch.json @@ -4,7 +4,17 @@ "type": "kApplication", "id": 2, "application": { - "name": "synapse-example-app" + "name": "synapse-example-app", + "parameters": { + "low_cutoff_hz": 200.0, + "high_cutoff_hz": 5000.0, + "spike_threshold_uv": 50.0, + "waveform_size": 50, + "refractory_period_us": 1000, + "window_size": 5, + "max_expected_rate": 10.0, + "cursor_channels": [0, 7, 16, 30] + } } }, { diff --git a/external/sciencecorp/synapse-api b/external/sciencecorp/synapse-api index 1b01a82..bd47aac 160000 --- a/external/sciencecorp/synapse-api +++ b/external/sciencecorp/synapse-api @@ -1 +1 @@ -Subproject commit 1b01a8211e04709c22c169827eeb62ffcd10e1ac +Subproject commit bd47aacf3894acce1a51e67118c64820d4a885dd diff --git a/proto/example_app.proto b/proto/example_app.proto new file mode 100644 index 0000000..93c64b5 --- /dev/null +++ b/proto/example_app.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package app; + +message ExampleAppConfig { + float low_cutoff_hz = 1; + float high_cutoff_hz = 2; + + // Threshold in microvolts + float spike_threshold_uv = 3; + + // Samples per waveform + uint32 waveform_size = 4; + + // Refractory Period, in microseconds + uint32 refractory_period_us = 5; + + // Number of bins to use for firing rate estimation + uint32 window_size = 6; + + // max expected rate, used for normalization + float max_expected_rate = 7; + + // Cursor control channels to use, we expect there to be four channels + repeated int32 cursor_channels = 8; +} diff --git a/src/fixed_weight_decoder.cpp b/src/fixed_weight_decoder.cpp index 79871f3..f434575 100644 --- a/src/fixed_weight_decoder.cpp +++ b/src/fixed_weight_decoder.cpp @@ -1,413 +1,431 @@ #include "fixed_weight_decoder.hpp" -#include -#include +#include // for std::clamp #include -#include // for std::clamp +#include #include // for parse_protobuf_message +#include -namespace app -{ - // Helper function to clamp a value between min and max - template - T clamp(T value, T min, T max) - { - return (value < min) ? min : (value > max) ? max - : value; - } +namespace app { +// Helper function to clamp a value between min and max +template T clamp(T value, T min, T max) { + return (value < min) ? min : (value > max) ? max : value; +} - FixedWeightDecoder::FixedWeightDecoder() : publish_rate_limiter_(kPublishRateSec) {} +FixedWeightDecoder::FixedWeightDecoder() + : publish_rate_limiter_(kPublishRateSec) {} + +bool FixedWeightDecoder::setup() { + // Make sure our app configuration is valid + if (!get_app_parameters( + [this](const app::ExampleAppConfig &config) { + return validate_configuration(config); + }, + configuration_)) { + spdlog::error("Failed to validate app parameters from app configuration"); + return false; + } - bool FixedWeightDecoder::setup() - { - const uint32_t broadband_node_id = 1; - if (!setup_reader(broadband_node_id)) - { - spdlog::warn("Failed to set up reader for controller"); - return 1; - } + const uint32_t broadband_node_id = 1; + if (!setup_reader(broadband_node_id)) { + spdlog::warn("Failed to set up reader for controller"); + return 1; + } - // Setup our output tap - if (!create_tap("joystick_out")) - { - spdlog::warn("Failed to create tap for joystick out"); - return false; - } + // Setup our output tap + if (!create_tap("joystick_out")) { + spdlog::warn("Failed to create tap for joystick out"); + return false; + } - // Enable performance monitoring - function_profiler_manager_.add("full_loop"); + // Enable performance monitoring + function_profiler_manager_.add("full_loop"); - // Publish loop stats every 1 second - if (!enable_function_profiling(std::chrono::seconds(1))) { - spdlog::error("Failed to enable function profile monitoring"); - return false; - } - return true; + // Publish loop stats every 1 second + if (!enable_function_profiling(std::chrono::seconds(1))) { + spdlog::error("Failed to enable function profile monitoring"); + return false; } + return true; +} - void FixedWeightDecoder::main() - { - // Store our broadband frames here - const float bin_size_ms = 10; - std::vector broadband_frames; - - while (node_running_) - { - // Receive data from the node you configured - if (!wait_for_frames(broadband_frames, bin_size_ms)) - { - // No frames just go wait again - continue; - } +void FixedWeightDecoder::main() { + // Store our broadband frames here + const float bin_size_ms = 10; + std::vector broadband_frames; - // Keep track of how long processing takes - start_profile("full_loop"); + while (node_running_) { + // Receive data from the node you configured + if (!wait_for_frames(broadband_frames, bin_size_ms)) { + // No frames just go wait again + continue; + } - // You have a set of broadband frames now, you can do whatever you want - // 1. Initialize the filters and our state on the first full set of frames - const auto broadband_frame = broadband_frames.at(0); - if (!filters_initialized_) - { - const size_t channel_count = broadband_frame.frame_data_size(); - const float sample_rate_hz = broadband_frame.sample_rate_hz(); + // Keep track of how long processing takes + start_profile("full_loop"); - // Store the sample rate for later use - sample_rate_hz_ = sample_rate_hz; + // You have a set of broadband frames now, you can do whatever you want + // 1. Initialize the filters and our state on the first full set of frames + const auto broadband_frame = broadband_frames.at(0); + if (!filters_initialized_) { + const size_t channel_count = broadband_frame.frame_data_size(); + const float sample_rate_hz = broadband_frame.sample_rate_hz(); - initialize_filters(channel_count, sample_rate_hz, bin_size_ms); - initialize_spike_detectors(channel_count); + // Store the sample rate for later use + sample_rate_hz_ = sample_rate_hz; - // Move to the next loop after init - continue; - } + initialize_filters(channel_count, sample_rate_hz, bin_size_ms); + initialize_spike_detectors(channel_count); - // Cleanup any previously detected spikes before processing new frames - cleanup_spike_events(); - - // 2. Filter the received frames - // We have a mapping of channel to filtered data with size of the frames we got - // TODO/NOTE: you could drop out early based on timestamps - std::vector> filtered_channel_data; - filtered_channel_data.resize(broadband_frames.at(0).frame_data_size()); - for (auto &channel_vector : filtered_channel_data) - { - channel_vector.reserve(broadband_frames.size()); - } + // Move to the next loop after init + continue; + } - // Create a vector to count spikes per channel in this batch - std::vector spike_counts(broadband_frames.at(0).frame_data_size(), 0); - - for (const auto &frame : broadband_frames) - { - const auto &frame_data = frame.frame_data(); - const uint64_t frame_timestamp_ns = frame.timestamp_ns(); - - for (int channel_id = 0; channel_id < frame_data.size(); ++channel_id) - { - // TODO: bounds checking - but we might not even want this way of doing things - auto &channel_filter = bandpass_filters_.at(channel_id); - const float filtered_data = channel_filter->filter(frame_data[channel_id]); - filtered_channel_data.at(channel_id).push_back(filtered_data); - - // 3. Detect spikes on the filtered data - if (spike_detectors_initialized_) - { - auto &spike_detector = spike_detectors_.at(channel_id); - - // Pass the filtered data to the spike detector along with the frame timestamp - // The detector handles the rest internally - synapse::SpikeEvent *spike_event = - spike_detector->detect(filtered_data, frame_timestamp_ns, channel_id); - - if (spike_event != nullptr) - { - // Store the detected spike for further processing - detected_spikes_.push_back(spike_event); - - // Increment the spike count for this channel - spike_counts[channel_id]++; - } + // Cleanup any previously detected spikes before processing new frames + cleanup_spike_events(); + + // 2. Filter the received frames + // We have a mapping of channel to filtered data with size of the frames we + // got + // TODO/NOTE: you could drop out early based on timestamps + std::vector> filtered_channel_data; + filtered_channel_data.resize(broadband_frames.at(0).frame_data_size()); + for (auto &channel_vector : filtered_channel_data) { + channel_vector.reserve(broadband_frames.size()); + } + + // Create a vector to count spikes per channel in this batch + std::vector spike_counts(broadband_frames.at(0).frame_data_size(), + 0); + + for (const auto &frame : broadband_frames) { + const auto &frame_data = frame.frame_data(); + const uint64_t frame_timestamp_ns = frame.timestamp_ns(); + + for (int channel_id = 0; channel_id < frame_data.size(); ++channel_id) { + // TODO: bounds checking - but we might not even want this way of doing + // things + auto &channel_filter = bandpass_filters_.at(channel_id); + const float filtered_data = + channel_filter->filter(frame_data[channel_id]); + filtered_channel_data.at(channel_id).push_back(filtered_data); + + // 3. Detect spikes on the filtered data + if (spike_detectors_initialized_) { + auto &spike_detector = spike_detectors_.at(channel_id); + + // Pass the filtered data to the spike detector along with the frame + // timestamp The detector handles the rest internally + synapse::SpikeEvent *spike_event = spike_detector->detect( + filtered_data, frame_timestamp_ns, channel_id); + + if (spike_event != nullptr) { + // Store the detected spike for further processing + detected_spikes_.push_back(spike_event); + + // Increment the spike count for this channel + spike_counts[channel_id]++; } } } + } - // Add current binned spike counts to the window - spike_count_window_.push_back(spike_counts); - - // Keep window at fixed size - if (spike_count_window_.size() > window_size_) - { - spike_count_window_.pop_front(); - } + // Add current binned spike counts to the window + spike_count_window_.push_back(spike_counts); - // Calculate cursor position based on the binned spike counts - float cursor_x = 0.0f; - float cursor_y = 0.0f; - - // Only calculate cursor position if we have enough data in the window - if (spike_count_window_.size() == window_size_) - { - // Calculate firing rates over the window for each cursor control channel - std::array firing_rates = {0.0f, 0.0f, 0.0f, 0.0f}; - - for (int i = 0; i < 4; i++) - { - size_t ch = cursor_channels_[i]; - for (const auto &bin_counts : spike_count_window_) - { - firing_rates[i] += bin_counts[ch]; - } - firing_rates[i] /= window_size_; // Average over window - } + // Keep window at fixed size + if (spike_count_window_.size() > configuration_.window_size()) { + spike_count_window_.pop_front(); + } - // Calculate x-position based on first channel pair (differential) - cursor_x = firing_rates[1] - firing_rates[0]; // Positive = right, negative = left + // Calculate cursor position based on the binned spike counts + float cursor_x = 0.0f; + float cursor_y = 0.0f; - // Calculate y-position based on second channel pair (differential) - cursor_y = firing_rates[3] - firing_rates[2]; // Positive = up, negative = down + // Only calculate cursor position if we have enough data in the window + if (spike_count_window_.size() == configuration_.window_size()) { + // Calculate firing rates over the window for each cursor control channel + std::array firing_rates = {0.0f, 0.0f, 0.0f, 0.0f}; - // Normalize to reasonable range (-1 to 1) - cursor_x = clamp(cursor_x / max_expected_rate_, -1.0f, 1.0f); - cursor_y = clamp(cursor_y / max_expected_rate_, -1.0f, 1.0f); - } - else - { - // Not enough data in window yet, use default values - cursor_x = 0.0f; - cursor_y = 0.0f; + for (int i = 0; i < 4; i++) { + size_t ch = cursor_channels_[i]; + for (const auto &bin_counts : spike_count_window_) { + firing_rates[i] += bin_counts[ch]; + } + firing_rates[i] /= configuration_.window_size(); // Average over window } - // Create a tensor with the cursor position - synapse::Tensor output_tensor; - const auto tensor_shape = {2}; - output_tensor.mutable_shape()->Add(tensor_shape.begin(), tensor_shape.end()); - output_tensor.set_dtype(synapse::Tensor_DType_DT_FLOAT); - output_tensor.set_endianness(synapse::Tensor_Endianness_TENSOR_LITTLE_ENDIAN); - - // Use the calculated cursor position instead of raw data values - const std::vector tensor_data = {cursor_x, cursor_y}; - - // TODO: this could be a helper - // Get pointers for serialization - const char *data_ptr = reinterpret_cast(tensor_data.data()); - size_t data_size = tensor_data.size() * sizeof(float); - output_tensor.set_data(std::string(data_ptr, data_size)); - - const auto current_time_ns = synapse::get_steady_clock_now(); - output_tensor.set_timestamp_ns(current_time_ns.count()); - - // Then, send off your data using the publisher you configured earlier - // In this demo, we use a ZMQ publisher over tcp - if (publish_rate_limiter_.reset_if_elapsed()) - { - if (publish_tap("joystick_out", output_tensor)) - { - spdlog::info("Published tensor: [x,y]: [{},{}]", tensor_data[0], tensor_data[1]); - } - else - { - spdlog::warn("Failed to publish tensor data"); - } - stop_profile("full_loop"); + // Calculate x-position based on first channel pair (differential) + cursor_x = firing_rates[1] - + firing_rates[0]; // Positive = right, negative = left + + // Calculate y-position based on second channel pair (differential) + cursor_y = + firing_rates[3] - firing_rates[2]; // Positive = up, negative = down + + // Normalize to reasonable range (-1 to 1) + cursor_x = + clamp(cursor_x / configuration_.max_expected_rate(), -1.0f, 1.0f); + cursor_y = + clamp(cursor_y / configuration_.max_expected_rate(), -1.0f, 1.0f); + } else { + // Not enough data in window yet, use default values + cursor_x = 0.0f; + cursor_y = 0.0f; + } - // We can also get a debug print of the output - print_profile("full_loop"); + // Create a tensor with the cursor position + synapse::Tensor output_tensor; + const auto tensor_shape = {2}; + output_tensor.mutable_shape()->Add(tensor_shape.begin(), + tensor_shape.end()); + output_tensor.set_dtype(synapse::Tensor_DType_DT_FLOAT); + output_tensor.set_endianness( + synapse::Tensor_Endianness_TENSOR_LITTLE_ENDIAN); + + // Use the calculated cursor position instead of raw data values + output_tensor.set_data(synapse::pack_tensor_data({cursor_x, cursor_y})); + + const auto current_time_ns = synapse::get_steady_clock_now(); + output_tensor.set_timestamp_ns(current_time_ns.count()); + + // Then, send off your data using the publisher you configured earlier + // In this demo, we use a ZMQ publisher over tcp + if (publish_rate_limiter_.reset_if_elapsed()) { + if (publish_tap("joystick_out", output_tensor)) { + spdlog::info("Published tensor: [x,y]: [{},{}]", cursor_x, cursor_y); + } else { + spdlog::warn("Failed to publish tensor data"); } + stop_profile("full_loop"); - // You can sleep here if you want, - // We busy wait up at the top if there is no data, so you don't need to here + // We can also get a debug print of the output + print_profile("full_loop"); } + + // You can sleep here if you want, + // We busy wait up at the top if there is no data, so you don't need to here } +} - bool FixedWeightDecoder::wait_for_frames(std::vector &frames, - float bin_size_ms) - { - if (bin_size_ms <= 0) - { - spdlog::warn("invalid bin size of: {}", bin_size_ms); - return false; - } +bool FixedWeightDecoder::wait_for_frames( + std::vector &frames, float bin_size_ms) { + if (bin_size_ms <= 0) { + spdlog::warn("invalid bin size of: {}", bin_size_ms); + return false; + } - const uint64_t target_bin_size_ns = static_cast(bin_size_ms * 1e6); - - // Prepare our output vector - frames.clear(); - - // Get the first timestamp - uint64_t first_timestamp_ns = 0; - - // TODO: We should consider having a timeout here - while (node_running_) - { - // In this example, we are listening to BroadbandFrame data - // TODO: the broadband node sends over the messages using multipart - // Figure out why this is the case - auto messages = data_reader_->receive_multipart(); - if (messages.empty()) - { - // Just keep trying - // TODO: We should have better signaling on the read failure - std::this_thread::sleep_for(std::chrono::microseconds(1)); - continue; - } + const uint64_t target_bin_size_ns = static_cast(bin_size_ms * 1e6); + + // Prepare our output vector + frames.clear(); + + // Get the first timestamp + uint64_t first_timestamp_ns = 0; + + // TODO: We should consider having a timeout here + while (node_running_) { + // In this example, we are listening to BroadbandFrame data + // TODO: the broadband node sends over the messages using multipart + // Figure out why this is the case + auto messages = data_reader_->receive_multipart(); + if (messages.empty()) { + // Just keep trying + // TODO: We should have better signaling on the read failure + std::this_thread::sleep_for(std::chrono::microseconds(1)); + continue; + } - // Reserve space for these frames - frames.reserve(frames.size() + messages.size()); - - // Process each received message in this multipart - for (auto &message : messages) - { - // Parse the message into a BroadbandFrame - const auto maybe_frame = - synapse::parse_protobuf_message(std::move(message)); - if (!maybe_frame.has_value()) - { - spdlog::warn("Failed to parse broadband frame"); - // If we have no frames at all, return false - if (frames.empty()) - { - return false; - } - // Otherwise, return what we have so far - return true; + // Reserve space for these frames + frames.reserve(frames.size() + messages.size()); + + // Process each received message in this multipart + for (auto &message : messages) { + // Parse the message into a BroadbandFrame + const auto maybe_frame = + synapse::parse_protobuf_message( + std::move(message)); + if (!maybe_frame.has_value()) { + spdlog::warn("Failed to parse broadband frame"); + // If we have no frames at all, return false + if (frames.empty()) { + return false; } + // Otherwise, return what we have so far + return true; + } - const auto &broadband_frame = maybe_frame.value(); - - // Check for dropped frames - const auto dropped_frames = - detect_dropped_frames(last_sequence_number_, broadband_frame.sequence_number()); - if (dropped_frames != 0) - { - spdlog::warn("Dropped: {} frames", dropped_frames); - } - last_sequence_number_ = broadband_frame.sequence_number(); + const auto &broadband_frame = maybe_frame.value(); - // Record the first timestamp if this is our first frame - if (frames.empty()) - { - first_timestamp_ns = broadband_frame.timestamp_ns(); - } + // Check for dropped frames + const auto dropped_frames = detect_dropped_frames( + last_sequence_number_, broadband_frame.sequence_number()); + if (dropped_frames != 0) { + spdlog::warn("Dropped: {} frames", dropped_frames); + } + last_sequence_number_ = broadband_frame.sequence_number(); - // Add the frame to our collection - frames.push_back(broadband_frame); + // Record the first timestamp if this is our first frame + if (frames.empty()) { + first_timestamp_ns = broadband_frame.timestamp_ns(); } - // TODO: Instead, we could process the entire multipart? - // After processing this multipart, check if we've reached the bin size - if (!frames.empty()) - { - const auto &last_frame = frames.back(); - if (last_frame.timestamp_ns() - first_timestamp_ns >= target_bin_size_ns) - { - // We've collected enough frames to reach the bin size - return true; - } + // Add the frame to our collection + frames.push_back(broadband_frame); + } + + // TODO: Instead, we could process the entire multipart? + // After processing this multipart, check if we've reached the bin size + if (!frames.empty()) { + const auto &last_frame = frames.back(); + if (last_frame.timestamp_ns() - first_timestamp_ns >= + target_bin_size_ns) { + // We've collected enough frames to reach the bin size + return true; } } - return false; } + return false; +} + +int FixedWeightDecoder::detect_dropped_frames( + const uint64_t last_sequence_number, + const uint64_t current_sequence_number) { + const auto expected_sequence_number = last_sequence_number + 1; + return (current_sequence_number - expected_sequence_number); +} - int FixedWeightDecoder::detect_dropped_frames(const uint64_t last_sequence_number, - const uint64_t current_sequence_number) - { - const auto expected_sequence_number = last_sequence_number + 1; - return (current_sequence_number - expected_sequence_number); +void FixedWeightDecoder::initialize_spike_detectors( + const size_t channel_count) { + // Create spike detectors for each channel + spike_detectors_.clear(); + spike_detectors_.reserve(channel_count); + + for (size_t channel_index = 0; channel_index < channel_count; + ++channel_index) { + auto detector_ptr = synapse::create_threshold_detector( + configuration_.spike_threshold_uv(), configuration_.waveform_size(), + configuration_.refractory_period_us(), sample_rate_hz_); + + if (detector_ptr == nullptr) { + spdlog::error("Failed to create spike detector for channel: {}", + channel_index); + } + spike_detectors_.push_back(std::move(detector_ptr)); } - void FixedWeightDecoder::initialize_spike_detectors(const size_t channel_count) - { - // Create spike detectors for each channel - spike_detectors_.clear(); - spike_detectors_.reserve(channel_count); + spdlog::info( + "Initialized spike detectors with threshold: {} μV, sample rate: {} Hz", + configuration_.spike_threshold_uv(), sample_rate_hz_); + spike_detectors_initialized_ = true; +} - for (size_t channel_index = 0; channel_index < channel_count; ++channel_index) - { - auto detector_ptr = synapse::create_threshold_detector(spike_threshold_, waveform_size_, - refractory_period_us_, sample_rate_hz_); +void FixedWeightDecoder::cleanup_spike_events() { + // Free memory for all detected spike events + for (auto spike_event : detected_spikes_) { + delete spike_event; + } + detected_spikes_.clear(); +} - if (detector_ptr == nullptr) - { - spdlog::error("Failed to create spike detector for channel: {}", channel_index); - } - spike_detectors_.push_back(std::move(detector_ptr)); +void FixedWeightDecoder::initialize_filters(const size_t channel_count, + const float sample_rate_hz, + const float bin_size_ms) { + if (!initialize_cursor_channels(channel_count)) { + return; + } + + // We have four channels selected, initialize our filters + spdlog::info("Initializing\tsample_rate={} Hz\tchannels={}\tbin_size={} ms", + sample_rate_hz, channel_count, bin_size_ms); + + // Create filters for each channel + bandpass_filters_.clear(); + bandpass_filters_.reserve(channel_count); + for (size_t channel_index = 0; channel_index < channel_count; + ++channel_index) { + auto filter_ptr = synapse::create_bandpass_filter( + sample_rate_hz, configuration_.low_cutoff_hz(), + configuration_.high_cutoff_hz()); + if (filter_ptr == nullptr) { + spdlog::error("Failed to create filter for channel: {}", channel_index); } + bandpass_filters_.push_back(std::move(filter_ptr)); + } + spdlog::info("Initialized filters"); + filters_initialized_ = true; +} - spdlog::info("Initialized spike detectors with threshold: {} μV, sample rate: {} Hz", - spike_threshold_, sample_rate_hz_); - spike_detectors_initialized_ = true; +bool FixedWeightDecoder::initialize_cursor_channels( + const size_t channel_count) { + if (channel_count < 4) { + spdlog::warn("Need at least four channels for joystick control"); + return false; } - void FixedWeightDecoder::cleanup_spike_events() - { - // Free memory for all detected spike events - for (auto spike_event : detected_spikes_) - { - delete spike_event; - } - detected_spikes_.clear(); + // Copy values from the repeated field to the fixed-size array + for (int i = 0; i < 4 && i < configuration_.cursor_channels_size(); i++) { + cursor_channels_[i] = configuration_.cursor_channels(i); } - void FixedWeightDecoder::initialize_filters(const size_t channel_count, - const float sample_rate_hz, - const float bin_size_ms) - { - if (!initialize_cursor_channels(channel_count)) - { - return; - } + std::stringstream ss; + ss << "Using ["; + for (const auto &channel : cursor_channels_) { + ss << channel << ","; + } - // We have four channels selected, initialize our filters - spdlog::info("Initializing\tsample_rate={} Hz\tchannels={}\tbin_size={} ms", sample_rate_hz, - channel_count, bin_size_ms); - - // Create filters for each channel - bandpass_filters_.clear(); - bandpass_filters_.reserve(channel_count); - for (size_t channel_index = 0; channel_index < channel_count; ++channel_index) - { - auto filter_ptr = synapse::create_bandpass_filter( - sample_rate_hz, low_cutoff_hz_, high_cutoff_hz_); - if (filter_ptr == nullptr) - { - spdlog::error("Failed to create filter for channel: {}", channel_index); - } - bandpass_filters_.push_back(std::move(filter_ptr)); - } - spdlog::info("Initialized filters"); - filters_initialized_ = true; + ss << "] for cursor control"; + spdlog::info("{}", ss.str()); + return true; +} + +bool FixedWeightDecoder::validate_configuration( + const app::ExampleAppConfig &config) { + // FIlters should be above zero, high cutoff should be above low cutoff + if (config.low_cutoff_hz() <= 0 || config.high_cutoff_hz() <= 0 || + config.low_cutoff_hz() >= config.high_cutoff_hz()) { + spdlog::error( + "Invalid filter configuration: low_cutoff={} Hz, high_cutoff={} Hz", + config.low_cutoff_hz(), config.high_cutoff_hz()); + return false; } - bool FixedWeightDecoder::initialize_cursor_channels(const size_t channel_count) - { - if (channel_count < 4) - { - spdlog::warn("Need at least four channels for joystick control"); - return false; - } + // Spike threshold should be above zero + if (config.spike_threshold_uv() <= 0) { + spdlog::error("Invalid spike threshold: {}", config.spike_threshold_uv()); + return false; + } - // Select four random channels - // std::vector all_channels(channel_count); - // std::iota(all_channels.begin(), all_channels.end(), 0); + // Refractory period should be above zero + if (config.refractory_period_us() <= 0) { + spdlog::error("Invalid refractory period: {}", + config.refractory_period_us()); + return false; + } - // // Randomly sample 4 channels - // std::random_device rd; - // std::mt19937 gen(rd()); - // std::sample(all_channels.begin(), all_channels.end(), cursor_channels_.begin(), 4, gen); + // Window size should be above zero + if (config.window_size() <= 0) { + spdlog::error("Invalid window size: {}", config.window_size()); + return false; + } - std::stringstream ss; - ss << "Using ["; - for (const auto &channel : cursor_channels_) - { - ss << channel << ","; - } + // Max expected rate should be above zero + if (config.max_expected_rate() <= 0) { + spdlog::error("Invalid max expected rate: {}", config.max_expected_rate()); + return false; + } - ss << "] for cursor control"; - spdlog::info("{}", ss.str()); - return true; + // Should only have 4 cursor channels + if (config.cursor_channels_size() != 4) { + spdlog::error("Invalid number of cursor channels: {}, expected 4", + config.cursor_channels_size()); + return false; } + + return true; +} + } // namespace app -int main(const int, const char **) -{ +int main(const int, const char **) { return synapse::Entrypoint(); } diff --git a/src/fixed_weight_decoder.hpp b/src/fixed_weight_decoder.hpp index 3db9c5d..565b795 100644 --- a/src/fixed_weight_decoder.hpp +++ b/src/fixed_weight_decoder.hpp @@ -1,90 +1,91 @@ #pragma once #include -#include #include +#include #include -#include -#include #include #include +#include +#include #include "api/datatype.pb.h" #include "api/nodes/broadband_source.pb.h" -namespace app -{ - // 10 hz - constexpr auto kPublishRateSec = 1.0 / 10.0; - class FixedWeightDecoder : public synapse::App - { - public: - FixedWeightDecoder(); - - virtual bool setup() override; - - protected: - virtual void main() override; - - private: - // Use this to detect if there is frame drops - uint64_t last_sequence_number_ = 0; - - // A timer to provide a consistent publishing cadence for joystick commands - synapse::Timer publish_rate_limiter_; - - // We want to filter the incoming broadband data, so do so here - std::atomic filters_initialized_{false}; - - // TODO(gilbert): This should probably be configurable? - const float low_cutoff_hz_ = 200.0; - const float high_cutoff_hz_ = 5000.0; - static constexpr int kSpectralFilterOrder = 2; - std::vector> bandpass_filters_; - - // Spike detection configuration and detectors - std::atomic spike_detectors_initialized_{false}; - const float spike_threshold_ = 50.0; // Threshold in microvolts - const uint32_t waveform_size_ = 50; // Total samples per waveform - const uint64_t refractory_period_us_ = 1000; // 1ms refractory period - float sample_rate_hz_ = 30000.0; // Will be updated during initialization - std::vector> spike_detectors_; - - // Collection of detected spikes - std::vector detected_spikes_; - - // Spike binning and cursor control parameters - static constexpr int window_size_ = 5; // Number of bins to use for firing rate estimation - static constexpr float max_expected_rate_ = 10.0f; // For normalization - std::deque> - spike_count_window_; // Window buffer to store binned spike counts - - // We will select 4 channels randomly for cursor control - std::array cursor_channels_ = {0, 7, 16, 30}; - - // Waits until a set of broadband frames are read from the node - // Returns false if there was an error reading - bool wait_for_frames(std::vector &frames, const float bin_size_ms); - - // If not zero, we dropped some frames, determine what to do - int detect_dropped_frames(const uint64_t last_sequence_number, - const uint64_t current_sequence_number); - - // Randomly select channels to use for cursor control - bool initialize_cursor_channels(const size_t channel_count); - - // Before starting, set up our filters. - // We can use the first broadband frame to do this initialization - void initialize_filters(const size_t channel_count, const float sample_rate_hz, - const float bin_size_ms); - - // Initialize spike detectors for each channel - void initialize_spike_detectors(const size_t channel_count); - - // Clean up any allocated spike events - void cleanup_spike_events(); - - // Calculate cursor position from spike counts - std::pair calculate_cursor_position(const std::vector &spike_counts); - }; -} // namespace app \ No newline at end of file +#include "example_app.pb.h" + +namespace app { +// 10 hz +constexpr auto kPublishRateSec = 1.0 / 10.0; +class FixedWeightDecoder : public synapse::App { +public: + FixedWeightDecoder(); + + virtual bool setup() override; + +protected: + virtual void main() override; + +private: + // Use this to detect if there is frame drops + uint64_t last_sequence_number_ = 0; + + // A timer to provide a consistent publishing cadence for joystick commands + synapse::Timer publish_rate_limiter_; + + // We want to filter the incoming broadband data, so do so here + std::atomic filters_initialized_{false}; + + // App parameters + app::ExampleAppConfig configuration_; + + // Filter configuration + static constexpr int kSpectralFilterOrder = 2; + std::vector> bandpass_filters_; + + // Spike detection configuration and detectors + std::atomic spike_detectors_initialized_{false}; + float sample_rate_hz_ = 30000.0; // Will be updated during initialization + std::vector> spike_detectors_; + + // Collection of detected spikes + std::vector detected_spikes_; + + // Spike binning and cursor control parameters + std::deque> + spike_count_window_; // Window buffer to store binned spike counts + + // We will select 4 channels randomly for cursor control + // Default to a random selection + std::array cursor_channels_ = {0, 7, 16, 30}; + + // Waits until a set of broadband frames are read from the node + // Returns false if there was an error reading + bool wait_for_frames(std::vector &frames, + const float bin_size_ms); + + // If not zero, we dropped some frames, determine what to do + int detect_dropped_frames(const uint64_t last_sequence_number, + const uint64_t current_sequence_number); + + // Randomly select channels to use for cursor control + bool initialize_cursor_channels(const size_t channel_count); + + // Before starting, set up our filters. + // We can use the first broadband frame to do this initialization + void initialize_filters(const size_t channel_count, + const float sample_rate_hz, const float bin_size_ms); + + // Initialize spike detectors for each channel + void initialize_spike_detectors(const size_t channel_count); + + // Clean up any allocated spike events + void cleanup_spike_events(); + + // Calculate cursor position from spike counts + std::pair + calculate_cursor_position(const std::vector &spike_counts); + + bool validate_configuration(const app::ExampleAppConfig &config); +}; +} // namespace app From ccff06b1a638d54271cf529e8f8f1274e5bfa00c Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Thu, 22 May 2025 13:05:40 -0700 Subject: [PATCH 02/13] Lint all the files --- client/listen_to_joystick.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 client/listen_to_joystick.py diff --git a/client/listen_to_joystick.py b/client/listen_to_joystick.py old mode 100644 new mode 100755 From ca07f00258f4fc8d61d16262cf1d50090e8fd30e Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Thu, 22 May 2025 13:05:44 -0700 Subject: [PATCH 03/13] Lint all the files --- .clang-format | 9 +++ src/fixed_weight_decoder.cpp | 130 ++++++++++++++--------------------- src/fixed_weight_decoder.hpp | 26 ++++--- 3 files changed, 73 insertions(+), 92 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d6d6395 --- /dev/null +++ b/.clang-format @@ -0,0 +1,9 @@ +--- +Language: Cpp +BasedOnStyle: Google + +# Headstage differences +ColumnLimit: 100 +SortIncludes: false +DerivePointerAlignment: false +PointerAlignment: Left diff --git a/src/fixed_weight_decoder.cpp b/src/fixed_weight_decoder.cpp index f434575..2971960 100644 --- a/src/fixed_weight_decoder.cpp +++ b/src/fixed_weight_decoder.cpp @@ -1,25 +1,23 @@ #include "fixed_weight_decoder.hpp" -#include // for std::clamp +#include // for std::clamp #include #include -#include // for parse_protobuf_message +#include // for parse_protobuf_message #include namespace app { // Helper function to clamp a value between min and max -template T clamp(T value, T min, T max) { +template +T clamp(T value, T min, T max) { return (value < min) ? min : (value > max) ? max : value; } -FixedWeightDecoder::FixedWeightDecoder() - : publish_rate_limiter_(kPublishRateSec) {} +FixedWeightDecoder::FixedWeightDecoder() : publish_rate_limiter_(kPublishRateSec) {} bool FixedWeightDecoder::setup() { // Make sure our app configuration is valid if (!get_app_parameters( - [this](const app::ExampleAppConfig &config) { - return validate_configuration(config); - }, + [this](const app::ExampleAppConfig& config) { return validate_configuration(config); }, configuration_)) { spdlog::error("Failed to validate app parameters from app configuration"); return false; @@ -89,34 +87,32 @@ void FixedWeightDecoder::main() { // TODO/NOTE: you could drop out early based on timestamps std::vector> filtered_channel_data; filtered_channel_data.resize(broadband_frames.at(0).frame_data_size()); - for (auto &channel_vector : filtered_channel_data) { + for (auto& channel_vector : filtered_channel_data) { channel_vector.reserve(broadband_frames.size()); } // Create a vector to count spikes per channel in this batch - std::vector spike_counts(broadband_frames.at(0).frame_data_size(), - 0); + std::vector spike_counts(broadband_frames.at(0).frame_data_size(), 0); - for (const auto &frame : broadband_frames) { - const auto &frame_data = frame.frame_data(); + for (const auto& frame : broadband_frames) { + const auto& frame_data = frame.frame_data(); const uint64_t frame_timestamp_ns = frame.timestamp_ns(); for (int channel_id = 0; channel_id < frame_data.size(); ++channel_id) { // TODO: bounds checking - but we might not even want this way of doing // things - auto &channel_filter = bandpass_filters_.at(channel_id); - const float filtered_data = - channel_filter->filter(frame_data[channel_id]); + auto& channel_filter = bandpass_filters_.at(channel_id); + const float filtered_data = channel_filter->filter(frame_data[channel_id]); filtered_channel_data.at(channel_id).push_back(filtered_data); // 3. Detect spikes on the filtered data if (spike_detectors_initialized_) { - auto &spike_detector = spike_detectors_.at(channel_id); + auto& spike_detector = spike_detectors_.at(channel_id); // Pass the filtered data to the spike detector along with the frame // timestamp The detector handles the rest internally - synapse::SpikeEvent *spike_event = spike_detector->detect( - filtered_data, frame_timestamp_ns, channel_id); + synapse::SpikeEvent* spike_event = + spike_detector->detect(filtered_data, frame_timestamp_ns, channel_id); if (spike_event != nullptr) { // Store the detected spike for further processing @@ -148,25 +144,21 @@ void FixedWeightDecoder::main() { for (int i = 0; i < 4; i++) { size_t ch = cursor_channels_[i]; - for (const auto &bin_counts : spike_count_window_) { + for (const auto& bin_counts : spike_count_window_) { firing_rates[i] += bin_counts[ch]; } - firing_rates[i] /= configuration_.window_size(); // Average over window + firing_rates[i] /= configuration_.window_size(); // Average over window } // Calculate x-position based on first channel pair (differential) - cursor_x = firing_rates[1] - - firing_rates[0]; // Positive = right, negative = left + cursor_x = firing_rates[1] - firing_rates[0]; // Positive = right, negative = left // Calculate y-position based on second channel pair (differential) - cursor_y = - firing_rates[3] - firing_rates[2]; // Positive = up, negative = down + cursor_y = firing_rates[3] - firing_rates[2]; // Positive = up, negative = down // Normalize to reasonable range (-1 to 1) - cursor_x = - clamp(cursor_x / configuration_.max_expected_rate(), -1.0f, 1.0f); - cursor_y = - clamp(cursor_y / configuration_.max_expected_rate(), -1.0f, 1.0f); + cursor_x = clamp(cursor_x / configuration_.max_expected_rate(), -1.0f, 1.0f); + cursor_y = clamp(cursor_y / configuration_.max_expected_rate(), -1.0f, 1.0f); } else { // Not enough data in window yet, use default values cursor_x = 0.0f; @@ -176,11 +168,9 @@ void FixedWeightDecoder::main() { // Create a tensor with the cursor position synapse::Tensor output_tensor; const auto tensor_shape = {2}; - output_tensor.mutable_shape()->Add(tensor_shape.begin(), - tensor_shape.end()); + output_tensor.mutable_shape()->Add(tensor_shape.begin(), tensor_shape.end()); output_tensor.set_dtype(synapse::Tensor_DType_DT_FLOAT); - output_tensor.set_endianness( - synapse::Tensor_Endianness_TENSOR_LITTLE_ENDIAN); + output_tensor.set_endianness(synapse::Tensor_Endianness_TENSOR_LITTLE_ENDIAN); // Use the calculated cursor position instead of raw data values output_tensor.set_data(synapse::pack_tensor_data({cursor_x, cursor_y})); @@ -207,8 +197,8 @@ void FixedWeightDecoder::main() { } } -bool FixedWeightDecoder::wait_for_frames( - std::vector &frames, float bin_size_ms) { +bool FixedWeightDecoder::wait_for_frames(std::vector& frames, + float bin_size_ms) { if (bin_size_ms <= 0) { spdlog::warn("invalid bin size of: {}", bin_size_ms); return false; @@ -239,11 +229,10 @@ bool FixedWeightDecoder::wait_for_frames( frames.reserve(frames.size() + messages.size()); // Process each received message in this multipart - for (auto &message : messages) { + for (auto& message : messages) { // Parse the message into a BroadbandFrame const auto maybe_frame = - synapse::parse_protobuf_message( - std::move(message)); + synapse::parse_protobuf_message(std::move(message)); if (!maybe_frame.has_value()) { spdlog::warn("Failed to parse broadband frame"); // If we have no frames at all, return false @@ -254,11 +243,11 @@ bool FixedWeightDecoder::wait_for_frames( return true; } - const auto &broadband_frame = maybe_frame.value(); + const auto& broadband_frame = maybe_frame.value(); // Check for dropped frames - const auto dropped_frames = detect_dropped_frames( - last_sequence_number_, broadband_frame.sequence_number()); + const auto dropped_frames = + detect_dropped_frames(last_sequence_number_, broadband_frame.sequence_number()); if (dropped_frames != 0) { spdlog::warn("Dropped: {} frames", dropped_frames); } @@ -276,9 +265,8 @@ bool FixedWeightDecoder::wait_for_frames( // TODO: Instead, we could process the entire multipart? // After processing this multipart, check if we've reached the bin size if (!frames.empty()) { - const auto &last_frame = frames.back(); - if (last_frame.timestamp_ns() - first_timestamp_ns >= - target_bin_size_ns) { + const auto& last_frame = frames.back(); + if (last_frame.timestamp_ns() - first_timestamp_ns >= target_bin_size_ns) { // We've collected enough frames to reach the bin size return true; } @@ -287,35 +275,30 @@ bool FixedWeightDecoder::wait_for_frames( return false; } -int FixedWeightDecoder::detect_dropped_frames( - const uint64_t last_sequence_number, - const uint64_t current_sequence_number) { +int FixedWeightDecoder::detect_dropped_frames(const uint64_t last_sequence_number, + const uint64_t current_sequence_number) { const auto expected_sequence_number = last_sequence_number + 1; return (current_sequence_number - expected_sequence_number); } -void FixedWeightDecoder::initialize_spike_detectors( - const size_t channel_count) { +void FixedWeightDecoder::initialize_spike_detectors(const size_t channel_count) { // Create spike detectors for each channel spike_detectors_.clear(); spike_detectors_.reserve(channel_count); - for (size_t channel_index = 0; channel_index < channel_count; - ++channel_index) { + for (size_t channel_index = 0; channel_index < channel_count; ++channel_index) { auto detector_ptr = synapse::create_threshold_detector( configuration_.spike_threshold_uv(), configuration_.waveform_size(), configuration_.refractory_period_us(), sample_rate_hz_); if (detector_ptr == nullptr) { - spdlog::error("Failed to create spike detector for channel: {}", - channel_index); + spdlog::error("Failed to create spike detector for channel: {}", channel_index); } spike_detectors_.push_back(std::move(detector_ptr)); } - spdlog::info( - "Initialized spike detectors with threshold: {} μV, sample rate: {} Hz", - configuration_.spike_threshold_uv(), sample_rate_hz_); + spdlog::info("Initialized spike detectors with threshold: {} μV, sample rate: {} Hz", + configuration_.spike_threshold_uv(), sample_rate_hz_); spike_detectors_initialized_ = true; } @@ -327,25 +310,22 @@ void FixedWeightDecoder::cleanup_spike_events() { detected_spikes_.clear(); } -void FixedWeightDecoder::initialize_filters(const size_t channel_count, - const float sample_rate_hz, +void FixedWeightDecoder::initialize_filters(const size_t channel_count, const float sample_rate_hz, const float bin_size_ms) { if (!initialize_cursor_channels(channel_count)) { return; } // We have four channels selected, initialize our filters - spdlog::info("Initializing\tsample_rate={} Hz\tchannels={}\tbin_size={} ms", - sample_rate_hz, channel_count, bin_size_ms); + spdlog::info("Initializing\tsample_rate={} Hz\tchannels={}\tbin_size={} ms", sample_rate_hz, + channel_count, bin_size_ms); // Create filters for each channel bandpass_filters_.clear(); bandpass_filters_.reserve(channel_count); - for (size_t channel_index = 0; channel_index < channel_count; - ++channel_index) { + for (size_t channel_index = 0; channel_index < channel_count; ++channel_index) { auto filter_ptr = synapse::create_bandpass_filter( - sample_rate_hz, configuration_.low_cutoff_hz(), - configuration_.high_cutoff_hz()); + sample_rate_hz, configuration_.low_cutoff_hz(), configuration_.high_cutoff_hz()); if (filter_ptr == nullptr) { spdlog::error("Failed to create filter for channel: {}", channel_index); } @@ -355,8 +335,7 @@ void FixedWeightDecoder::initialize_filters(const size_t channel_count, filters_initialized_ = true; } -bool FixedWeightDecoder::initialize_cursor_channels( - const size_t channel_count) { +bool FixedWeightDecoder::initialize_cursor_channels(const size_t channel_count) { if (channel_count < 4) { spdlog::warn("Need at least four channels for joystick control"); return false; @@ -369,7 +348,7 @@ bool FixedWeightDecoder::initialize_cursor_channels( std::stringstream ss; ss << "Using ["; - for (const auto &channel : cursor_channels_) { + for (const auto& channel : cursor_channels_) { ss << channel << ","; } @@ -378,14 +357,12 @@ bool FixedWeightDecoder::initialize_cursor_channels( return true; } -bool FixedWeightDecoder::validate_configuration( - const app::ExampleAppConfig &config) { +bool FixedWeightDecoder::validate_configuration(const app::ExampleAppConfig& config) { // FIlters should be above zero, high cutoff should be above low cutoff if (config.low_cutoff_hz() <= 0 || config.high_cutoff_hz() <= 0 || config.low_cutoff_hz() >= config.high_cutoff_hz()) { - spdlog::error( - "Invalid filter configuration: low_cutoff={} Hz, high_cutoff={} Hz", - config.low_cutoff_hz(), config.high_cutoff_hz()); + spdlog::error("Invalid filter configuration: low_cutoff={} Hz, high_cutoff={} Hz", + config.low_cutoff_hz(), config.high_cutoff_hz()); return false; } @@ -397,8 +374,7 @@ bool FixedWeightDecoder::validate_configuration( // Refractory period should be above zero if (config.refractory_period_us() <= 0) { - spdlog::error("Invalid refractory period: {}", - config.refractory_period_us()); + spdlog::error("Invalid refractory period: {}", config.refractory_period_us()); return false; } @@ -424,8 +400,6 @@ bool FixedWeightDecoder::validate_configuration( return true; } -} // namespace app +} // namespace app -int main(const int, const char **) { - return synapse::Entrypoint(); -} +int main(const int, const char**) { return synapse::Entrypoint(); } diff --git a/src/fixed_weight_decoder.hpp b/src/fixed_weight_decoder.hpp index 565b795..bea7d5a 100644 --- a/src/fixed_weight_decoder.hpp +++ b/src/fixed_weight_decoder.hpp @@ -18,15 +18,15 @@ namespace app { // 10 hz constexpr auto kPublishRateSec = 1.0 / 10.0; class FixedWeightDecoder : public synapse::App { -public: + public: FixedWeightDecoder(); virtual bool setup() override; -protected: + protected: virtual void main() override; -private: + private: // Use this to detect if there is frame drops uint64_t last_sequence_number_ = 0; @@ -45,15 +45,15 @@ class FixedWeightDecoder : public synapse::App { // Spike detection configuration and detectors std::atomic spike_detectors_initialized_{false}; - float sample_rate_hz_ = 30000.0; // Will be updated during initialization + float sample_rate_hz_ = 30000.0; // Will be updated during initialization std::vector> spike_detectors_; // Collection of detected spikes - std::vector detected_spikes_; + std::vector detected_spikes_; // Spike binning and cursor control parameters std::deque> - spike_count_window_; // Window buffer to store binned spike counts + spike_count_window_; // Window buffer to store binned spike counts // We will select 4 channels randomly for cursor control // Default to a random selection @@ -61,8 +61,7 @@ class FixedWeightDecoder : public synapse::App { // Waits until a set of broadband frames are read from the node // Returns false if there was an error reading - bool wait_for_frames(std::vector &frames, - const float bin_size_ms); + bool wait_for_frames(std::vector& frames, const float bin_size_ms); // If not zero, we dropped some frames, determine what to do int detect_dropped_frames(const uint64_t last_sequence_number, @@ -73,8 +72,8 @@ class FixedWeightDecoder : public synapse::App { // Before starting, set up our filters. // We can use the first broadband frame to do this initialization - void initialize_filters(const size_t channel_count, - const float sample_rate_hz, const float bin_size_ms); + void initialize_filters(const size_t channel_count, const float sample_rate_hz, + const float bin_size_ms); // Initialize spike detectors for each channel void initialize_spike_detectors(const size_t channel_count); @@ -83,9 +82,8 @@ class FixedWeightDecoder : public synapse::App { void cleanup_spike_events(); // Calculate cursor position from spike counts - std::pair - calculate_cursor_position(const std::vector &spike_counts); + std::pair calculate_cursor_position(const std::vector& spike_counts); - bool validate_configuration(const app::ExampleAppConfig &config); + bool validate_configuration(const app::ExampleAppConfig& config); }; -} // namespace app +} // namespace app From c88480453619c1acafe17132dfd7a1cd50e87b64 Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Thu, 22 May 2025 16:59:15 -0700 Subject: [PATCH 04/13] Updated cmake to build python bindings too --- .gitignore | 2 ++ CMakeLists.txt | 2 ++ cmake/protos.cmake | 61 ++++++++++++++++++++++++++++++++++++-- config/simulator_32ch.json | 1 + manifest.json | 4 ++- 5 files changed, 67 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 16d4cbc..af0708e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.pb.cc *.pb.h +*_pb2.py .cache/ .vscode/ @@ -79,3 +80,4 @@ Testing/ .synapse_deploy_cache.json app-sdk/ vcpkg_installed/ +*.desc diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a40072..d38f1ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,8 @@ generate_protobufs( TARGET synapse-example-app OUT_PROTO_DIR APP_PROTO_OUT_DIR PROTO_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/proto" + GENERATE_PYTHON + PYTHON_OUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/proto" ) # Be strict about the standard diff --git a/cmake/protos.cmake b/cmake/protos.cmake index 1991038..3845e45 100644 --- a/cmake/protos.cmake +++ b/cmake/protos.cmake @@ -1,7 +1,7 @@ function(generate_protobufs) cmake_parse_arguments(PARSE_ARGV 0 "arg" - "" - "TARGET;OUT_PROTO_DIR" + "GENERATE_PYTHON" + "TARGET;OUT_PROTO_DIR;PYTHON_OUT_DIR" "PROTO_DIRS;PROTO_FILES" ) @@ -17,6 +17,9 @@ function(generate_protobufs) if(NOT DEFINED arg_PROTO_DIRS AND NOT DEFINED arg_PROTO_FILES) message(FATAL_ERROR "At least one of PROTO_DIRS or PROTO_FILES must be specified.") endif() + if(arg_GENERATE_PYTHON AND NOT DEFINED arg_PYTHON_OUT_DIR) + message(FATAL_ERROR "PYTHON_OUT_DIR must be specified when GENERATE_PYTHON is enabled.") + endif() set(PROTO_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/include) file(MAKE_DIRECTORY ${PROTO_OUT_DIR}) @@ -50,9 +53,17 @@ function(generate_protobufs) foreach(PROTO_FILE ${arg_PROTO_FILES}) get_filename_component(ABS_PROTO_FILE ${PROTO_FILE} REALPATH) list(APPEND PROTOS ${ABS_PROTO_FILE}) + + # Also add the directory containing this proto file to include dirs + get_filename_component(PROTO_DIR ${ABS_PROTO_FILE} DIRECTORY) + list(APPEND PROTO_INCLUDE_DIRS ${PROTO_DIR}) endforeach() endif() + # Remove duplicates from PROTO_INCLUDE_DIRS + list(REMOVE_DUPLICATES PROTO_INCLUDE_DIRS) + + # Generate C++ protobufs (existing functionality) protobuf_generate( TARGET ${arg_TARGET} LANGUAGE cpp @@ -62,6 +73,52 @@ function(generate_protobufs) OUT_VAR PROTO_SOURCES ) + # Generate Python protobufs if requested + if(arg_GENERATE_PYTHON) + file(MAKE_DIRECTORY ${arg_PYTHON_OUT_DIR}) + + # Generate Python bindings + protobuf_generate( + TARGET ${arg_TARGET} + LANGUAGE python + IMPORT_DIRS ${PROTO_INCLUDE_DIRS} + PROTOS ${PROTOS} + PROTOC_OUT_DIR ${arg_PYTHON_OUT_DIR} + OUT_VAR PYTHON_PROTO_SOURCES + ) + + # Convert PROTO_INCLUDE_DIRS to --proto_path arguments BEFORE using them + set(PROTO_INCLUDE_DIRS_ARGS "") + foreach(INCLUDE_DIR ${PROTO_INCLUDE_DIRS}) + list(APPEND PROTO_INCLUDE_DIRS_ARGS --proto_path=${INCLUDE_DIR}) + endforeach() + + # Generate descriptor sets (.desc files) for runtime loading + set(DESC_OUT_DIR ${arg_PYTHON_OUT_DIR}) + foreach(PROTO_FILE ${PROTOS}) + get_filename_component(PROTO_NAME ${PROTO_FILE} NAME_WE) + set(DESC_FILE ${DESC_OUT_DIR}/${PROTO_NAME}.desc) + + add_custom_command( + OUTPUT ${DESC_FILE} + COMMAND ${Protobuf_PROTOC_EXECUTABLE} + ARGS --descriptor_set_out=${DESC_FILE} + --include_imports + ${PROTO_INCLUDE_DIRS_ARGS} + ${PROTO_FILE} + DEPENDS ${PROTO_FILE} + COMMENT "Generating descriptor set for ${PROTO_NAME}" + VERBATIM + ) + + # Add to target dependencies + add_custom_target(${arg_TARGET}_${PROTO_NAME}_desc DEPENDS ${DESC_FILE}) + add_dependencies(${arg_TARGET} ${arg_TARGET}_${PROTO_NAME}_desc) + endforeach() + + message(STATUS "Python protobufs and descriptors will be generated in: ${arg_PYTHON_OUT_DIR}") + endif() + # NOTE: Uncomment this to generate the gRPC code if we ever need it # protobuf_generate( # TARGET ${arg_TARGET} diff --git a/config/simulator_32ch.json b/config/simulator_32ch.json index 4d9bf26..72dcc06 100644 --- a/config/simulator_32ch.json +++ b/config/simulator_32ch.json @@ -6,6 +6,7 @@ "application": { "name": "synapse-example-app", "parameters": { + "@type": "type.googleapis.com/app.ExampleAppConfig", "low_cutoff_hz": 200.0, "high_cutoff_hz": 5000.0, "spike_threshold_uv": 50.0, diff --git a/manifest.json b/manifest.json index c164bbe..374c8e8 100644 --- a/manifest.json +++ b/manifest.json @@ -1,3 +1,5 @@ { - "name": "synapse-example-app" + "name": "synapse-example-app", + "proto_files": ["proto/example_app.proto"], + "device_config_path": "config/simulator_32ch.json" } From b4596811bd3ce830e9ab8c0e44262704c256a0d1 Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Fri, 23 May 2025 16:26:45 -0700 Subject: [PATCH 05/13] Clean up cmake --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d38f1ba..b6292e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ include("cmake/protos.cmake") option(BUILD_FOR_ARM64 "Build for ARM64 architecture" OFF) option(USE_LOCAL_SDK "Use locally built SDK instead of system installation" OFF) -set(LOCAL_SDK_PATH "app-sdk") +set(LOCAL_SDK_PATH "app-sdk" CACHE PATH "Path to local SDK build directory") if (BUILD_FOR_ARM64 AND NOT CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") message(STATUS "Cross compiling for ARM64") set(CMAKE_SYSTEM_NAME Linux) @@ -21,7 +21,7 @@ find_package(Protobuf REQUIRED) find_package(spdlog REQUIRED CONFIG) find_package(iir REQUIRED CONFIG) -if(true) +if(USE_LOCAL_SDK) if(NOT LOCAL_SDK_PATH) message(FATAL_ERROR "USE_LOCAL_SDK is ON but LOCAL_SDK_PATH is not set") endif() From c5bc85808ecd10e394d89ee0873b23f467e3a3b2 Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Fri, 23 May 2025 16:31:23 -0700 Subject: [PATCH 06/13] Updated protos --- external/sciencecorp/synapse-api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/sciencecorp/synapse-api b/external/sciencecorp/synapse-api index bd47aac..d2f08b6 160000 --- a/external/sciencecorp/synapse-api +++ b/external/sciencecorp/synapse-api @@ -1 +1 @@ -Subproject commit bd47aacf3894acce1a51e67118c64820d4a885dd +Subproject commit d2f08b69eaae1cb37371734f5fac98f17814e09f From e7f8c6b49d331f996d03c7e0214bf0b5f9322bab Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Tue, 27 May 2025 16:56:05 -0700 Subject: [PATCH 07/13] Updated api --- client/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 163 bytes .../listen_to_joystick.cpython-312.pyc | Bin 0 -> 4221 bytes client/__pycache__/request_reset.cpython-312.pyc | Bin 0 -> 2098 bytes client/__pycache__/update_config.cpython-312.pyc | Bin 0 -> 1759 bytes external/sciencecorp/synapse-api | 2 +- proto/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 162 bytes .../__pycache__/example_app_pb2.cpython-312.pyc | Bin 0 -> 1526 bytes 7 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 client/__pycache__/__init__.cpython-312.pyc create mode 100644 client/__pycache__/listen_to_joystick.cpython-312.pyc create mode 100644 client/__pycache__/request_reset.cpython-312.pyc create mode 100644 client/__pycache__/update_config.cpython-312.pyc create mode 100644 proto/__pycache__/__init__.cpython-312.pyc create mode 100644 proto/__pycache__/example_app_pb2.cpython-312.pyc diff --git a/client/__pycache__/__init__.cpython-312.pyc b/client/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad577de66ac87d0a24b4e91604e87e26667ddd43 GIT binary patch literal 163 zcmX@j%ge<81c?%c86f&Gh(HIQS%4zb87dhx8U0o=6fpsLpFwJVIq7HQ=cekXXXYfO z7M19i=NDxc7bGU9>K7+x>K9k$B^DH?>ZVpC<`(3n>LwNx=qKl7rskFC$H!;pWtPOp k>lIY~;;_lhPbtkwwJTx;8p#O6#URE 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 literal 0 HcmV?d00001 diff --git a/proto/__pycache__/example_app_pb2.cpython-312.pyc b/proto/__pycache__/example_app_pb2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a59c669b8548e77765b3a0ba13c24cacf86e7b1f GIT binary patch literal 1526 zcmaJ>OK;mo5MGkjYh_BZ8YfB|C$f|Hk`xA|rGy1{XS=@9CrOahk=$j1 zs^IR;QGXYc1dfO}r5{I>-MRlp;ppB10PHS~kp#NX_hU#>IDT1}XmY@f54eE_SQT92 zTwHR%4dNQHMr<*4c>OW43DaGIvks#!HK>&>)yrkVvIfOu`P4saQSu-r++e2TQYNhA z&_WKSmQV3Utwb$^byqLxoCqpXs+$%jj86+S;RHF*YmP;b?l{ZAS!qSU+219Y(z>oS z$62R#*{q}|p=!~6WYk?+E~DyUM#@SHa{3}nRLx3tgqH*U3QTjy+#|@XGQz9W!l?c# zqXa&YYF~dv%9Pa*HxEf>OweljD!jl*ndyeOQ3E-InN)1znNZ;U0LJ%C8;cTxE#ZK? z3@2*(0U`$uF@#{qmn{@>QL1H?6{u zsF1k9g`%Z!AjEwyIU){nO7}KjoceR=O(5;dwdY&Uw6(3R^_MR;pFTeQTkxm<_)Zn& zwSu;t*S^@%3ftzSP-nc!+b{E8q+-#MZgG*LkFI_056^SLxn3b_HrDKVZDTNl-0S+csWU=~u-*plrO{47 zL+faGN=JqMu7yZ`iuS$x^R-bFvHQCzpkLv}+X_P;Rv7x6e#BuV-U0%`6I z_~;FI6#PCu3xoKLV|b+vXIpUgHN4r3oI#M7J65OL>aCV~>$N)HR##i<>KOo^Y{W#d z2`$!yf5g+@tTv+^FyD-}!F&tMe-D3rasod47jyhT`|02WY;}}GTbXGoGvDp}s@(2m zt~PI<$djFW51I?d@oO#l`iZ>KxqE+T Date: Tue, 27 May 2025 16:56:23 -0700 Subject: [PATCH 08/13] Updated api --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index af0708e..e58fb04 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.pb.cc *.pb.h *_pb2.py +*.pyc .cache/ .vscode/ From 0a305a8f700d3949771065bc65066af7d08a463c Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Tue, 27 May 2025 16:56:56 -0700 Subject: [PATCH 09/13] Remove pycache --- client/__pycache__/__init__.cpython-312.pyc | Bin 163 -> 0 bytes .../listen_to_joystick.cpython-312.pyc | Bin 4221 -> 0 bytes client/__pycache__/request_reset.cpython-312.pyc | Bin 2098 -> 0 bytes client/__pycache__/update_config.cpython-312.pyc | Bin 1759 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 client/__pycache__/__init__.cpython-312.pyc delete mode 100644 client/__pycache__/listen_to_joystick.cpython-312.pyc delete mode 100644 client/__pycache__/request_reset.cpython-312.pyc delete mode 100644 client/__pycache__/update_config.cpython-312.pyc diff --git a/client/__pycache__/__init__.cpython-312.pyc b/client/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index ad577de66ac87d0a24b4e91604e87e26667ddd43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163 zcmX@j%ge<81c?%c86f&Gh(HIQS%4zb87dhx8U0o=6fpsLpFwJVIq7HQ=cekXXXYfO z7M19i=NDxc7bGU9>K7+x>K9k$B^DH?>ZVpC<`(3n>LwNx=qKl7rskFC$H!;pWtPOp k>lIY~;;_lhPbtkwwJTx;8p#O6#URE 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 Date: Tue, 27 May 2025 16:58:35 -0700 Subject: [PATCH 10/13] Removed pycache --- proto/__pycache__/__init__.cpython-312.pyc | Bin 162 -> 0 bytes .../__pycache__/example_app_pb2.cpython-312.pyc | Bin 1526 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 proto/__pycache__/__init__.cpython-312.pyc delete mode 100644 proto/__pycache__/example_app_pb2.cpython-312.pyc diff --git a/proto/__pycache__/__init__.cpython-312.pyc b/proto/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index e8d7bb613fd315601954d28d2802d08c05af267d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmX@j%ge<81Zfh686f&Gh(HIQS%4zb87dhx8U0o=6fpsLpFwJVIqGNR=cekXXXYfO z7M19i=NDxc7bGU9>K7+x>K9k$B^DH?>ZVpC<`(3n>LwNx=ob{_m*nfm$7kkcmc+;F j6;%G>u*uC&Da}c>D`Ewj$Oy#6AjU^#Mn=XWW*`dyWBVuo diff --git a/proto/__pycache__/example_app_pb2.cpython-312.pyc b/proto/__pycache__/example_app_pb2.cpython-312.pyc deleted file mode 100644 index a59c669b8548e77765b3a0ba13c24cacf86e7b1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1526 zcmaJ>OK;mo5MGkjYh_BZ8YfB|C$f|Hk`xA|rGy1{XS=@9CrOahk=$j1 zs^IR;QGXYc1dfO}r5{I>-MRlp;ppB10PHS~kp#NX_hU#>IDT1}XmY@f54eE_SQT92 zTwHR%4dNQHMr<*4c>OW43DaGIvks#!HK>&>)yrkVvIfOu`P4saQSu-r++e2TQYNhA z&_WKSmQV3Utwb$^byqLxoCqpXs+$%jj86+S;RHF*YmP;b?l{ZAS!qSU+219Y(z>oS z$62R#*{q}|p=!~6WYk?+E~DyUM#@SHa{3}nRLx3tgqH*U3QTjy+#|@XGQz9W!l?c# zqXa&YYF~dv%9Pa*HxEf>OweljD!jl*ndyeOQ3E-InN)1znNZ;U0LJ%C8;cTxE#ZK? z3@2*(0U`$uF@#{qmn{@>QL1H?6{u zsF1k9g`%Z!AjEwyIU){nO7}KjoceR=O(5;dwdY&Uw6(3R^_MR;pFTeQTkxm<_)Zn& zwSu;t*S^@%3ftzSP-nc!+b{E8q+-#MZgG*LkFI_056^SLxn3b_HrDKVZDTNl-0S+csWU=~u-*plrO{47 zL+faGN=JqMu7yZ`iuS$x^R-bFvHQCzpkLv}+X_P;Rv7x6e#BuV-U0%`6I z_~;FI6#PCu3xoKLV|b+vXIpUgHN4r3oI#M7J65OL>aCV~>$N)HR##i<>KOo^Y{W#d z2`$!yf5g+@tTv+^FyD-}!F&tMe-D3rasod47jyhT`|02WY;}}GTbXGoGvDp}s@(2m zt~PI<$djFW51I?d@oO#l`iZ>KxqE+T Date: Tue, 27 May 2025 17:04:12 -0700 Subject: [PATCH 11/13] Bump dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 04b5679..9beb407 100644 --- a/Dockerfile +++ b/Dockerfile @@ -109,7 +109,7 @@ RUN cd "${VCPKG_ROOT}" && \ # ----------------------------------------------------------------------------- # Install Synapse SDK from internal repository (same steps on both) # ----------------------------------------------------------------------------- -ARG SDK_VERSION=0.3.0 +ARG SDK_VERSION=0.4.1 COPY keys/science-repo-public.asc /usr/share/keyrings/scifi-repo-science-public.asc RUN set -eux; \ apt-get update && apt-get install -y --no-install-recommends ca-certificates; \ From 2446fd557c29b063d7578e6585d10a7d2b326164 Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Tue, 27 May 2025 17:43:02 -0700 Subject: [PATCH 12/13] Get the hotfix build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9beb407..2604bd5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -109,7 +109,7 @@ RUN cd "${VCPKG_ROOT}" && \ # ----------------------------------------------------------------------------- # Install Synapse SDK from internal repository (same steps on both) # ----------------------------------------------------------------------------- -ARG SDK_VERSION=0.4.1 +ARG SDK_VERSION=0.4.2 COPY keys/science-repo-public.asc /usr/share/keyrings/scifi-repo-science-public.asc RUN set -eux; \ apt-get update && apt-get install -y --no-install-recommends ca-certificates; \ From b0039ddab9f82aa05ba416bad4575cbcf794c5bd Mon Sep 17 00:00:00 2001 From: Gilbert Montague Date: Tue, 27 May 2025 18:22:53 -0700 Subject: [PATCH 13/13] Added option to enable disable function profile --- Dockerfile | 2 +- config/simulator_32ch.json | 3 ++- proto/example_app.proto | 3 +++ src/fixed_weight_decoder.cpp | 17 ++++++++++------- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2604bd5..991b5d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -109,7 +109,7 @@ RUN cd "${VCPKG_ROOT}" && \ # ----------------------------------------------------------------------------- # Install Synapse SDK from internal repository (same steps on both) # ----------------------------------------------------------------------------- -ARG SDK_VERSION=0.4.2 +ARG SDK_VERSION=0.4.4 COPY keys/science-repo-public.asc /usr/share/keyrings/scifi-repo-science-public.asc RUN set -eux; \ apt-get update && apt-get install -y --no-install-recommends ca-certificates; \ diff --git a/config/simulator_32ch.json b/config/simulator_32ch.json index 72dcc06..8b0e677 100644 --- a/config/simulator_32ch.json +++ b/config/simulator_32ch.json @@ -14,7 +14,8 @@ "refractory_period_us": 1000, "window_size": 5, "max_expected_rate": 10.0, - "cursor_channels": [0, 7, 16, 30] + "cursor_channels": [0, 7, 16, 30], + "enable_function_profiling": false } } }, diff --git a/proto/example_app.proto b/proto/example_app.proto index 93c64b5..8fba323 100644 --- a/proto/example_app.proto +++ b/proto/example_app.proto @@ -23,4 +23,7 @@ message ExampleAppConfig { // Cursor control channels to use, we expect there to be four channels repeated int32 cursor_channels = 8; + + // Should we be enabling the function profiling and readouts + bool enable_function_profiling = 9; } diff --git a/src/fixed_weight_decoder.cpp b/src/fixed_weight_decoder.cpp index 2971960..badd342 100644 --- a/src/fixed_weight_decoder.cpp +++ b/src/fixed_weight_decoder.cpp @@ -35,14 +35,17 @@ bool FixedWeightDecoder::setup() { return false; } - // Enable performance monitoring - function_profiler_manager_.add("full_loop"); - - // Publish loop stats every 1 second - if (!enable_function_profiling(std::chrono::seconds(1))) { - spdlog::error("Failed to enable function profile monitoring"); - return false; + if (configuration_.enable_function_profiling()) { + // Enable performance monitoring + function_profiler_manager_.add("full_loop"); + + // Publish loop stats every 1 second + if (!enable_function_profiling(std::chrono::seconds(1))) { + spdlog::error("Failed to enable function profile monitoring"); + return false; + } } + return true; }