From 3d16faaa185f10faeb64c4ab284c2e1af1c43004 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Tue, 25 Mar 2025 15:49:43 -0400 Subject: [PATCH 01/20] Update onix.h - Mainly adding hub enums --- libONI/include/onix.h | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/libONI/include/onix.h b/libONI/include/onix.h index f89152b..b2e616c 100644 --- a/libONI/include/onix.h +++ b/libONI/include/onix.h @@ -11,12 +11,8 @@ extern "C" { #define ONI_EXPORT #endif -// Device definitions -#define MAXDEVID 99999 - -// NB: "Officially" supported device IDs for the ONIX project occupy -// device IDs < MAXDEVID. IDs above this value are not reserved and can be used -// for custom projects without future conflict. +// NB: Device IDs are 32-bit integers with the following format: +// Reserved(8-bit).Company(8-bit).Device(16-bit) // NB: If you add a device here, make sure to update oni_device_str(). enum { ONIX_NULL = 0, // Placeholder device @@ -48,19 +44,13 @@ enum { ONIX_MAX10ADCCORE = 26, // Max10 internal ADC device ONIX_LOADTEST = 27, // Variable load testing device ONIX_MEMUSAGE = 28, // Acquisition hardware buffer usage reporting device + // Accidentally skipped ONIX_HARPSYNCINPUT = 30, // Harp synchronization data input device ONIX_RHS2116 = 31, // Intan RHS2116 bioamplifier and stimulator ONIX_RHS2116TRIGGER = 32, // Multi Intan RHS2116 stimulation trigger - - // NB: Final reserved device ID. Always on bottom - ONIX_MAXDEVICEID = MAXDEVID, - - // >= MAXDEVID: Not reserved. Free to use for custom projects + ONIX_NRIC1384 = 33 // IMEC NRIC1384 384-channel bioaquisition chip }; -// Hub definitions -#define MAXHUBID 99999 - // Each hub has a "hidden" information device with a fixed device index and // several read only registers #define ONIX_HUB_DEV_IDX 254 // Device index @@ -74,9 +64,8 @@ enum { ONIX_HUB_DELAYNS = 5, // Hub to host transmission delay in nanoseconds }; -// NB: "Officially" supported hub IDs for the ONIX project occupy -// hub IDs < MAXHUBID. IDs above this value are not reserved and can be used -// for custom projects without future conflict. +// NB: Hub IDs are 32-bit integers that uniquely identify the hub. They have the folowing format: +// Reserved(8-bit).Company(8-bit).Hub(16-bit) // NB: If you add a hub here, make sure to update oni_hub_str(). enum { ONIX_HUB_NULL = 0, // Placeholder hub @@ -85,11 +74,13 @@ enum { ONIX_HUB_HSNP = 3, // Open Ephys headstage-neuropix1 ONIX_HUB_HSRHS2116 = 4, // Open Ephys headstage-rhs2116 ONIX_HUB_HS64S = 5, // Open Ephys headstage-64s + ONIX_HUB_HSNP1ET = 6, // Open Ephys headstage-neuropix1e-te + ONIX_HUB_HSNP2EB = 7, // Open Ephys headstage-neuropix2e-beta + ONIX_HUB_HSNP2E = 8, // Open Ephys headstage-neuropix2e + ONIX_HUB_HSNRIC1384 = 9, // Open Ephys headstage-nric1384 + ONIX_HUB_HSNP1EH = 10, // Open Ephys headstage-neuropix1e-hirose + ONIX_HUB_RHYTHM = 11 // Open Ephys Acquisition Board Rhythm wrapper - // NB: Final reserved hub ID. Always on bottom - ONIX_HUB_MAXID = MAXHUBID, - - // >= MAXHUBID: Not reserved. Free to use for custom projects }; // ONIX Specific configuration registers From ac52fdbb2590bf836f2a74e1b1d94fcfb45f3d66 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 28 Mar 2025 17:39:39 -0400 Subject: [PATCH 02/20] Add Neuropixels 2.0e functionality and UI elements - Create common Neuropixels interface to make it easier to generate UI elements - Add support for passthrough device indices --- Source/Devices/DS90UB9x.h | 8 +- Source/Devices/Neuropixels2e.cpp | 1008 +++++++++++++++++++- Source/Devices/Neuropixels2e.h | 138 ++- Source/Devices/Neuropixels_1.cpp | 128 +-- Source/Devices/Neuropixels_1.h | 54 +- Source/Devices/PortController.cpp | 4 + Source/Formats/ProbeInterface.h | 19 +- Source/FrameReader.cpp | 2 +- Source/NeuropixComponents.h | 129 ++- Source/Onix1.h | 1 + Source/OnixDevice.cpp | 16 + Source/OnixDevice.h | 9 +- Source/OnixSource.cpp | 38 +- Source/OnixSource.h | 2 +- Source/OnixSourceCanvas.cpp | 65 +- Source/OnixSourceCanvas.h | 48 +- Source/OnixSourceEditor.cpp | 22 +- Source/UI/CustomTabButton.h | 49 + Source/UI/CustomTabComponent.h | 50 + Source/UI/CustomViewport.h | 5 +- Source/UI/InterfaceList.h | 1 + Source/UI/NeuropixV1Interface.cpp | 241 ++--- Source/UI/NeuropixV1Interface.h | 27 +- Source/UI/NeuropixelsV1fProbeBrowser.h | 43 + Source/UI/NeuropixelsV2eInterface.cpp | 102 ++ Source/UI/NeuropixelsV2eInterface.h | 62 ++ Source/UI/NeuropixelsV2eProbeBrowser.h | 43 + Source/UI/NeuropixelsV2eProbeInterface.cpp | 745 +++++++++++++++ Source/UI/NeuropixelsV2eProbeInterface.h | 138 +++ Source/UI/ProbeBrowser.cpp | 831 ---------------- Source/UI/ProbeBrowser.h | 833 +++++++++++++++- Source/UI/SettingsInterface.h | 6 + 32 files changed, 3520 insertions(+), 1347 deletions(-) create mode 100644 Source/UI/CustomTabButton.h create mode 100644 Source/UI/CustomTabComponent.h create mode 100644 Source/UI/NeuropixelsV1fProbeBrowser.h create mode 100644 Source/UI/NeuropixelsV2eInterface.cpp create mode 100644 Source/UI/NeuropixelsV2eInterface.h create mode 100644 Source/UI/NeuropixelsV2eProbeBrowser.h create mode 100644 Source/UI/NeuropixelsV2eProbeInterface.cpp create mode 100644 Source/UI/NeuropixelsV2eProbeInterface.h delete mode 100644 Source/UI/ProbeBrowser.cpp diff --git a/Source/Devices/DS90UB9x.h b/Source/Devices/DS90UB9x.h index 93463b9..7e44719 100644 --- a/Source/Devices/DS90UB9x.h +++ b/Source/Devices/DS90UB9x.h @@ -27,8 +27,6 @@ static class DS90UB9x { public: - const int ID = 24; - // managed registers static const uint32_t ENABLE = 0x8000; static const uint32_t READSZ = 0x8001; @@ -89,6 +87,12 @@ static class DS90UB9x enum class DS90UB9xDeserializerI2CRegister : uint32_t { PortMode = 0x6D, + PortSel = 0x4C, + I2CConfig = 0x58, + GpioCtrl0 = 0x6E, + GpioCtrl1 = 0x6F, + + SerAlias = 0x5C, SlaveID1 = 0x5E, SlaveID2 = 0x5F, diff --git a/Source/Devices/Neuropixels2e.cpp b/Source/Devices/Neuropixels2e.cpp index 8ccb09d..1408003 100644 --- a/Source/Devices/Neuropixels2e.cpp +++ b/Source/Devices/Neuropixels2e.cpp @@ -21,11 +21,23 @@ */ #include "Neuropixels2e.h" -#include "DS90UB9x.h" -Neuropixels2e::Neuropixels2e(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx_) - : OnixDevice(name, OnixDeviceType::NEUROPIXELS_2, deviceIdx_, ctx_) -{} +Neuropixels2e::Neuropixels2e(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx_) : + OnixDevice(name, OnixDeviceType::NEUROPIXELSV2E, deviceIdx_, ctx_), + I2CRegisterContext(ProbeI2CAddress, deviceIdx_, ctx_), + INeuropixel(NeuropixelsV2eValues::numberOfSettings, NeuropixelsV2eValues::numberOfShanks) +{ + probeSN[0] = 0; + probeSN[1] = 0; + + for (int i = 0; i < NeuropixelsV2eValues::numberOfSettings; i++) + { + defineMetadata(settings[i].get(), NeuropixelsV2eValues::numberOfShanks); + } + + for (int i = 0; i < numFrames; i++) + eventCodes[i] = 0; +} void Neuropixels2e::createDataStream(int n) { @@ -48,24 +60,305 @@ int Neuropixels2e::getNumProbes() const return m_numProbes; } +std::vector Neuropixels2e::selectElectrodeConfiguration(String config) +{ + std::vector selection; + + if (config.equalsIgnoreCase("Bank A") || config.equalsIgnoreCase("Shank 1 Bank A")) + { + for (int i = 0; i < 384; i++) + selection.emplace_back(i); + } + else if (config.equalsIgnoreCase("Bank B") || config.equalsIgnoreCase("Shank 1 Bank B")) + { + for (int i = 384; i < 768; i++) + selection.emplace_back(i); + } + else if (config.equalsIgnoreCase("Bank C") || config.equalsIgnoreCase("Shank 1 Bank C")) + { + for (int i = 768; i < 1152; i++) + selection.emplace_back(i); + } + else if (config.equalsIgnoreCase("Bank D")) + { + for (int i = 896; i < 1280; i++) + selection.emplace_back(i); + } + else if (config.equalsIgnoreCase("Shank 2 Bank A")) + { + int startElectrode = 1280; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 2 Bank B")) + { + int startElectrode = 1280 + 384; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 2 Bank C")) + { + int startElectrode = 1280 + 384 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 3 Bank A")) + { + int startElectrode = 1280 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 3 Bank B")) + { + int startElectrode = 1280 * 2 + 384; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 3 Bank C")) + { + int startElectrode = 1280 * 2 + 384 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 4 Bank A")) + { + int startElectrode = 1280 * 3; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 4 Bank B")) + { + int startElectrode = 1280 * 3 + 384; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 4 Bank C")) + { + int startElectrode = 1280 * 3 + 384 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("All Shanks 1-96")) + { + int startElectrode = 0; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 97-192")) + { + int startElectrode = 96; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 193-288")) + { + int startElectrode = 192; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 289-384")) + { + int startElectrode = 288; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 385-480")) + { + int startElectrode = 384; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 481-576")) + { + int startElectrode = 480; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 577-672")) + { + int startElectrode = 576; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 673-768")) + { + int startElectrode = 672; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 769-864")) + { + int startElectrode = 768; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 865-960")) + { + int startElectrode = 864; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 961-1056")) + { + int startElectrode = 960; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 1057-1152")) + { + int startElectrode = 1056; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 1153-1248")) + { + int startElectrode = 1152; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + + return selection; +} + +uint64_t Neuropixels2e::getProbeSerialNumber(int index) +{ + switch (index) + { + case 0: + return probeSN[0]; + case 1: + return probeSN[1]; + default: + return 0; + } +} + int Neuropixels2e::configureDevice() { if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; configureSerDes(); setProbeSupply(true); - probeSNA = getProbeSN(ProbeASelected); - probeSNB = getProbeSN(ProbeBSelected); + auto sclTimes = (uint32_t)(std::round(1.0 / (100e-9 * 400e3))); // NB: set I2C clock rate to ~400 kHz + serializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xSerializerI2CRegister::SCLHIGH, sclTimes); + serializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xSerializerI2CRegister::SCLLOW, sclTimes); + probeSN[0] = getProbeSN(ProbeASelected); + probeSN[1] = getProbeSN(ProbeBSelected); setProbeSupply(false); - LOGD("Probe A SN: ", probeSNA); - LOGD("Probe B SN: ", probeSNB); + LOGD("Probe A SN: ", probeSN[0]); + LOGD("Probe B SN: ", probeSN[1]); - if (probeSNA == -1 && probeSNB == -1) + if (probeSN[0] == 0 && probeSN[1] == 0) { m_numProbes = 0; return -2; } - else if (probeSNA != -1 && probeSNB != -1) + else if (probeSN[0] != 0 && probeSN[1] != 0) { m_numProbes = 2; } @@ -74,16 +367,91 @@ int Neuropixels2e::configureDevice() m_numProbes = 1; } + streams.clear(); + for (int i = 0; i < m_numProbes; i++) + { createDataStream(i); - - //TODO: Gain correction loading would be here + } return 0; } bool Neuropixels2e::updateSettings() { + for (int i = 0; i < 2; i++) + { + if (probeSN[i] != 0) + { + if (gainCorrectionFilePath[i] == "None" || gainCorrectionFilePath[i] == "") + { + LOGE("Missing gain correction file for probe " + probeSN[i]); + return false; + } + + File gainCorrectionFile = File(gainCorrectionFilePath[i]); + + if (!gainCorrectionFile.existsAsFile()) + { + LOGE("The gain correction file \"", gainCorrectionFilePath[i], "\" for probe ", probeSN[i], " does not exist."); + return false; + } + + StringArray fileLines; + gainCorrectionFile.readLines(fileLines); + + fileLines.removeEmptyStrings(true); + + auto gainSN = std::stoull(fileLines[0].toStdString()); + + if (gainSN != probeSN[i]) + { + LOGE("Invalid serial number found in the calibration file. Should match the probe serial number (", probeSN[i], ")"); + return false; + } + + if (fileLines.size() != numberOfChannels + 1) + { + LOGE("Found the wrong number of lines in the calibration file. Expected ", numberOfChannels + 1, ", found ", fileLines.size()); + return false; + } + + StringRef breakCharacters = ","; + StringRef noQuote = ""; + StringArray firstLine = StringArray::fromTokens(fileLines[1], breakCharacters, noQuote); + auto correctionValue = std::stod(firstLine[1].toStdString()); + + for (int j = 0; j < numberOfChannels; j++) + { + StringArray calibrationValues = StringArray::fromTokens(fileLines[j + 1], breakCharacters, noQuote); + + if (std::stoi(calibrationValues[0].toStdString()) != j || std::stod(calibrationValues[1].toStdString()) != correctionValue) + { + LOGE("Calibration file is incorrectly formatted for probe ", probeSN[i]); + return false; + } + } + + gainCorrection[i] = correctionValue; + } + } + + setProbeSupply(true); + resetProbes(); + + for (int i = 0; i < 2; i++) + { + if (probeSN[i] != 0) + { + selectProbe(i == 0 ? ProbeASelected : ProbeBSelected); + writeConfiguration(settings[i].get()); + configureProbeStreaming(); + } + } + + selectProbe(NoProbeSelected); + //IMPORTANT! BNO polling thread must be started after this + return true; } @@ -116,24 +484,30 @@ void Neuropixels2e::configureSerDes() deviceContext->writeRegister(deviceIdx, DS90UB9x::ENABLE, 1); // configure deserializer trigger mode - deviceContext->writeRegister(deviceIdx,DS90UB9x::TRIGGEROFF, 0); - deviceContext->writeRegister(deviceIdx,DS90UB9x::TRIGGER, (uint32_t)(DS90UB9x::DS90UB9xTriggerMode::Continuous)); - deviceContext->writeRegister(deviceIdx,DS90UB9x::SYNCBITS, 0); - deviceContext->writeRegister(deviceIdx,DS90UB9x::DATAGATE, (uint32_t)(DS90UB9x::DS90UB9xDataGate::Disabled)); - deviceContext->writeRegister(deviceIdx,DS90UB9x::MARK, (uint32_t)(DS90UB9x::DS90UB9xMarkMode::Disabled)); + deviceContext->writeRegister(deviceIdx, DS90UB9x::TRIGGEROFF, 0); + deviceContext->writeRegister(deviceIdx, DS90UB9x::TRIGGER, (uint32_t)(DS90UB9x::DS90UB9xTriggerMode::Continuous)); + deviceContext->writeRegister(deviceIdx, DS90UB9x::SYNCBITS, 0); + deviceContext->writeRegister(deviceIdx, DS90UB9x::DATAGATE, (uint32_t)(DS90UB9x::DS90UB9xDataGate::Disabled)); + deviceContext->writeRegister(deviceIdx, DS90UB9x::MARK, (uint32_t)(DS90UB9x::DS90UB9xMarkMode::Disabled)); // configure two 4-bit magic word-triggered streams, one for each probe - deviceContext->writeRegister(deviceIdx,DS90UB9x::READSZ, 0x00100009); // 16 frames/superframe, 8x 12-bit words + magic bits - deviceContext->writeRegister(deviceIdx,DS90UB9x::MAGIC_MASK, 0xC000003F); // Enable inverse, wait for non-inverse, 14-bit magic word - deviceContext->writeRegister(deviceIdx,DS90UB9x::MAGIC, 0b0000000000101110); // Super-frame sync word - deviceContext->writeRegister(deviceIdx,DS90UB9x::MAGIC_WAIT, 0); - deviceContext->writeRegister(deviceIdx,DS90UB9x::DATAMODE, 0b00100000000000000000001010110101); - deviceContext->writeRegister(deviceIdx,DS90UB9x::DATALINES0, 0xFFFFF8A6); // NP A - deviceContext->writeRegister(deviceIdx,DS90UB9x::DATALINES1, 0xFFFFF97B); // NP B + deviceContext->writeRegister(deviceIdx, DS90UB9x::READSZ, 0x00100009); // 16 frames/superframe, 8x 12-bit words + magic bits + deviceContext->writeRegister(deviceIdx, DS90UB9x::MAGIC_MASK, 0xC000003F); // Enable inverse, wait for non-inverse, 14-bit magic word + deviceContext->writeRegister(deviceIdx, DS90UB9x::MAGIC, 0b0000000000101110); // Super-frame sync word + deviceContext->writeRegister(deviceIdx, DS90UB9x::MAGIC_WAIT, 0); + deviceContext->writeRegister(deviceIdx, DS90UB9x::DATAMODE, 0b00100000000000000000001010110101); + deviceContext->writeRegister(deviceIdx, DS90UB9x::DATALINES0, 0xFFFFF8A6); // NP A + deviceContext->writeRegister(deviceIdx, DS90UB9x::DATALINES1, 0xFFFFF97B); // NP B deserializer = std::make_unique(DS90UB9x::DES_ADDR, deviceIdx, deviceContext); + deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::PortSel, 0x01); int coaxMode = 0x4 + (uint32_t)(DS90UB9x::DS90UB9xMode::Raw12BitHighFrequency); // 0x4 maintains coax mode deserializer->WriteByte((uint32_t)(DS90UB9x::DS90UB9xDeserializerI2CRegister::PortMode), coaxMode); + deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::I2CConfig, 0b01011000); + deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::SerAlias, DS90UB9x::SER_ADDR << 1); + + deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::GpioCtrl0, 0x10); + deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::GpioCtrl1, 0x32); int alias = ProbeI2CAddress << 1; deserializer->WriteByte((uint32_t)(DS90UB9x::DS90UB9xDeserializerI2CRegister::SlaveID1), alias); @@ -145,7 +519,7 @@ void Neuropixels2e::configureSerDes() serializer = std::make_unique(DS90UB9x::SER_ADDR, deviceIdx, deviceContext); flex = std::make_unique(FlexAddress, deviceIdx, deviceContext); - probeControl = std::make_unique(ProbeI2CAddress, deviceIdx, deviceContext); //TODO: this probably be a derived class that includes all the connfiguration methods + probeControl = std::make_unique(ProbeI2CAddress, deviceIdx, deviceContext); } void Neuropixels2e::setProbeSupply(bool en) @@ -154,7 +528,7 @@ void Neuropixels2e::setProbeSupply(bool en) selectProbe(NoProbeSelected); serializer->WriteByte((uint32_t)(DS90UB9x::DS90UB9xSerializerI2CRegister::GPIO10), gpo10Config); - Thread::sleep(10); + Thread::sleep(20); } void Neuropixels2e::selectProbe(uint8_t probeSelect) @@ -184,7 +558,7 @@ uint64_t Neuropixels2e::getProbeSN(uint8_t probeSelect) flex->ReadByte(reg_addr, &val); - if (flex->getLastResult() != ONI_ESUCCESS) return -1; + if (flex->getLastResult() != ONI_ESUCCESS) return 0; if (val <= 0xFF) { @@ -196,23 +570,11 @@ uint64_t Neuropixels2e::getProbeSN(uint8_t probeSelect) void Neuropixels2e::startAcquisition() { - setProbeSupply(true); - resetProbes(); - if (probeSNA != -1) - { - selectProbe(ProbeASelected); - //TODO: Here would be proper probe configuration theough a proper probe control class - configureProbeStreaming(); - } - if (probeSNB != -1) - { - selectProbe(ProbeBSelected); - //TODO: Here would be proper probe configuration theough a proper probe control class - configureProbeStreaming(); - } + shouldAddToBuffer = false; + sampleNumber = 0; + frameCount = 0; - // disconnect i2c bus from both probes to prevent digital interference during acquisition - selectProbe(NoProbeSelected); //IMPORTANT! BNO polling thread must be started after this + singleProbe = m_numProbes == 1; } void Neuropixels2e::stopAcquisition() @@ -238,7 +600,7 @@ void Neuropixels2e::addSourceBuffers(OwnedArray& sourceBuffers) for (const auto& streamInfo : streamInfos) { sourceBuffers.add(new DataBuffer(streamInfo.getNumChannels(), (int)streamInfo.getSampleRate() * bufferSizeInSeconds)); - apBuffer[bufferIdx++] = sourceBuffers.getLast(); + amplifierBuffer[bufferIdx++] = sourceBuffers.getLast(); } } @@ -249,21 +611,563 @@ void Neuropixels2e::processFrames() const GenericScopedLock frameLock(frameArray.getLock()); oni_frame_t* frame = frameArray.removeAndReturn(0); - uint16_t* dataPtr; - dataPtr = (uint16_t*)frame->data; + uint16_t* dataPtr = (uint16_t*)frame->data; + uint16_t probeIndex = *(dataPtr + 4); - uint16_t* amplifierData = dataPtr + 6; - uint64_t timestamp = frame->time; + uint16_t* amplifierData = dataPtr + 9; + + sampleNumbers[frameCount] = sampleNumber; + timestamps[frameCount] = frame->time; - if (m_numProbes == 1) + for (int i = 0; i < FramesPerSuperFrame; i++) { - //Fill always in buffer[0], we can ignore the probeIndex + auto adcDataOffset = i * FrameWords; + + for (int j = 0; j < AdcsPerProbe; j++) + { + samples[rawToChannel[j][i] * numFrames + frameCount] = + (float)(*(amplifierData + adcIndices[j] + adcDataOffset) * gainCorrection[probeIndex]); + } + } + + frameCount++; + sampleNumber++; + + if (frameCount >= numFrames) + { + frameCount = 0; + shouldAddToBuffer = true; } - else + + if (shouldAddToBuffer) { - //Fill buffer[probeindex] + shouldAddToBuffer = false; + + amplifierBuffer[probeIndex]->addToBuffer(samples.data(), sampleNumbers, timestamps, eventCodes, numFrames); } oni_destroy_frame(frame); } } + +void Neuropixels2e::writeConfiguration(ProbeSettings* settings) +{ + auto baseBits = makeBaseBits(getReference(settings->referenceIndex)); + writeShiftRegister(SR_CHAIN5, baseBits[0]); + writeShiftRegister(SR_CHAIN6, baseBits[1]); + + auto shankBits = makeShankBits(getReference(settings->referenceIndex), settings->electrodeMetadata); + writeShiftRegister(SR_CHAIN1, shankBits[0]); + writeShiftRegister(SR_CHAIN2, shankBits[1]); + writeShiftRegister(SR_CHAIN3, shankBits[2]); + writeShiftRegister(SR_CHAIN4, shankBits[3]); +} + +template +void Neuropixels2e::writeShiftRegister(uint32_t srAddress, std::bitset bits) +{ + std::vector bytes = toBitReversedBytes(bits); + + for (int i = 2; i > 0; i -= 1) + { + WriteByte(SOFT_RESET, 0xFF); + WriteByte(SOFT_RESET, 0x00); + + WriteByte(SR_LENGTH1, (uint32_t)(bytes.size() % 0x100)); + WriteByte(SR_LENGTH2, (uint32_t)(bytes.size() / 0x100)); + + for (auto b : bytes) + { + WriteByte(srAddress, b); + } + } + + uint32_t status; + ReadByte(STATUS, &status); + if (status != (uint32_t)NeuropixelsV2Status::SR_OK) + { + LOGE("Warning: Shift register ", srAddress, " status check failed. ", getShankName(srAddress), " may be damaged."); + } +} + +String Neuropixels2e::getShankName(uint32_t shiftRegisterAddress) +{ + switch (shiftRegisterAddress) + { + case SR_CHAIN1: + return "Shank 1"; + case SR_CHAIN2: + return "Shank 2"; + case SR_CHAIN3: + return "Shank 3"; + case SR_CHAIN4: + return "Shank 4"; + default: + return ""; + } +} + +void Neuropixels2e::setGainCorrectionFile(int index, String filename) +{ + if (index < gainCorrectionFilePath.size()) + { + gainCorrectionFilePath[index] = filename; + } +} + +String Neuropixels2e::getGainCorrectionFile(int index) +{ + if (index < gainCorrectionFilePath.size()) + { + return gainCorrectionFilePath[index]; + } + else + return ""; +} + +NeuropixelsV2Reference Neuropixels2e::getReference(int index) +{ + switch (index) + { + case 0: + return NeuropixelsV2Reference::External; + case 1: + return NeuropixelsV2Reference::Tip1; + case 2: + return NeuropixelsV2Reference::Tip2; + case 3: + return NeuropixelsV2Reference::Tip3; + case 4: + return NeuropixelsV2Reference::Tip4; + default: + break; + } + + return NeuropixelsV2Reference::External; +} + +Neuropixels2e::BaseBitsArray Neuropixels2e::makeBaseBits(NeuropixelsV2Reference reference) +{ + BaseBitsArray baseBits; + + int referenceBit; + + if (reference == NeuropixelsV2Reference::External) + referenceBit = 1; + else + referenceBit = 2; + + for (int i = 0; i < numberOfChannels; i++) + { + auto configIndex = i % 2; + auto bitOffset = (382 - i + configIndex) / 2 * baseBitsPerChannel; + baseBits[configIndex][bitOffset + 0] = false; + baseBits[configIndex][bitOffset + referenceBit] = true; + } + + return baseBits; +} + +Neuropixels2e::ShankBitsArray Neuropixels2e::makeShankBits(NeuropixelsV2Reference reference, std::array channelMap) +{ + ShankBitsArray shankBits; + + if (reference != NeuropixelsV2Reference::External) + { + shankBits[(int)reference - 1][643] = true; + shankBits[(int)reference - 1][644] = true; + } + else + { + shankBits[0][2] = true; + shankBits[0][1285] = true; + shankBits[1][2] = true; + shankBits[1][1285] = true; + shankBits[2][2] = true; + shankBits[2][1285] = true; + shankBits[3][2] = true; + shankBits[3][1285] = true; + } + + const int pixelOffset = (NeuropixelsV2eValues::electrodesPerShank - 1) / 2; + const int referencePixelOffset = 3; + + int count = 0; + + for (const auto& e : channelMap) + { + if (e.status == ElectrodeStatus::CONNECTED) + { + auto baseIndex = e.shank_local_index % 2; + auto pixelIndex = e.shank_local_index / 2; + pixelIndex = baseIndex == 0 + ? pixelIndex + pixelOffset + 2 * referencePixelOffset + : pixelOffset - pixelIndex + referencePixelOffset; + + shankBits[e.shank][pixelIndex] = true; + + count++; + } + } + + if (count != numberOfChannels) + { + LOGE("Invalid number of channels connected for Neuropixels 2.0e, configuration might be invalid."); + } + + return shankBits; +} + +void Neuropixels2e::setSettings(ProbeSettings* settings_, int index) +{ + if (index >= settings.size()) + { + LOGE("Invalid index given when trying to update settings."); + return; + } + + settings[index]->updateProbeSettings(settings_); +} + +void Neuropixels2e::defineMetadata(ProbeSettings* settings, int shankCount) +{ + settings->probeType = ProbeType::NPX_V2E; + settings->probeMetadata.name = "Neuropixels 2.0e" + String(shankCount == 1 ? " - Single Shank" : " - Quad Shank"); + + Path path; + path.startNewSubPath(27, 31); + path.lineTo(27, 514); + path.lineTo(27 + 5, 522); + path.lineTo(27 + 10, 514); + path.lineTo(27 + 10, 31); + path.closeSubPath(); + + settings->probeMetadata.shank_count = shankCount; + settings->probeMetadata.electrodes_per_shank = NeuropixelsV2eValues::electrodesPerShank; + settings->probeMetadata.rows_per_shank = NeuropixelsV2eValues::electrodesPerShank / 2; + settings->probeMetadata.columns_per_shank = 2; + settings->probeMetadata.shankOutline = path; + settings->probeMetadata.num_adcs = 24; + settings->probeMetadata.adc_bits = 12; + + settings->availableBanks = { + Bank::A, + Bank::B, + Bank::C, + Bank::D, + Bank::NONE //disconnected + }; + + for (int i = 0; i < settings->probeMetadata.electrodes_per_shank * settings->probeMetadata.shank_count; i++) + { + ElectrodeMetadata metadata; + + metadata.global_index = i; + + metadata.shank = i / settings->probeMetadata.electrodes_per_shank; + metadata.shank_local_index = i % settings->probeMetadata.electrodes_per_shank; + + metadata.xpos = i % 2 * 32.0f + 8.0f; + metadata.ypos = (metadata.shank_local_index - (metadata.shank_local_index % 2)) * 7.5f; + metadata.site_width = 12; + + metadata.column_index = i % 2; + metadata.row_index = metadata.shank_local_index / 2; + + metadata.isSelected = false; + + metadata.colour = Colours::lightgrey; + + if (shankCount == 1) + { + if (i < 384) + { + metadata.bank = Bank::A; + + int bank_index = metadata.shank_local_index % 384; + int block = bank_index / 32; + int row = (bank_index % 32) / 2; + + if (i % 2 == 0) + { + metadata.channel = row * 2 + block * 32; + } + else + { + metadata.channel = row * 2 + block * 32 + 1; + } + + metadata.status = ElectrodeStatus::CONNECTED; + } + else if (i >= 384 && i < 768) + { + metadata.bank = Bank::B; + + int bank_index = metadata.shank_local_index % 384; + int block = bank_index / 32; + int row = (bank_index % 32) / 2; + + if (i % 2 == 0) + { + metadata.channel = ((row * 7) % 16) * 2 + block * 32; + } + else + { + metadata.channel = ((row * 7 + 4) % 16) * 2 + block * 32 + 1; + } + + metadata.status = ElectrodeStatus::DISCONNECTED; + } + else if (i >= 768 && i < 1152) + { + metadata.bank = Bank::C; + + int bank_index = metadata.shank_local_index % 384; + int block = bank_index / 32; + int row = (bank_index % 32) / 2; + + if (i % 2 == 0) + { + metadata.channel = ((row * 5) % 16) * 2 + block * 32; + } + else + { + metadata.channel = ((row * 5 + 8) % 16) * 2 + block * 32 + 1; + } + + metadata.status = ElectrodeStatus::DISCONNECTED; + } + else + { + metadata.bank = Bank::D; + + int bank_index = metadata.shank_local_index % 384; + int block = bank_index / 32; + int row = (bank_index % 32) / 2; + + if (i % 2 == 0) + { + metadata.channel = ((row * 3) % 16) * 2 + block * 32; + } + else + { + metadata.channel = ((row * 3 + 12) % 16) * 2 + block * 32 + 1; + } + + metadata.status = ElectrodeStatus::DISCONNECTED; + } + } + else if (shankCount == 4) + { + if (i < 384) + { + metadata.status = ElectrodeStatus::CONNECTED; + } + else + { + metadata.status = ElectrodeStatus::DISCONNECTED; + } + + if (metadata.shank_local_index < 384) + metadata.bank = Bank::A; + else if (metadata.shank_local_index >= 384 && metadata.shank_local_index < 768) + metadata.bank = Bank::B; + else if (metadata.shank_local_index >= 768 && metadata.shank_local_index < 1152) + metadata.bank = Bank::C; + else + metadata.bank = Bank::D; + + int block = metadata.shank_local_index % 384 / 48 + 1; + int block_index = metadata.shank_local_index % 48; + + if (metadata.shank == 0) + { + switch (block) + { + case 1: + metadata.channel = block_index + 48 * 0; // 1-48 (Bank 0-3) + break; + case 2: + metadata.channel = block_index + 48 * 2; // 96-144 (Bank 0-3) + break; + case 3: + metadata.channel = block_index + 48 * 4; // 192-223 (Bank 0-3) + break; + case 4: + metadata.channel = block_index + 48 * 6; // 288-336 (Bank 0-2) + break; + case 5: + metadata.channel = block_index + 48 * 5; // 240-288 (Bank 0-2) + break; + case 6: + metadata.channel = block_index + 48 * 7; // 336-384 (Bank 0-2) + break; + case 7: + metadata.channel = block_index + 48 * 1; // 48-96 (Bank 0-2) + break; + case 8: + metadata.channel = block_index + 48 * 3; // 144-192 (Bank 0-2) + break; + default: + metadata.channel = -1; + } + } + else if (metadata.shank == 1) + { + switch (block) + { + case 1: + metadata.channel = block_index + 48 * 1; + break; + case 2: + metadata.channel = block_index + 48 * 3; + break; + case 3: + metadata.channel = block_index + 48 * 5; + break; + case 4: + metadata.channel = block_index + 48 * 7; + break; + case 5: + metadata.channel = block_index + 48 * 4; + break; + case 6: + metadata.channel = block_index + 48 * 6; + break; + case 7: + metadata.channel = block_index + 48 * 0; + break; + case 8: + metadata.channel = block_index + 48 * 2; + break; + default: + metadata.channel = -1; + } + } + else if (metadata.shank == 2) + { + switch (block) + { + case 1: + metadata.channel = block_index + 48 * 4; + break; + case 2: + metadata.channel = block_index + 48 * 6; + break; + case 3: + metadata.channel = block_index + 48 * 0; + break; + case 4: + metadata.channel = block_index + 48 * 2; + break; + case 5: + metadata.channel = block_index + 48 * 1; + break; + case 6: + metadata.channel = block_index + 48 * 3; + break; + case 7: + metadata.channel = block_index + 48 * 5; + break; + case 8: + metadata.channel = block_index + 48 * 7; + break; + default: + metadata.channel = -1; + } + } + else if (metadata.shank == 3) + { + switch (block) + { + case 1: + metadata.channel = block_index + 48 * 5; + break; + case 2: + metadata.channel = block_index + 48 * 7; + break; + case 3: + metadata.channel = block_index + 48 * 1; + break; + case 4: + metadata.channel = block_index + 48 * 3; + break; + case 5: + metadata.channel = block_index + 48 * 0; + break; + case 6: + metadata.channel = block_index + 48 * 2; + break; + case 7: + metadata.channel = block_index + 48 * 4; + break; + case 8: + metadata.channel = block_index + 48 * 6; + break; + default: + metadata.channel = -1; + } + } + + metadata.type = ElectrodeType::ELECTRODE; + } + + settings->electrodeMetadata[i] = metadata; + } + + settings->apGainIndex = -1; + settings->lfpGainIndex = -1; + settings->referenceIndex = 0; + settings->apFilterState = false; + + auto selection = selectElectrodeConfiguration("Bank A"); + settings->selectElectrodes(selection); + + settings->availableReferences.add("Ext"); + settings->availableReferences.add("Tip1"); + + if (shankCount == 4) + { + settings->availableReferences.add("Tip2"); + settings->availableReferences.add("Tip3"); + settings->availableReferences.add("Tip4"); + } + + if (shankCount == 1) + { + settings->availableElectrodeConfigurations.add("Bank A"); + settings->availableElectrodeConfigurations.add("Bank B"); + settings->availableElectrodeConfigurations.add("Bank C"); + settings->availableElectrodeConfigurations.add("Bank D"); + } + else if (shankCount == 4) + { + settings->availableElectrodeConfigurations.add("Shank 1 Bank A"); + settings->availableElectrodeConfigurations.add("Shank 1 Bank B"); + settings->availableElectrodeConfigurations.add("Shank 1 Bank C"); + settings->availableElectrodeConfigurations.add("Shank 2 Bank A"); + settings->availableElectrodeConfigurations.add("Shank 2 Bank B"); + settings->availableElectrodeConfigurations.add("Shank 2 Bank C"); + settings->availableElectrodeConfigurations.add("Shank 3 Bank A"); + settings->availableElectrodeConfigurations.add("Shank 3 Bank B"); + settings->availableElectrodeConfigurations.add("Shank 3 Bank C"); + settings->availableElectrodeConfigurations.add("Shank 4 Bank A"); + settings->availableElectrodeConfigurations.add("Shank 4 Bank B"); + settings->availableElectrodeConfigurations.add("Shank 4 Bank C"); + settings->availableElectrodeConfigurations.add("All Shanks 1-96"); + settings->availableElectrodeConfigurations.add("All Shanks 97-192"); + settings->availableElectrodeConfigurations.add("All Shanks 193-288"); + settings->availableElectrodeConfigurations.add("All Shanks 289-384"); + settings->availableElectrodeConfigurations.add("All Shanks 385-480"); + settings->availableElectrodeConfigurations.add("All Shanks 481-576"); + settings->availableElectrodeConfigurations.add("All Shanks 577-672"); + settings->availableElectrodeConfigurations.add("All Shanks 673-768"); + settings->availableElectrodeConfigurations.add("All Shanks 769-864"); + settings->availableElectrodeConfigurations.add("All Shanks 865-960"); + settings->availableElectrodeConfigurations.add("All Shanks 961-1056"); + settings->availableElectrodeConfigurations.add("All Shanks 1057-1152"); + settings->availableElectrodeConfigurations.add("All Shanks 1153-1248"); + } + + settings->electrodeConfigurationIndex = 0; +} diff --git a/Source/Devices/Neuropixels2e.h b/Source/Devices/Neuropixels2e.h index 2c7f692..22d99ef 100644 --- a/Source/Devices/Neuropixels2e.h +++ b/Source/Devices/Neuropixels2e.h @@ -24,15 +24,45 @@ #include "../OnixDevice.h" #include "../I2CRegisterContext.h" +#include "../NeuropixComponents.h" +#include "DS90UB9x.h" + +enum class NeuropixelsV2Reference : uint32_t +{ + External, + Tip1, + Tip2, + Tip3, + Tip4 +}; + +enum class NeuropixelsV2Status : uint32_t +{ + SR_OK = 1 << 7 +}; /* Configures and streams data from a Neuropixels 2.0e device */ -class Neuropixels2e : public OnixDevice +class Neuropixels2e : public INeuropixel, + public OnixDevice, + public I2CRegisterContext { public: Neuropixels2e(String name, const oni_dev_idx_t, std::shared_ptr); + ~Neuropixels2e() + { + if (serializer != nullptr) + serializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xSerializerI2CRegister::GPIO10, DefaultGPO10Config); + + if (i2cContext != nullptr) + selectProbe(NoProbeSelected); + + if (deviceContext != nullptr && deviceContext->isInitialized()) + deviceContext->setOption(ONIX_OPT_PASSTHROUGH, 0); + } + int configureDevice() override; /** Update the settings of the device */ @@ -52,8 +82,42 @@ class Neuropixels2e : public OnixDevice int getNumProbes() const; + static const int baseBitsPerChannel = 4; + static const int configurationBitCount = NeuropixelsV2eValues::numberOfChannels * baseBitsPerChannel / 2; + + static const int referencePixelCount = 4; + static const int dummyPixelCount = 4; + static const int registersPerShank = NeuropixelsV2eValues::electrodesPerShank + referencePixelCount + dummyPixelCount; + + using BaseBitsArray = std::array, 2>; + using ShankBitsArray = std::array, 4>; + + BaseBitsArray static makeBaseBits(NeuropixelsV2Reference reference); + ShankBitsArray static makeShankBits(NeuropixelsV2Reference reference, std::array channelMap); + + template + void writeShiftRegister(uint32_t srAddress, std::bitset bits); + + void setGainCorrectionFile(int index, String filename); + + String getGainCorrectionFile(int index); + + // INeuropixel Methods + + std::vector selectElectrodeConfiguration(String config) override; + + uint64_t getProbeSerialNumber(int index) override; + + void defineMetadata(ProbeSettings*, int); + + void setSettings(ProbeSettings* settings_, int index) override; + private: - DataBuffer* apBuffer[2]; + DataBuffer* amplifierBuffer[2]; + + std::array probeSN; + std::array gainCorrection; + std::array gainCorrectionFilePath; void createDataStream(int n); @@ -64,13 +128,31 @@ class Neuropixels2e : public OnixDevice void selectProbe(uint8_t probeSelect); void configureProbeStreaming(); + void writeConfiguration(ProbeSettings*); + + NeuropixelsV2Reference getReference(int); + static String getShankName(uint32_t shiftRegisterAddress); int m_numProbes = 0; + static const int numFrames = 10; + + std::array samples; + + int64 sampleNumbers[numFrames]; + double timestamps[numFrames]; + uint64 eventCodes[numFrames]; + + bool shouldAddToBuffer = false; + int frameCount = 0; + int sampleNumber = 0; + + bool singleProbe = false; + std::unique_ptr serializer; std::unique_ptr deserializer; std::unique_ptr flex; - std::unique_ptr probeControl; //TODO: this probably be a derived class that includes all the connfiguration methods + std::unique_ptr probeControl; static const int ProbeI2CAddress = 0x10; static const int FlexAddress = 0x50; @@ -130,10 +212,54 @@ class Neuropixels2e : public OnixDevice const uint32_t OFFSET_FLEX_PN = 0x20; const uint32_t OFFSET_PROBE_PN = 0x40; - uint64_t probeSNA; - uint64_t probeSNB; - Array frameArray; + static inline const std::array adcIndices = { + 0, 1, 2, + 4, 5, 6, + 8, 9, 10, + 12, 13, 14, + 16, 17, 18, + 20, 21, 22, + 24, 25, 26, + 28, 29, 30 + }; + + static inline const std::array, AdcsPerProbe> rawToChannel = { { + { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }, // Data Index 9, ADC 0 + { 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158 }, // Data Index 10, ADC 8 + { 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 }, // Data Index 11, ADC 16 + + { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 }, // Data Index 13, ADC 1 + { 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159 }, // Data Index 14, ADC 9 + { 257, 259, 261, 263, 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 }, // Data Index 15, ADC 17 + + { 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62 }, // Data Index 17, ADC 2 + { 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 }, // Data Index 18, ADC 10 + { 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318 }, // Data Index 19, ADC 18 + + { 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63 }, // Data Index 21, ADC 3 + { 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 }, // Data Index 22, ADC 11 + { 289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311, 313, 315, 317, 319 }, // Data Index 23, ADC 19 + + { 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 }, // Data Index 25, ADC 4 + { 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222 }, // Data Index 26, ADC 12 + { 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350 }, // Data Index 27, ADC 20 + + { 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 }, // Data Index 29, ADC 5 + { 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223 }, // Data Index 30, ADC 13 + { 321, 323, 325, 327, 329, 331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351 }, // Data Index 31, ADC 21 + + { 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126 }, // Data Index 33, ADC 6 + { 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254 }, // Data Index 34, ADC 14 + { 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 }, // Data Index 35, ADC 22 + + { 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127 }, // Data Index 37, ADC 7 + { 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 }, // Data Index 38, ADC 15 + { 353, 355, 357, 359, 361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 }, // Data Index 39, ADC 23 + } }; + JUCE_LEAK_DETECTOR(Neuropixels2e); }; + +#endif // !__NEUROPIXELS2E_H__ diff --git a/Source/Devices/Neuropixels_1.cpp b/Source/Devices/Neuropixels_1.cpp index 3854746..68a3a80 100644 --- a/Source/Devices/Neuropixels_1.cpp +++ b/Source/Devices/Neuropixels_1.cpp @@ -94,13 +94,13 @@ void BackgroundUpdaterWithProgressWindow::run() LOGD("Gain calibration file SN = ", gainSN); - if (gainSN != device->getProbeNumber()) + if (gainSN != device->getProbeSerialNumber()) { result = false; - LOGE("Gain calibration serial number (", gainSN, ") does not match probe serial number (", device->getProbeNumber(), ")."); + LOGE("Gain calibration serial number (", gainSN, ") does not match probe serial number (", device->getProbeSerialNumber(), ")."); - CoreServices::sendStatusMessage("Serial Number Mismatch: Gain calibration (" + String(gainSN) + ") does not match " + String(device->getProbeNumber())); + CoreServices::sendStatusMessage("Serial Number Mismatch: Gain calibration (" + String(gainSN) + ") does not match " + String(device->getProbeSerialNumber())); return; } @@ -111,8 +111,8 @@ void BackgroundUpdaterWithProgressWindow::run() StringArray calibrationValues = StringArray::fromTokens(gainCalLine, breakCharacters, noQuote); - double apGainCorrection = std::stod(calibrationValues[device->settings->apGainIndex + 1].toStdString()); - double lfpGainCorrection = std::stod(calibrationValues[device->settings->lfpGainIndex + 8].toStdString()); + double apGainCorrection = std::stod(calibrationValues[device->settings[0]->apGainIndex + 1].toStdString()); + double lfpGainCorrection = std::stod(calibrationValues[device->settings[0]->lfpGainIndex + 8].toStdString()); LOGD("AP gain correction = ", apGainCorrection, ", LFP gain correction = ", lfpGainCorrection); @@ -125,13 +125,13 @@ void BackgroundUpdaterWithProgressWindow::run() LOGD("ADC calibration file SN = ", adcSN); - if (adcSN != device->getProbeNumber()) + if (adcSN != device->getProbeSerialNumber()) { result = false; - LOGE("ADC calibration serial number (", adcSN, ") does not match probe serial number (", device->getProbeNumber(), ")."); + LOGE("ADC calibration serial number (", adcSN, ") does not match probe serial number (", device->getProbeSerialNumber(), ")."); - CoreServices::sendStatusMessage("Serial Number Mismatch: ADC calibration (" + String(adcSN) + ") does not match " + String(device->getProbeNumber())); + CoreServices::sendStatusMessage("Serial Number Mismatch: ADC calibration (" + String(adcSN) + ") does not match " + String(device->getProbeSerialNumber())); return; } @@ -158,8 +158,8 @@ void BackgroundUpdaterWithProgressWindow::run() setProgress(0.8); // Write shift registers - auto shankBits = device->makeShankBits(device->getReference(device->settings->referenceIndex), device->settings->selectedChannel); - auto configBits = device->makeConfigBits(device->getReference(device->settings->referenceIndex), device->getGainEnum(device->settings->apGainIndex), device->getGainEnum(device->settings->lfpGainIndex), true, adcs); + auto shankBits = device->makeShankBits(device->getReference(device->settings[0]->referenceIndex), device->settings[0]->selectedElectrode); + auto configBits = device->makeConfigBits(device->getReference(device->settings[0]->referenceIndex), device->getGainEnum(device->settings[0]->apGainIndex), device->getGainEnum(device->settings[0]->lfpGainIndex), true, adcs); device->writeShiftRegisters(shankBits, configBits, adcs, lfpGainCorrection, apGainCorrection); @@ -170,7 +170,8 @@ void BackgroundUpdaterWithProgressWindow::run() Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx_) : OnixDevice("Neuropixels 1.0 " + name, OnixDeviceType::NEUROPIXELS_1, deviceIdx_, ctx_), - I2CRegisterContext(ProbeI2CAddress, deviceIdx_, ctx_) + I2CRegisterContext(ProbeI2CAddress, deviceIdx_, ctx_), + INeuropixel(NeuropixelsV1fValues::numberOfSettings, NeuropixelsV1fValues::numberOfShanks) { StreamInfo apStream = StreamInfo( name + "-AP", @@ -198,11 +199,16 @@ Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::s {}); streamInfos.add(lfpStream); - settings = std::make_unique(); - defineMetadata(settings.get()); + defineMetadata(settings[0].get()); adcCalibrationFilePath = "None"; gainCalibrationFilePath = "None"; + + for (int i = 0; i < numUltraFrames; i++) + { + apEventCodes[i] = 0; + lfpEventCodes[i] = 0; + } } NeuropixelsGain Neuropixels_1::getGainEnum(int index) @@ -259,19 +265,19 @@ int Neuropixels_1::getGainValue(NeuropixelsGain gain) } } -NeuropixelsReference Neuropixels_1::getReference(int index) +NeuropixelsV1Reference Neuropixels_1::getReference(int index) { switch (index) { case 0: - return NeuropixelsReference::External; + return NeuropixelsV1Reference::External; case 1: - return NeuropixelsReference::Tip; + return NeuropixelsV1Reference::Tip; default: break; } - return NeuropixelsReference::External; + return NeuropixelsV1Reference::External; } int Neuropixels_1::configureDevice() @@ -325,60 +331,68 @@ bool Neuropixels_1::updateSettings() return updater.updateSettings(); } -void Neuropixels_1::setSettings(ProbeSettings* settings_) const +void Neuropixels_1::setSettings(ProbeSettings* settings_, int index) { - settings->updateProbeSettings(settings_); + if (index >= settings.size()) + { + LOGE("Invalid index given when trying to update settings."); + return; + } + + settings[index]->updateProbeSettings(settings_); } -Array Neuropixels_1::selectElectrodeConfiguration(String config) +std::vector Neuropixels_1::selectElectrodeConfiguration(String config) { - Array selection; + std::vector selection; if (config.equalsIgnoreCase("Bank A")) { for (int i = 0; i < 384; i++) - selection.add(i); + selection.emplace_back(i); } else if (config.equalsIgnoreCase("Bank B")) { for (int i = 384; i < 768; i++) - selection.add(i); + selection.emplace_back(i); } else if (config.equalsIgnoreCase("Bank C")) { for (int i = 576; i < 960; i++) - selection.add(i); + selection.emplace_back(i); } else if (config.equalsIgnoreCase("Single Column")) { for (int i = 0; i < 384; i += 2) - selection.add(i); + selection.emplace_back(i); for (int i = 385; i < 768; i += 2) - selection.add(i); + selection.emplace_back(i); } else if (config.equalsIgnoreCase("Tetrodes")) { for (int i = 0; i < 384; i += 8) { for (int j = 0; j < 4; j++) - selection.add(i + j); + selection.emplace_back(i + j); } for (int i = 388; i < 768; i += 8) { for (int j = 0; j < 4; j++) - selection.add(i + j); + selection.emplace_back(i + j); } } + assert(selection.size() == numberOfChannels && "Invalid number of selected channels."); + return selection; } void Neuropixels_1::startAcquisition() { - apGain = getGainValue(getGainEnum(settings->apGainIndex)); - lfpGain = getGainValue(getGainEnum(settings->lfpGainIndex)); + apGain = getGainValue(getGainEnum(settings[0]->apGainIndex)); + lfpGain = getGainValue(getGainEnum(settings[0]->lfpGainIndex)); apOffsetValues.clear(); apOffsetValues.reserve(numberOfChannels); @@ -573,7 +587,7 @@ void Neuropixels_1::updateLfpOffsets(std::array& samples, } } -std::bitset Neuropixels_1::makeShankBits(NeuropixelsReference reference, Array channelMap) +Neuropixels_1::ShankBitset Neuropixels_1::makeShankBits(NeuropixelsV1Reference reference, std::array channelMap) { const int shankBitExt1 = 965; const int shankBitExt2 = 2; @@ -596,11 +610,11 @@ std::bitset Neuropixels_1::makeShankB switch (reference) { - case NeuropixelsReference::External: + case NeuropixelsV1Reference::External: shankBits[shankBitExt1] = true; shankBits[shankBitExt2] = true; break; - case NeuropixelsReference::Tip: + case NeuropixelsV1Reference::Tip: shankBits[shankBitTip1] = true; shankBits[shankBitTip2] = true; break; @@ -608,14 +622,19 @@ std::bitset Neuropixels_1::makeShankB break; } + if (channelMap.size() != numberOfChannels) + { + LOGE("Invalid number of channels connected for Neuropixels 1.0f, configuration might be invalid."); + } + return shankBits; } -std::vector> Neuropixels_1::makeConfigBits(NeuropixelsReference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs) +Neuropixels_1::CongigBitsArray Neuropixels_1::makeConfigBits(NeuropixelsV1Reference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs) { const int BaseConfigurationConfigOffset = 576; - std::vector> baseConfigs(2); + CongigBitsArray baseConfigs; for (int i = 0; i < numberOfChannels; i++) { @@ -691,25 +710,7 @@ std::vector> Neuropixels_1 return baseConfigs; } -template std::vector Neuropixels_1::toBitReversedBytes(std::bitset bits) -{ - std::vector bytes((bits.size() - 1) / 8 + 1); - - for (int i = 0; i < bytes.size(); i++) - { - for (int j = 0; j < 8; j++) - { - bytes[i] |= bits[i * 8 + j] << (8 - j - 1); - } - - // NB: Reverse bytes (http://graphics.stanford.edu/~seander/bithacks.html) - bytes[i] = (unsigned char)((bytes[i] * 0x0202020202ul & 0x010884422010ul) % 1023); - } - - return bytes; -} - -void Neuropixels_1::writeShiftRegisters(std::bitset shankBits, std::vector> configBits, Array adcs, double lfpGainCorrection, double apGainCorrection) +void Neuropixels_1::writeShiftRegisters(ShankBitset shankBits, CongigBitsArray configBits, Array adcs, double lfpGainCorrection, double apGainCorrection) { auto shankBytes = toBitReversedBytes(shankBits); @@ -796,7 +797,7 @@ void Neuropixels_1::writeShiftRegisters(std::bitset } } -void Neuropixels_1::defineMetadata(ProbeSettings* settings) +void Neuropixels_1::defineMetadata(ProbeSettings* settings) { settings->probeType = ProbeType::NPX_V1E; settings->probeMetadata.name = "Neuropixels 1.0f"; @@ -810,8 +811,8 @@ void Neuropixels_1::defineMetadata(ProbeSettings* settings) path.closeSubPath(); settings->probeMetadata.shank_count = 1; - settings->probeMetadata.electrodes_per_shank = 960; - settings->probeMetadata.rows_per_shank = 960 / 2; + settings->probeMetadata.electrodes_per_shank = numberOfElectrodes; + settings->probeMetadata.rows_per_shank = numberOfElectrodes / 2; settings->probeMetadata.columns_per_shank = 2; settings->probeMetadata.shankOutline = path; settings->probeMetadata.num_adcs = 32; // NB: Is this right for 1.0e? @@ -826,7 +827,7 @@ void Neuropixels_1::defineMetadata(ProbeSettings* settings) Array xpositions = { 27.0f, 59.0f, 11.0f, 43.0f }; - for (int i = 0; i < settings->probeMetadata.electrodes_per_shank * settings->probeMetadata.shank_count; i++) + for (int i = 0; i < numberOfElectrodes; i++) { ElectrodeMetadata metadata; @@ -869,7 +870,7 @@ void Neuropixels_1::defineMetadata(ProbeSettings* settings) metadata.type = ElectrodeType::ELECTRODE; } - settings->electrodeMetadata.add(metadata); + settings->electrodeMetadata[i] = metadata; } settings->apGainIndex = 4; // NB: AP Gain Index of 4 = Gain1000 @@ -879,10 +880,9 @@ void Neuropixels_1::defineMetadata(ProbeSettings* settings) for (int i = 0; i < numberOfChannels; i++) { - settings->selectedBank.add(Bank::A); - settings->selectedChannel.add(i); - settings->selectedShank.add(0); - settings->selectedElectrode.add(i); + settings->selectedBank[i] = Bank::A; + settings->selectedShank[i] = 0; + settings->selectedElectrode[i] = i; } settings->availableApGains.add(50.0f); @@ -913,4 +913,6 @@ void Neuropixels_1::defineMetadata(ProbeSettings* settings) settings->availableElectrodeConfigurations.add("Tetrodes"); settings->electrodeConfigurationIndex = 0; + + settings->isValid = true; } diff --git a/Source/Devices/Neuropixels_1.h b/Source/Devices/Neuropixels_1.h index 7da604c..e1a6cb5 100644 --- a/Source/Devices/Neuropixels_1.h +++ b/Source/Devices/Neuropixels_1.h @@ -25,13 +25,6 @@ #include "../OnixDevice.h" #include "../NeuropixComponents.h" -#include -#include -#include -#include -#include -#include - enum class NeuropixelsRegisters : uint32_t { OP_MODE = 0x00, @@ -78,7 +71,7 @@ enum class RecMod : uint32_t ACTIVE = DIG_NRESET | CH_NRESET }; -enum class NeuropixelsReference : unsigned char +enum class NeuropixelsV1Reference : unsigned char { External = 0b001, Tip = 0b010 @@ -148,7 +141,8 @@ struct NeuropixelsV1Adc Configures and streams data from a Neuropixels 1.0f device */ -class Neuropixels_1 : public OnixDevice, +class Neuropixels_1 : public INeuropixel, + public OnixDevice, public I2CRegisterContext { public: @@ -161,8 +155,6 @@ class Neuropixels_1 : public OnixDevice, /** Update the settings of the device by writing to hardware */ bool updateSettings() override; - void setSettings(ProbeSettings* settings_) const; - /** Starts probe data streaming */ void startAcquisition() override; @@ -176,29 +168,17 @@ class Neuropixels_1 : public OnixDevice, void processFrames() override; - int64 getProbeNumber() const { return probeNumber; } - NeuropixelsGain getGainEnum(int index); int getGainValue(NeuropixelsGain); - NeuropixelsReference getReference(int index); - - /** Select a preset electrode configuration */ - Array selectElectrodeConfiguration(String config); + NeuropixelsV1Reference getReference(int index); static const int shankConfigurationBitCount = 968; static const int BaseConfigurationBitCount = 2448; - std::bitset static makeShankBits(NeuropixelsReference reference, Array channelMap); - - std::vector> static makeConfigBits(NeuropixelsReference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs); - - void writeShiftRegisters(std::bitset shankBits, std::vector> configBits, Array adcs, double lfpGainCorrection, double apGainCorrection); - - std::unique_ptr settings; - - static const int numberOfChannels = 384; + using ShankBitset = std::bitset; + using CongigBitsArray = std::array, 2>; String adcCalibrationFilePath; String gainCalibrationFilePath; @@ -207,6 +187,22 @@ class Neuropixels_1 : public OnixDevice, void setShouldCorrectOffset(bool value) { shouldCorrectOffset = value; } + ShankBitset static makeShankBits(NeuropixelsV1Reference reference, std::array channelMap); + + CongigBitsArray static makeConfigBits(NeuropixelsV1Reference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs); + + void writeShiftRegisters(ShankBitset shankBits, CongigBitsArray configBits, Array adcs, double lfpGainCorrection, double apGainCorrection); + + // INeuropixels methods + void setSettings(ProbeSettings* settings_, int index = 0) override; + + void defineMetadata(ProbeSettings* settings) override; + + uint64_t getProbeSerialNumber(int index = 0) override { return probeNumber; } + + /** Select a preset electrode configuration */ + std::vector selectElectrodeConfiguration(String config) override; + private: DataBuffer* apBuffer; @@ -241,13 +237,9 @@ class Neuropixels_1 : public OnixDevice, static const int ProbeI2CAddress = 0x70; - template std::vector static toBitReversedBytes(std::bitset shankBits); - - void defineMetadata(ProbeSettings* settings); - Array frameArray; - int64 probeNumber = 0; + uint64_t probeNumber = 0; std::array lfpSamples; std::array apSamples; diff --git a/Source/Devices/PortController.cpp b/Source/Devices/PortController.cpp index 1d0eddb..0afa964 100644 --- a/Source/Devices/PortController.cpp +++ b/Source/Devices/PortController.cpp @@ -90,6 +90,10 @@ DiscoveryParameters PortController::getHeadstageDiscoveryParameters(String heads { return DiscoveryParameters(5.0, 7.0, 1.0, 0.2); } + else if (headstage == NEUROPIXELSV2E_HEADSTAGE_NAME) + { + return DiscoveryParameters(3.3f, 5.5f, 1.0f, 0.2f); + } return DiscoveryParameters(); } diff --git a/Source/Formats/ProbeInterface.h b/Source/Formats/ProbeInterface.h index eca22e1..635b34b 100644 --- a/Source/Formats/ProbeInterface.h +++ b/Source/Formats/ProbeInterface.h @@ -27,7 +27,7 @@ class ProbeInterfaceJson { public: - static bool writeProbeSettingsToJson(File& file, ProbeSettings* settings) + static bool writeProbeSettingsToJson(File& file, ProbeSettings<0, 0>* settings) { DynamicObject output; @@ -49,22 +49,23 @@ class ProbeInterfaceJson for (int elec = 0; elec < settings->electrodeMetadata.size(); elec++) { - ElectrodeMetadata& em = settings->electrodeMetadata.getReference(elec); - int channelIndex = settings->selectedElectrode.indexOf(elec); - int channel = -1; - if (channelIndex > -1) - channel = settings->selectedChannel[channelIndex]; + ElectrodeMetadata& em = settings->electrodeMetadata[elec]; + int channelIndex = settings->selectedElectrode[elec]; + // TODO: Fix this section + //int channel = -1; + //if (channelIndex > -1) + // channel = settings->selectedChannel[channelIndex]; Array contact_position; contact_position.add(em.xpos + 250 * em.shank); contact_position.add(em.ypos); DynamicObject::Ptr contact_shape_param = new DynamicObject; - contact_shape_param->setProperty(Identifier("width"), settings->electrodeMetadata.getReference(elec).site_width); + contact_shape_param->setProperty(Identifier("width"), settings->electrodeMetadata[elec].site_width); contact_positions.add(contact_position); shank_ids.add(String(em.shank)); - device_channel_indices.add(channel); + device_channel_indices.add(0); contact_plane_axes.add(contact_plane_axis); contact_shapes.add("square"); contact_shape_params.add(contact_shape_param.get()); @@ -105,7 +106,7 @@ class ProbeInterfaceJson return true; } - static bool readProbeSettingsFromJson(File& file, ProbeSettings* settings) + static bool readProbeSettingsFromJson(File& file, ProbeSettings<0,0>* settings) { var result; diff --git a/Source/FrameReader.cpp b/Source/FrameReader.cpp index ae15ed5..a91ddb7 100644 --- a/Source/FrameReader.cpp +++ b/Source/FrameReader.cpp @@ -48,7 +48,7 @@ void FrameReader::run() for (const auto& source : sources) { - if (frame->dev_idx == source->getDeviceIdx()) + if (frame->dev_idx == source->getDeviceIdx(true)) { source->addFrame(frame); destroyFrame = false; diff --git a/Source/NeuropixComponents.h b/Source/NeuropixComponents.h index e29363c..40cef71 100644 --- a/Source/NeuropixComponents.h +++ b/Source/NeuropixComponents.h @@ -25,14 +25,18 @@ #include #include #include +#include #include "UI/ActivityView.h" -# define SAMPLECOUNT 64 -# define MAX_HEADSTAGE_CLK_SAMPLE 3221225475 -# define MAX_ALLOWABLE_TIMESTAMP_JUMP 4 - -# define MAXPACKETS 64 +enum class VisualizationMode +{ + ENABLE_VIEW, + AP_GAIN_VIEW, + LFP_GAIN_VIEW, + REFERENCE_VIEW, + ACTIVITY_VIEW +}; enum class ProbeType { @@ -103,6 +107,24 @@ struct ProbeMetadata bool switchable; }; +struct NeuropixelsV1fValues +{ + static const int numberOfChannels = 384; + static const int numberOfElectrodes = 960; + static const int numberOfShanks = 1; + static const int numberOfSettings = 1; +}; + +struct NeuropixelsV2eValues +{ + static const int numberOfChannels = 384; + static const int electrodesPerShank = 1280; + static const int numberOfShanks = 4; + static const int numberOfElectrodes = numberOfShanks * electrodesPerShank; + static const int numberOfSettings = 2; +}; + +template struct ProbeSettings { void updateProbeSettings(ProbeSettings* newSettings) @@ -121,7 +143,6 @@ struct ProbeSettings selectedBank = newSettings->selectedBank; selectedShank = newSettings->selectedShank; - selectedChannel = newSettings->selectedChannel; selectedElectrode = newSettings->selectedElectrode; electrodeMetadata = newSettings->electrodeMetadata; @@ -134,10 +155,45 @@ struct ProbeSettings { selectedBank.clear(); selectedShank.clear(); - selectedChannel.clear(); selectedElectrode.clear(); } + void selectElectrodes(std::vector electrodes) + { + for (int i = 0; i < electrodes.size(); i++) + { + selectElectrode(electrodes[i]); + } + } + + void selectElectrode(int electrode) + { + Bank bank = electrodeMetadata[electrode].bank; + int channel = electrodeMetadata[electrode].channel; + int shank = electrodeMetadata[electrode].shank; + int global_index = electrodeMetadata[electrode].global_index; + + for (int j = 0; j < electrodeMetadata.size(); j++) + { + if (electrodeMetadata[j].channel == channel) + { + if (electrodeMetadata[j].bank == bank && electrodeMetadata[j].shank == shank) + { + electrodeMetadata[j].status = ElectrodeStatus::CONNECTED; + } + + else + { + electrodeMetadata[j].status = ElectrodeStatus::DISCONNECTED; + } + } + } + + selectedBank[channel] = bank; + selectedShank[channel] = shank; + selectedElectrode[channel] = global_index; + } + Array availableElectrodeConfigurations; Array availableApGains; // Available AP gain values for each channel (if any) Array availableLfpGains; // Available LFP gain values for each channel (if any) @@ -150,13 +206,62 @@ struct ProbeSettings int referenceIndex; bool apFilterState; - Array selectedBank; - Array selectedShank; - Array selectedChannel; - Array selectedElectrode; - Array electrodeMetadata; + std::array selectedBank; + std::array selectedShank; + std::array selectedElectrode; + std::array electrodeMetadata; ProbeType probeType; ProbeMetadata probeMetadata; + + bool isValid = false; +}; + +template +std::vector toBitReversedBytes(std::bitset bits) +{ + std::vector bytes((bits.size() - 1) / 8 + 1); + + for (int i = 0; i < bytes.size(); i++) + { + for (int j = 0; j < 8; j++) + { + bytes[i] |= bits[i * 8 + j] << (8 - j - 1); + } + + // NB: Reverse bytes (http://graphics.stanford.edu/~seander/bithacks.html) + bytes[i] = (unsigned char)((bytes[i] * 0x0202020202ul & 0x010884422010ul) % 1023); + } + + return bytes; +} + +template +class INeuropixel +{ +public: + INeuropixel(int numSettings, int numShanks) : numberOfShanks(numShanks) + { + if (numSettings > 2) return; + + for (int i = 0; i < numSettings; i++) + { + settings.emplace_back(std::make_unique>()); + } + } + + static const int numberOfChannels = ch; + static const int numberOfElectrodes = e; + const int numberOfShanks; + + std::vector>> settings; + + virtual void setSettings(ProbeSettings* settings_, int index) { return; } + + virtual void defineMetadata(ProbeSettings* settings) { return; } + + virtual uint64_t getProbeSerialNumber(int index) { return 0; } + + virtual std::vector selectElectrodeConfiguration(String config) { return {}; } }; diff --git a/Source/Onix1.h b/Source/Onix1.h index 397b8a0..f55e406 100644 --- a/Source/Onix1.h +++ b/Source/Onix1.h @@ -30,6 +30,7 @@ #include "../../plugin-GUI/Source/Utils/Utils.h" constexpr char* NEUROPIXELSV1F_HEADSTAGE_NAME = "Neuropixels 1.0f"; +constexpr char* NEUROPIXELSV2E_HEADSTAGE_NAME = "Neuropixels 2.0e"; constexpr char* BREAKOUT_BOARD_NAME = "Breakout Board"; class error_t : public std::exception diff --git a/Source/OnixDevice.cpp b/Source/OnixDevice.cpp index 1ae5b3b..de51c32 100644 --- a/Source/OnixDevice.cpp +++ b/Source/OnixDevice.cpp @@ -27,4 +27,20 @@ OnixDevice::OnixDevice(String name_, OnixDeviceType type_, const oni_dev_idx_t d { deviceContext = ctx; name = name_; + + if (type == OnixDeviceType::NEUROPIXELSV2E) + isPassthrough = true; +} + +oni_dev_idx_t OnixDevice::getDeviceIdx(bool getPassthroughIndex) const +{ + if (isPassthrough && !getPassthroughIndex) + return getDeviceIndexFromPassthroughIndex(deviceIdx); + else + return deviceIdx; +} + +oni_dev_idx_t OnixDevice::getDeviceIndexFromPassthroughIndex(oni_dev_idx_t passthroughIndex) +{ + return (passthroughIndex - 7) << 8; } diff --git a/Source/OnixDevice.h b/Source/OnixDevice.h index b3a7a6d..b94e14a 100644 --- a/Source/OnixDevice.h +++ b/Source/OnixDevice.h @@ -45,7 +45,7 @@ enum class OnixDeviceType { HS64, BNO, NEUROPIXELS_1, - NEUROPIXELS_2, + NEUROPIXELSV2E, ADC, PORT_CONTROL, MEMORYMONITOR, @@ -161,9 +161,9 @@ class OnixDevice /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ virtual void addSourceBuffers(OwnedArray& sourceBuffers) {}; - const oni_dev_idx_t getDeviceIdx() const { return deviceIdx; } + oni_dev_idx_t getDeviceIdx(bool getPassthroughIndex = false) const; - OnixDeviceType type; + const OnixDeviceType type; Array streamInfos; @@ -171,6 +171,8 @@ class OnixDevice protected: + static oni_dev_idx_t getDeviceIndexFromPassthroughIndex(oni_dev_idx_t hubIndex); + const oni_dev_idx_t deviceIdx; std::shared_ptr deviceContext; @@ -179,6 +181,7 @@ class OnixDevice String name; bool enabled = true; + bool isPassthrough = false; JUCE_LEAK_DETECTOR(OnixDevice); }; diff --git a/Source/OnixSource.cpp b/Source/OnixSource.cpp index 8dd355f..7721692 100644 --- a/Source/OnixSource.cpp +++ b/Source/OnixSource.cpp @@ -89,6 +89,9 @@ void OnixSource::disconnectDevices(bool updateStreamInfo) devicesFound = false; + if (context != nullptr && context->isInitialized()) + context->setOption(ONIX_OPT_PASSTHROUGH, 0); + if (updateStreamInfo) CoreServices::updateSignalChain(editor); } @@ -105,24 +108,30 @@ void OnixSource::initializeDevices(bool updateStreamInfo) disconnectDevices(false); } - // TODO: How to set passthrough for Port A vs. Port B? - if (getParameter("passthroughA")->getValue() || getParameter("passthroughB")->getValue()) + int val = 0; + + if (getParameter("passthroughA")->getValue()) { - LOGD("Passthrough mode enabled"); - int val = 1; - context->setOption(ONIX_OPT_PASSTHROUGH, val); + LOGD("Passthrough mode enabled for Port A"); + val |= 1 << 0; } - else + + if (getParameter("passthroughB")->getValue()) { - int val = 0; - context->setOption(ONIX_OPT_PASSTHROUGH, val); + LOGD("Passthrough mode enabled for Port B"); + val |= 1 << 2; } + context->setOption(ONIX_OPT_PASSTHROUGH, val); + context->issueReset(); context->updateDeviceTable(); if (context->getLastResult() != ONI_ESUCCESS) return; + if (portA->configureDevice() != ONI_ESUCCESS) LOGE("Unable to configure Port A."); + if (portB->configureDevice() != ONI_ESUCCESS) LOGE("Unable to configure Port B."); + device_map_t deviceMap = context->getDeviceTable(); if (deviceMap.size() == 0) @@ -198,11 +207,11 @@ void OnixSource::initializeDevices(bool updateStreamInfo) auto EEPROM = std::make_unique(index, context); uint32_t hsid = EEPROM->GetHeadStageID(); LOGD("Detected headstage ", hsid); - if (hsid == 8) //Npix2.0e headstage, constant needs to be added to onix.h + if (hsid == ONIX_HUB_HSNP2E) { - auto np2 = std::make_shared("Probe-" + String::charToString(probeLetters[npxProbeIdx]), index, context); + auto np2 = std::make_shared("Neuropixels 2e-" + String::charToString(probeLetters[npxProbeIdx]), index, context); int res = np2->configureDevice(); - if (res != 0) + if (res != ONI_ESUCCESS) { if (res == -1) { @@ -214,6 +223,8 @@ void OnixSource::initializeDevices(bool updateStreamInfo) npxProbeIdx += np2->getNumProbes(); sources.emplace_back(np2); + + headstages.insert({ PortController::getOffsetFromIndex(index), NEUROPIXELSV2E_HEADSTAGE_NAME }); } } else if (device.id == ONIX_MEMUSAGE) @@ -308,9 +319,6 @@ void OnixSource::initializeDevices(bool updateStreamInfo) } } - if (portA->configureDevice() != ONI_ESUCCESS) LOGE("Unable to configure Port A."); - if (portB->configureDevice() != ONI_ESUCCESS) LOGE("Unable to configure Port B."); - context->issueReset(); oni_size_t frameSize = context->getOption(ONI_OPT_MAXREADFRAMESIZE); @@ -543,7 +551,7 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel addCombinedStreams(dataStreamSettings, source->streamInfos, dataStreams, deviceInfos, continuousChannels); } - else if (source->type == OnixDeviceType::NEUROPIXELS_2) + else if (source->type == OnixDeviceType::NEUROPIXELSV2E) { DeviceInfo::Settings deviceSettings{ source->getName(), diff --git a/Source/OnixSource.h b/Source/OnixSource.h index 59a56f4..7fcd642 100644 --- a/Source/OnixSource.h +++ b/Source/OnixSource.h @@ -27,8 +27,8 @@ #include "Onix1.h" #include "OnixDevice.h" #include "OnixSourceEditor.h" -#include "Devices/DeviceList.h" #include "FrameReader.h" +#include "Devices/PortController.h" /** diff --git a/Source/OnixSourceCanvas.cpp b/Source/OnixSourceCanvas.cpp index 2db1685..751f36f 100644 --- a/Source/OnixSourceCanvas.cpp +++ b/Source/OnixSourceCanvas.cpp @@ -21,38 +21,14 @@ */ #include "OnixSourceCanvas.h" - -CustomTabButton::CustomTabButton(const String& name, TabbedComponent* parent, bool isTopLevel_) : - TabBarButton(name, parent->getTabbedButtonBar()), - isTopLevel(isTopLevel_) -{ -} - -void CustomTabButton::paintButton(Graphics& g, - bool isMouseOver, - bool isMouseDown) -{ - getTabbedButtonBar().setTabBackgroundColour(getIndex(), Colours::grey); - - getLookAndFeel().drawTabButton(*this, g, isMouseOver, isMouseDown); -} - -CustomTabComponent::CustomTabComponent(OnixSourceEditor* editor_, bool isTopLevel_) : - TabbedComponent(TabbedButtonBar::TabsAtTop), - editor(editor_), - isTopLevel(isTopLevel_) -{ - setTabBarDepth(26); - setOutline(0); - setIndent(0); -} +#include "OnixSource.h" OnixSourceCanvas::OnixSourceCanvas(GenericProcessor* processor_, OnixSourceEditor* editor_, OnixSource* onixSource_) : Visualizer(processor_), editor(editor_), source(onixSource_) { - topLevelTabComponent = std::make_unique(editor, true); + topLevelTabComponent = std::make_unique(true); addAndMakeVisible(topLevelTabComponent.get()); addHub(BREAKOUT_BOARD_NAME, 0); @@ -60,7 +36,7 @@ OnixSourceCanvas::OnixSourceCanvas(GenericProcessor* processor_, OnixSourceEdito CustomTabComponent* OnixSourceCanvas::addTopLevelTab(String tabName, int index) { - CustomTabComponent* tab = new CustomTabComponent(editor, false); + CustomTabComponent* tab = new CustomTabComponent(false); topLevelTabComponent->addTab(tabName, Colours::grey, tab, true, index); tab->setName(tabName); @@ -83,7 +59,9 @@ void OnixSourceCanvas::addHub(String hubName, int offset) if (hubName == NEUROPIXELSV1F_HEADSTAGE_NAME) { - tab = addTopLevelTab(getTopLevelTabName(port, hubName), (int)port); + const int passthroughIndex = (offset >> 8) + 7; + + tab = addTopLevelTab(getTopLevelTabName(port, hubName), (int)port - 1); devices.emplace_back(std::make_shared("Probe-A", offset, nullptr)); devices.emplace_back(std::make_shared("Probe-B", offset + 1, nullptr)); @@ -98,6 +76,15 @@ void OnixSourceCanvas::addHub(String hubName, int offset) devices.emplace_back(std::make_shared("Digital IO", 7, nullptr)); devices.emplace_back(std::make_shared("Harp Sync Input", 12, nullptr)); } + else if (hubName == NEUROPIXELSV2E_HEADSTAGE_NAME) + { + const int passthroughIndex = (offset >> 8) + 7; + + tab = addTopLevelTab(getTopLevelTabName(port, hubName), (int)port - 1); + + devices.emplace_back(std::make_shared("Neuropixels 2e-A", passthroughIndex, nullptr)); + // TODO: Add Polled BNO here + } if (tab != nullptr && devices.size() > 0) { @@ -141,13 +128,18 @@ void OnixSourceCanvas::populateSourceTabs(CustomTabComponent* tab, OnixDeviceVec auto digitalIOInterface = std::make_shared(std::static_pointer_cast(device), editor, this); addInterfaceToTab(getDeviceTabName(device), tab, digitalIOInterface); } + else if (device->type == OnixDeviceType::NEUROPIXELSV2E) + { + auto npxv2eInterface = std::make_shared(std::static_pointer_cast(device), editor, this); + addInterfaceToTab(getDeviceTabName(device), tab, npxv2eInterface); + } } } void OnixSourceCanvas::addInterfaceToTab(String tabName, CustomTabComponent* tab, std::shared_ptr interface_) { settingsInterfaces.emplace_back(interface_); - tab->addTab(tabName, Colours::darkgrey, createCustomViewport(interface_.get()), true); + tab->addTab(tabName, Colours::darkgrey, CustomViewport::createCustomViewport(interface_.get()), true); } void OnixSourceCanvas::updateSettingsInterfaceDataSource(std::shared_ptr device) @@ -176,7 +168,7 @@ void OnixSourceCanvas::updateSettingsInterfaceDataSource(std::shared_ptr(device); auto npx1Selected = std::static_pointer_cast(settingsInterfaces[ind]->device); - npx1Found->setSettings(npx1Selected->settings.get()); + npx1Found->setSettings(npx1Selected->settings[0].get()); npx1Found->adcCalibrationFilePath = npx1Selected->adcCalibrationFilePath; npx1Found->gainCalibrationFilePath = npx1Selected->gainCalibrationFilePath; } @@ -198,6 +190,17 @@ void OnixSourceCanvas::updateSettingsInterfaceDataSource(std::shared_ptrsetChannelDirection(i, analogIOSelected->getChannelDirection(i)); } } + else if (device->type == OnixDeviceType::NEUROPIXELSV2E) + { + auto npx2Found = std::static_pointer_cast(device); + auto npx2Selected = std::static_pointer_cast(settingsInterfaces[ind]->device); + npx2Found->setSettings(npx2Selected->settings[0].get(), 0); + npx2Found->setSettings(npx2Selected->settings[1].get(), 1); + npx2Found->setGainCorrectionFile(0, npx2Selected->getGainCorrectionFile(0)); + npx2Found->setGainCorrectionFile(1, npx2Selected->getGainCorrectionFile(1)); + + std::static_pointer_cast(settingsInterfaces[ind])->updateDevice(npx2Found); + } device->setEnabled(settingsInterfaces[ind]->device->isEnabled()); settingsInterfaces[ind]->device.reset(); @@ -331,6 +334,8 @@ void OnixSourceCanvas::askKeepUpdate(int offset, String foundHeadstage, OnixDevi { String selectedHeadstage = editor->getHeadstageSelected(offset); + if (selectedHeadstage == foundHeadstage) return; + String msg = "Headstage " + selectedHeadstage + " is selected on " + PortController::getPortName(offset) + ". "; msg += "However, headstage " + foundHeadstage + " was found on " + PortController::getPortName(offset) + ". \n\n"; msg += "Select one of the options below to continue:\n"; diff --git a/Source/OnixSourceCanvas.h b/Source/OnixSourceCanvas.h index c7bbced..2e5c1ba 100644 --- a/Source/OnixSourceCanvas.h +++ b/Source/OnixSourceCanvas.h @@ -25,57 +25,14 @@ #include #include "UI/InterfaceList.h" +#include "UI/CustomTabComponent.h" #include "OnixSourceEditor.h" -#include "OnixSource.h" class OnixSource; /** - - TabBarButton with custom appearance - -*/ -class CustomTabButton : public TabBarButton -{ -public: - /** Constructor */ - CustomTabButton(const String& name, TabbedComponent* parent, bool isTopLevel_); - - /** Paints the button */ - void paintButton(Graphics& g, bool isMouseOver, bool isMouseDown) override; - -private: - bool isTopLevel; -}; - -/** - - Adds a callback when tab is changed - -*/ - -class CustomTabComponent : public TabbedComponent -{ -public: - /** Constructor */ - CustomTabComponent(OnixSourceEditor* editor_, bool isTopLevel_); - - /** Create tab buttons*/ - TabBarButton* createTabButton(const juce::String& name, int index) override - { - return new CustomTabButton(name, this, isTopLevel); - } - -private: - OnixSourceEditor* editor; - bool isTopLevel; -}; - -/** - Holds the visualizer for additional probe settings - */ class OnixSourceCanvas : public Visualizer { @@ -132,9 +89,6 @@ class OnixSourceCanvas : public Visualizer /** Get the given parameter from the source node */ Parameter* getSourceParameter(String name); - /** Creates a custom viewport for the given interface */ - CustomViewport* createCustomViewport(SettingsInterface* settingsInterface); - Array getHeadstageTabs(); std::map createSelectedMap(std::vector>); diff --git a/Source/OnixSourceEditor.cpp b/Source/OnixSourceEditor.cpp index 51777a3..81d889d 100644 --- a/Source/OnixSourceEditor.cpp +++ b/Source/OnixSourceEditor.cpp @@ -83,7 +83,6 @@ OnixSourceEditor::OnixSourceEditor(GenericProcessor* parentNode, OnixSource* sou headstageComboBoxB->addListener(this); headstageComboBoxB->setTooltip("Select the headstage connected to port B."); addHeadstageComboBoxOptions(headstageComboBoxB.get()); - headstageComboBoxB->setSelectedId(1, dontSendNotification); addAndMakeVisible(headstageComboBoxB.get()); @@ -126,6 +125,7 @@ void OnixSourceEditor::addHeadstageComboBoxOptions(ComboBox* comboBox) comboBox->addItem("Select headstage...", 1); comboBox->addSeparator(); comboBox->addItem(NEUROPIXELSV1F_HEADSTAGE_NAME, 2); + comboBox->addItem(NEUROPIXELSV2E_HEADSTAGE_NAME, 3); // TODO: Add list of available devices here // TODO: Create const char* for the headstage names so they are shared across the plugin } @@ -293,8 +293,14 @@ void OnixSourceEditor::updateComboBox(ComboBox* cb) bool otherHeadstageSelected = isPortA ? headstageComboBoxB->getSelectedId() > 1 : headstageComboBoxA->getSelectedId() > 1; bool currentHeadstageSelected = isPortA ? headstageComboBoxA->getSelectedId() > 1 : headstageComboBoxB->getSelectedId() > 1; - canvas->removeTabs(currentPort); + if (otherHeadstageSelected) + canvas->removeTabs(currentPort); + else + canvas->removeAllTabs(); + String passthroughName = isPortA ? "passthroughA" : "passthroughB"; + bool passthroughValue = false; + if (currentHeadstageSelected) { String headstage = isPortA ? headstageComboBoxA->getText() : headstageComboBoxB->getText(); @@ -302,17 +308,13 @@ void OnixSourceEditor::updateComboBox(ComboBox* cb) source->updateDiscoveryParameters(currentPort, PortController::getHeadstageDiscoveryParameters(headstage)); canvas->addHub(headstage, PortController::getPortOffset(currentPort)); - String passthroughName = isPortA ? "passthroughA" : "passthroughB"; - - if (headstage == NEUROPIXELSV1F_HEADSTAGE_NAME) + if (headstage == NEUROPIXELSV2E_HEADSTAGE_NAME) { - source->getParameter(passthroughName)->setNextValue(false); - } - else - { - source->getParameter(passthroughName)->setNextValue(true); + passthroughValue = true; } } + + source->getParameter(passthroughName)->setNextValue(passthroughValue); } void OnixSourceEditor::updateSettings() diff --git a/Source/UI/CustomTabButton.h b/Source/UI/CustomTabButton.h new file mode 100644 index 0000000..531ca5f --- /dev/null +++ b/Source/UI/CustomTabButton.h @@ -0,0 +1,49 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#pragma once + +#include + +/** + TabBarButton with custom appearance +*/ +class CustomTabButton : public TabBarButton +{ +public: + /** Constructor */ + CustomTabButton(const String& name, TabbedComponent* parent, bool isTopLevel_) : + TabBarButton(name, parent->getTabbedButtonBar()), + isTopLevel(isTopLevel_) + { + } + + /** Paints the button */ + void paintButton(Graphics& g, bool isMouseOver, bool isMouseDown) override + { + getTabbedButtonBar().setTabBackgroundColour(getIndex(), Colours::grey); + getLookAndFeel().drawTabButton(*this, g, isMouseOver, isMouseDown); + } + +private: + bool isTopLevel; +}; diff --git a/Source/UI/CustomTabComponent.h b/Source/UI/CustomTabComponent.h new file mode 100644 index 0000000..75a8c5e --- /dev/null +++ b/Source/UI/CustomTabComponent.h @@ -0,0 +1,50 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#pragma once + +#include +#include "CustomTabButton.h" + +/** + Adds a callback when tab is changed +*/ +class CustomTabComponent : public TabbedComponent +{ +public: + CustomTabComponent(bool isTopLevel_) : + TabbedComponent(TabbedButtonBar::TabsAtTop), + isTopLevel(isTopLevel_) + { + setTabBarDepth(26); + setOutline(0); + setIndent(0); + } + + TabBarButton* createTabButton(const juce::String& name, int index) override + { + return new CustomTabButton(name, this, isTopLevel); + } + +private: + bool isTopLevel; +}; diff --git a/Source/UI/CustomViewport.h b/Source/UI/CustomViewport.h index c093b6f..9a7d5f5 100644 --- a/Source/UI/CustomViewport.h +++ b/Source/UI/CustomViewport.h @@ -46,8 +46,11 @@ class CustomViewport : public Component addAndMakeVisible(viewport.get()); } - ~CustomViewport() + static CustomViewport* createCustomViewport(SettingsInterface* settingsInterface) { + Rectangle bounds = settingsInterface->getBounds(); + + return new CustomViewport(settingsInterface, bounds.getWidth(), bounds.getHeight()); } void resized() override diff --git a/Source/UI/InterfaceList.h b/Source/UI/InterfaceList.h index 1113145..f3b54d4 100644 --- a/Source/UI/InterfaceList.h +++ b/Source/UI/InterfaceList.h @@ -22,6 +22,7 @@ #include "CustomViewport.h" #include "NeuropixV1Interface.h" +#include "NeuropixelsV2eInterface.h" #include "Bno055Interface.h" #include "OutputClockInterface.h" #include "HarpSyncInputInterface.h" diff --git a/Source/UI/NeuropixV1Interface.cpp b/Source/UI/NeuropixV1Interface.cpp index 6177db9..8c28f29 100644 --- a/Source/UI/NeuropixV1Interface.cpp +++ b/Source/UI/NeuropixV1Interface.cpp @@ -21,9 +21,6 @@ */ #include "NeuropixV1Interface.h" -#include "ProbeBrowser.h" - -#include "../Formats/ProbeInterface.h" NeuropixV1Interface::NeuropixV1Interface(std::shared_ptr d, OnixSourceEditor* e, OnixSourceCanvas* c) : SettingsInterface(d, e, c), @@ -39,7 +36,7 @@ NeuropixV1Interface::NeuropixV1Interface(std::shared_ptr d, OnixS mode = VisualizationMode::ENABLE_VIEW; - probeBrowser = std::make_unique(this); + probeBrowser = std::make_unique(this, 0); probeBrowser->setBounds(0, 0, 600, 600); addAndMakeVisible(probeBrowser.get()); @@ -182,9 +179,11 @@ NeuropixV1Interface::NeuropixV1Interface(std::shared_ptr d, OnixS electrodeConfigurationComboBox->setItemEnabled(1, false); electrodeConfigurationComboBox->addSeparator(); - for (int i = 0; i < npx->settings->availableElectrodeConfigurations.size(); i++) + auto settings = std::static_pointer_cast(device)->settings[0].get(); + + for (int i = 0; i < settings->availableElectrodeConfigurations.size(); i++) { - electrodeConfigurationComboBox->addItem(npx->settings->availableElectrodeConfigurations[i], i + 2); + electrodeConfigurationComboBox->addItem(settings->availableElectrodeConfigurations[i], i + 2); } checkForExistingChannelPreset(); @@ -193,16 +192,16 @@ NeuropixV1Interface::NeuropixV1Interface(std::shared_ptr d, OnixS currentHeight += 55; - if (npx->settings->availableApGains.size() > 0) + if (settings->availableApGains.size() > 0) { apGainComboBox = std::make_unique("apGainComboBox"); apGainComboBox->setBounds(450, currentHeight, 65, 22); apGainComboBox->addListener(this); - for (int i = 0; i < npx->settings->availableApGains.size(); i++) - apGainComboBox->addItem(String(npx->settings->availableApGains[i]) + "x", i + 1); + for (int i = 0; i < settings->availableApGains.size(); i++) + apGainComboBox->addItem(String(settings->availableApGains[i]) + "x", i + 1); - apGainComboBox->setSelectedId(npx->settings->apGainIndex + 1, dontSendNotification); + apGainComboBox->setSelectedId(settings->apGainIndex + 1, dontSendNotification); addAndMakeVisible(apGainComboBox.get()); apGainViewButton = std::make_unique("VIEW"); @@ -221,16 +220,16 @@ NeuropixV1Interface::NeuropixV1Interface(std::shared_ptr d, OnixS currentHeight += 55; } - if (npx->settings->availableLfpGains.size() > 0) + if (settings->availableLfpGains.size() > 0) { lfpGainComboBox = std::make_unique("lfpGainComboBox"); lfpGainComboBox->setBounds(450, currentHeight, 65, 22); lfpGainComboBox->addListener(this); - for (int i = 0; i < npx->settings->availableLfpGains.size(); i++) - lfpGainComboBox->addItem(String(npx->settings->availableLfpGains[i]) + "x", i + 1); + for (int i = 0; i < settings->availableLfpGains.size(); i++) + lfpGainComboBox->addItem(String(settings->availableLfpGains[i]) + "x", i + 1); - lfpGainComboBox->setSelectedId(npx->settings->lfpGainIndex + 1, dontSendNotification); + lfpGainComboBox->setSelectedId(settings->lfpGainIndex + 1, dontSendNotification); addAndMakeVisible(lfpGainComboBox.get()); lfpGainViewButton = std::make_unique("VIEW"); @@ -249,18 +248,18 @@ NeuropixV1Interface::NeuropixV1Interface(std::shared_ptr d, OnixS currentHeight += 55; } - if (npx->settings->availableReferences.size() > 0) + if (settings->availableReferences.size() > 0) { referenceComboBox = std::make_unique("ReferenceComboBox"); referenceComboBox->setBounds(450, currentHeight, 65, 22); referenceComboBox->addListener(this); - for (int i = 0; i < npx->settings->availableReferences.size(); i++) + for (int i = 0; i < settings->availableReferences.size(); i++) { - referenceComboBox->addItem(npx->settings->availableReferences[i], i + 1); + referenceComboBox->addItem(settings->availableReferences[i], i + 1); } - referenceComboBox->setSelectedId(npx->settings->referenceIndex + 1, dontSendNotification); + referenceComboBox->setSelectedId(settings->referenceIndex + 1, dontSendNotification); addAndMakeVisible(referenceComboBox.get()); referenceViewButton = std::make_unique("VIEW"); @@ -304,7 +303,7 @@ NeuropixV1Interface::NeuropixV1Interface(std::shared_ptr d, OnixS //activityViewComboBox = std::make_unique("ActivityView Combo Box"); - //if (npx->settings->availableLfpGains.size() > 0) + //if (settings->availableLfpGains.size() > 0) //{ // activityViewComboBox->setBounds(450, currentHeight, 65, 22); // activityViewComboBox->addListener(this); @@ -472,7 +471,7 @@ NeuropixV1Interface::NeuropixV1Interface(std::shared_ptr d, OnixS referenceViewRectangles[i]->setRectangle(Rectangle(referenceViewLabels[0]->getX() + 6, referenceViewLabels[i]->getBottom() + 1, 12, 12)); referenceViewComponent->addAndMakeVisible(referenceViewRectangles[i].get()); - referenceViewLabels.emplace_back(std::make_unique