diff --git a/Source/Devices/AnalogIO.cpp b/Source/Devices/AnalogIO.cpp index 5c0cc0d..f8a1277 100644 --- a/Source/Devices/AnalogIO.cpp +++ b/Source/Devices/AnalogIO.cpp @@ -28,7 +28,7 @@ AnalogIO::AnalogIO (std::string name, std::string hubName, const oni_dev_idx_t d : OnixDevice (name, hubName, AnalogIO::getDeviceType(), deviceIdx_, oni_ctx) { StreamInfo analogInputStream = StreamInfo ( - OnixDevice::createStreamName ({ getHubName(), name, "AnalogInput" }), + createStreamName ("AnalogInput", false), "Analog Input data", getStreamIdentifier(), numChannels, diff --git a/Source/Devices/Bno055.cpp b/Source/Devices/Bno055.cpp index 14547ef..050d218 100644 --- a/Source/Devices/Bno055.cpp +++ b/Source/Devices/Bno055.cpp @@ -31,7 +31,7 @@ Bno055::Bno055 (std::string name, std::string hubName, const oni_dev_idx_t devic std::string port = getPortName (deviceIdx); StreamInfo eulerAngleStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Euler" }), + createStreamName ("Euler"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Euler angle", streamIdentifier, 3, @@ -46,7 +46,7 @@ Bno055::Bno055 (std::string name, std::string hubName, const oni_dev_idx_t devic streamInfos.add (eulerAngleStream); StreamInfo quaternionStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Quaternion" }), + createStreamName ("Quaternion"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Quaternion", streamIdentifier, 4, @@ -61,7 +61,7 @@ Bno055::Bno055 (std::string name, std::string hubName, const oni_dev_idx_t devic streamInfos.add (quaternionStream); StreamInfo accelerationStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Acceleration" }), + createStreamName ("Acceleration"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Acceleration", streamIdentifier, 3, @@ -76,7 +76,7 @@ Bno055::Bno055 (std::string name, std::string hubName, const oni_dev_idx_t devic streamInfos.add (accelerationStream); StreamInfo gravityStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Gravity" }), + createStreamName ("Gravity"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Gravity", streamIdentifier, 3, @@ -91,7 +91,7 @@ Bno055::Bno055 (std::string name, std::string hubName, const oni_dev_idx_t devic streamInfos.add (gravityStream); StreamInfo temperatureStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Temperature" }), + createStreamName ("Temperature"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Temperature", streamIdentifier, 1, @@ -105,7 +105,7 @@ Bno055::Bno055 (std::string name, std::string hubName, const oni_dev_idx_t devic streamInfos.add (temperatureStream); StreamInfo calibrationStatusStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Calibration" }), + createStreamName ("Calibration"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Calibration status", streamIdentifier, 4, diff --git a/Source/Devices/DigitalIO.cpp b/Source/Devices/DigitalIO.cpp index acd6de9..eedeb7f 100644 --- a/Source/Devices/DigitalIO.cpp +++ b/Source/Devices/DigitalIO.cpp @@ -29,7 +29,7 @@ DigitalIO::DigitalIO (std::string name, std::string hubName, const oni_dev_idx_t : OnixDevice (name, hubName, DigitalIO::getDeviceType(), deviceIdx_, oni_ctx) { StreamInfo digitalInputStream = StreamInfo ( - OnixDevice::createStreamName ({ getHubName(), name, "DigitalInputs" }), + createStreamName ({ getHubName(), name, "DigitalInputs" }), "Digital Inputs data", getStreamIdentifier(), NumDigitalInputs, @@ -43,7 +43,7 @@ DigitalIO::DigitalIO (std::string name, std::string hubName, const oni_dev_idx_t streamInfos.add (digitalInputStream); StreamInfo digitalButtonStream = StreamInfo ( - OnixDevice::createStreamName ({ getHubName(), name, "DigitalButtons" }), + createStreamName ({ getHubName(), name, "DigitalButtons" }), "Digital Buttons data", getStreamIdentifier(), NumButtons, @@ -111,7 +111,7 @@ EventChannel::Settings DigitalIO::getEventChannelSettings (DataStream* stream) { EventChannel::Settings settings { EventChannel::Type::TTL, - OnixDevice::createStreamName ({ getHubName(), getName(), "Events" }), + createStreamName ("Events", false), "Digital inputs and breakout button states coming from a DigitalIO device", getStreamIdentifier() + ".event.digital", stream, diff --git a/Source/Devices/HarpSyncInput.cpp b/Source/Devices/HarpSyncInput.cpp index 9e24cc8..b95acb9 100644 --- a/Source/Devices/HarpSyncInput.cpp +++ b/Source/Devices/HarpSyncInput.cpp @@ -30,7 +30,7 @@ HarpSyncInput::HarpSyncInput (std::string name, std::string hubName, const oni_d setEnabled (false); StreamInfo harpTimeStream = StreamInfo ( - OnixDevice::createStreamName ({ getHubName(), getName(), "HarpTime" }), + createStreamName ("HarpTime", false), "Harp clock time corresponding to the local acquisition ONIX clock count", getStreamIdentifier(), 1, diff --git a/Source/Devices/MemoryMonitor.cpp b/Source/Devices/MemoryMonitor.cpp index 8843852..78f4fac 100644 --- a/Source/Devices/MemoryMonitor.cpp +++ b/Source/Devices/MemoryMonitor.cpp @@ -78,7 +78,7 @@ MemoryMonitor::MemoryMonitor (std::string name, std::string hubName, const oni_d : OnixDevice (name, hubName, MemoryMonitor::getDeviceType(), deviceIdx_, oni_ctx) { StreamInfo percentUsedStream = StreamInfo ( - OnixDevice::createStreamName ({ getHubName(), getName(), "PercentUsed" }), + createStreamName ("PercentUsed", false), "Percent of available memory that is currently used", getStreamIdentifier(), 1, diff --git a/Source/Devices/Neuropixels1.cpp b/Source/Devices/Neuropixels1.cpp index fca20cf..9a17df5 100644 --- a/Source/Devices/Neuropixels1.cpp +++ b/Source/Devices/Neuropixels1.cpp @@ -302,14 +302,29 @@ void Neuropixels1::defineMetadata (ProbeSettings selectElectrodeConfiguration (int electrodeConfigurationIndex) override; - uint64_t getProbeSerialNumber (int index = 0) override; - void setSettings (ProbeSettings* settings_, int index = 0) override; + uint64_t getProbeSerialNumber (int index = 0) override; + std::string getProbePartNumber (int index = 0) override; + std::string getFlexPartNumber (int index = 0) override; + std::string getFlexVersion (int index = 0) override; + bool parseGainCalibrationFile(); bool parseAdcCalibrationFile(); @@ -73,7 +76,7 @@ class Neuropixels1 : public INeuropixelReadByte (OFFSET_ID + i, ®_val); - - if (rc != ONI_ESUCCESS) - throw error_str ("Unable to read the probe serial number for device at address " + getDeviceIdx()); - - if (reg_val <= 0xFF) - { - probeNumber |= (((uint64_t) reg_val) << (i * 8)); - } - } + // Get Probe Metadata + probeMetadata = NeuropixelsProbeMetadata (flex.get(), OnixDeviceType::NEUROPIXELSV1E); - LOGD ("Probe SN: ", probeNumber); + LOGD ("Probe SN: ", probeMetadata.getProbeSerialNumber()); return ONI_ESUCCESS; } diff --git a/Source/Devices/Neuropixels1e.h b/Source/Devices/Neuropixels1e.h index b33146d..b139755 100644 --- a/Source/Devices/Neuropixels1e.h +++ b/Source/Devices/Neuropixels1e.h @@ -53,21 +53,15 @@ class Neuropixels1e : public Neuropixels1 static constexpr int FlexEepromI2CAddress = 0x50; - static constexpr uint32_t OFFSET_ID = 0; - static constexpr uint32_t OFFSET_VERSION = 10; - static constexpr uint32_t OFFSET_REVISION = 11; - static constexpr uint32_t OFFSET_FLEXPN = 20; - static constexpr uint32_t OFFSET_PROBEPN = 40; + std::unique_ptr deserializer; + std::unique_ptr serializer; + std::unique_ptr flex; static constexpr uint8_t DefaultGPO10Config = 0b00010001; // GPIO0 Low, NP in MUX reset static constexpr uint8_t DefaultGPO32Config = 0b10010001; // LED off, GPIO1 Low static constexpr uint32_t Gpo10ResetMask = 1 << 3; // Used to issue mux reset command to probe static constexpr uint32_t Gpo32LedMask = 1 << 7; // Used to turn on and off LED - std::unique_ptr deserializer; - std::unique_ptr serializer; - std::unique_ptr flex; - void resetProbe(); void writeShiftRegisters(); diff --git a/Source/Devices/Neuropixels1f.cpp b/Source/Devices/Neuropixels1f.cpp index e7e77cd..1463c5f 100644 --- a/Source/Devices/Neuropixels1f.cpp +++ b/Source/Devices/Neuropixels1f.cpp @@ -68,7 +68,7 @@ Neuropixels1f::Neuropixels1f (std::string name, std::string hubName, const oni_d { std::string port = getPortName (deviceIdx); StreamInfo apStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), STREAM_NAME_AP }), + createStreamName (STREAM_NAME_AP), "Neuropixels 1.0 AP band data stream", getStreamIdentifier(), numberOfChannels, @@ -82,7 +82,7 @@ Neuropixels1f::Neuropixels1f (std::string name, std::string hubName, const oni_d streamInfos.add (apStream); StreamInfo lfpStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), STREAM_NAME_LFP }), + createStreamName (STREAM_NAME_LFP), "Neuropixels 1.0 LFP band data stream", getStreamIdentifier(), numberOfChannels, @@ -105,8 +105,6 @@ Neuropixels1f::Neuropixels1f (std::string name, std::string hubName, const oni_d apEventCodes[i] = 0; lfpEventCodes[i] = 0; } - - probeNumber = 0; } int Neuropixels1f::configureDevice() @@ -123,32 +121,11 @@ int Neuropixels1f::configureDevice() return ONI_ESUCCESS; } - // Get Probe SN - uint32_t eepromOffset = 0; - uint32_t i2cAddr = 0x50; - int errorCode = 0; - - for (int i = 0; i < 8; i++) - { - oni_reg_addr_t reg_addr = ((eepromOffset + i) << 7) | i2cAddr; - - oni_reg_val_t reg_val; - rc = deviceContext->readRegister (deviceIdx, reg_addr, ®_val); - - if (rc != ONI_ESUCCESS) - { - LOGE (oni_error_str (rc)); - throw error_str ("Could not communicate with " + getName() + " on " + getHubName() - + ". Ensure that the flex connection is properly seated, or disable the device if it is not connected."); - } - - if (reg_val <= 0xFF) - { - probeNumber |= (((uint64_t) reg_val) << (i * 8)); - } - } + // Get Probe Metadata + auto flex = std::make_unique (FlexEepromI2CAddress, deviceIdx, deviceContext); + probeMetadata = NeuropixelsProbeMetadata (flex.get(), OnixDeviceType::NEUROPIXELSV1F); - LOGD ("Probe SN: ", probeNumber); + LOGD ("Probe SN: ", probeMetadata.getProbeSerialNumber()); // Enable device streaming rc = deviceContext->writeRegister (deviceIdx, 0x8000, 1); diff --git a/Source/Devices/Neuropixels2e.cpp b/Source/Devices/Neuropixels2e.cpp index fbce27b..d14679c 100644 --- a/Source/Devices/Neuropixels2e.cpp +++ b/Source/Devices/Neuropixels2e.cpp @@ -29,13 +29,12 @@ Neuropixels2e::Neuropixels2e (std::string name, std::string hubName, const oni_d I2CRegisterContext (ProbeI2CAddress, deviceIdx_, ctx_), INeuropixel (NeuropixelsV2eValues::numberOfSettings, NeuropixelsV2eValues::numberOfShanks) { - probeSN.fill (0); frameCount.fill (0); sampleNumber.fill (0); for (int i = 0; i < NeuropixelsV2eValues::numberOfSettings; i++) { - defineMetadata (settings[i].get(), NeuropixelsV2eValues::numberOfShanks); + defineMetadata (settings[i].get()); } for (int i = 0; i < NumberOfProbes; i++) @@ -46,7 +45,7 @@ Neuropixels2e::~Neuropixels2e() { if (serializer != nullptr) { - selectProbe (NoProbeSelected); + selectProbe (serializer.get(), NoProbeSelected); serializer->WriteByte ((uint32_t) DS90UB9x::DS90UB9xSerializerI2CRegister::GPIO10, DefaultGPO10Config); } @@ -54,10 +53,15 @@ Neuropixels2e::~Neuropixels2e() deviceContext->setOption (ONIX_OPT_PASSTHROUGH, 0); } +std::string Neuropixels2e::createStreamName (int n) +{ + return OnixDevice::createStreamName (ProbeString + std::to_string (n)); +} + void Neuropixels2e::createDataStream (int n) { StreamInfo apStream = StreamInfo ( - OnixDevice::createStreamName ({ getPortName (getDeviceIdx()), getHubName(), "Probe" + std::to_string (n) }), + createStreamName (n), "Neuropixels 2.0 data stream", getStreamIdentifier(), numberOfChannels, @@ -231,7 +235,7 @@ uint64_t Neuropixels2e::getProbeSerialNumber (int index) { try { - return probeSN.at (index); + return probeMetadata.at (index).getProbeSerialNumber(); } catch (const std::out_of_range& ex) // filter for out of range { @@ -241,6 +245,48 @@ uint64_t Neuropixels2e::getProbeSerialNumber (int index) return 0ull; } +std::string Neuropixels2e::getProbePartNumber (int index) +{ + try + { + return probeMetadata.at (index).getProbePartNumber(); + } + catch (const std::out_of_range& ex) // filter for out of range + { + LOGE ("Invalid index given requesting probe serial number."); + } + + return ""; +} + +std::string Neuropixels2e::getFlexPartNumber (int index) +{ + try + { + return probeMetadata.at (index).getFlexPartNumber(); + } + catch (const std::out_of_range& ex) // filter for out of range + { + LOGE ("Invalid index given requesting probe serial number."); + } + + return ""; +} + +std::string Neuropixels2e::getFlexVersion (int index) +{ + try + { + return probeMetadata.at (index).getFlexVersion(); + } + catch (const std::out_of_range& ex) // filter for out of range + { + LOGE ("Invalid index given requesting probe serial number."); + } + + return ""; +} + OnixDeviceType Neuropixels2e::getDeviceType() { return OnixDeviceType::NEUROPIXELSV2E; @@ -260,18 +306,18 @@ int Neuropixels2e::configureDevice() rc = serializer->set933I2cRate (400e3); if (rc != ONI_ESUCCESS) throw error_str ("Unable to set I2C rate for " + getName()); - probeSN[0] = getProbeSN (ProbeASelected); - probeSN[1] = getProbeSN (ProbeBSelected); + probeMetadata[0] = readProbeMetadata (serializer.get(), flex.get(), ProbeASelected); + probeMetadata[1] = readProbeMetadata (serializer.get(), flex.get(), ProbeBSelected); setProbeSupply (false); - LOGD ("Probe A SN: ", probeSN[0]); - LOGD ("Probe B SN: ", probeSN[1]); + LOGD ("Probe A SN: ", probeMetadata[0].getProbeSerialNumber()); + LOGD ("Probe B SN: ", probeMetadata[1].getProbeSerialNumber()); - if (probeSN[0] == 0 && probeSN[1] == 0) + if (probeMetadata[0].getProbeSerialNumber() == 0 && probeMetadata[1].getProbeSerialNumber() == 0) { m_numProbes = 0; throw error_str ("No probes were found connected at address " + std::to_string (getDeviceIdx())); } - else if (probeSN[0] != 0 && probeSN[1] != 0) + else if (probeMetadata[0].getProbeSerialNumber() != 0 && probeMetadata[1].getProbeSerialNumber() != 0) { m_numProbes = 2; } @@ -294,11 +340,11 @@ bool Neuropixels2e::updateSettings() { for (int i = 0; i < 2; i++) { - if (probeSN[i] != 0) + if (probeMetadata[i].getProbeSerialNumber() != 0) { if (gainCorrectionFilePath[i] == "None" || gainCorrectionFilePath[i] == "") { - Onix1::showWarningMessageBoxAsync ("Missing File", "Missing gain correction file for probe " + std::to_string (probeSN[i])); + Onix1::showWarningMessageBoxAsync ("Missing File", "Missing gain correction file for probe " + std::to_string (probeMetadata[i].getProbeSerialNumber())); return false; } @@ -306,7 +352,7 @@ bool Neuropixels2e::updateSettings() if (! gainCorrectionFile.existsAsFile()) { - Onix1::showWarningMessageBoxAsync ("Missing File", "The gain correction file \"" + gainCorrectionFilePath[i] + "\" for probe " + std::to_string (probeSN[i]) + " does not exist."); + Onix1::showWarningMessageBoxAsync ("Missing File", "The gain correction file \"" + gainCorrectionFilePath[i] + "\" for probe " + std::to_string (probeMetadata[i].getProbeSerialNumber()) + " does not exist."); return false; } @@ -317,9 +363,9 @@ bool Neuropixels2e::updateSettings() auto gainSN = std::stoull (fileLines[0].toStdString()); - if (gainSN != probeSN[i]) + if (gainSN != probeMetadata[i].getProbeSerialNumber()) { - Onix1::showWarningMessageBoxAsync ("Invalid Serial Number", "Gain correction serial number (" + std::to_string (gainSN) + ") does not match probe serial number (" + std::to_string (probeSN[i]) + ")."); + Onix1::showWarningMessageBoxAsync ("Invalid Serial Number", "Gain correction serial number (" + std::to_string (gainSN) + ") does not match probe serial number (" + std::to_string (probeMetadata[i].getProbeSerialNumber()) + ")."); return false; } @@ -340,7 +386,7 @@ bool Neuropixels2e::updateSettings() if (std::stoi (calibrationValues[0].toStdString()) != j || std::stod (calibrationValues[1].toStdString()) != correctionValue) { - Onix1::showWarningMessageBoxAsync ("File Format Invalid", "Calibration file is incorrectly formatted for probe " + std::to_string (probeSN[i])); + Onix1::showWarningMessageBoxAsync ("File Format Invalid", "Calibration file is incorrectly formatted for probe " + std::to_string (probeMetadata[i].getProbeSerialNumber())); return false; } } @@ -356,15 +402,15 @@ bool Neuropixels2e::updateSettings() for (int i = 0; i < NumberOfProbes; i++) { - if (probeSN[i] != 0) + if (probeMetadata[i].getProbeSerialNumber() != 0) { - selectProbe (i == 0 ? ProbeASelected : ProbeBSelected); + selectProbe (serializer.get(), i == 0 ? ProbeASelected : ProbeBSelected); writeConfiguration (settings[i].get()); configureProbeStreaming(); } } - selectProbe (NoProbeSelected); + selectProbe (serializer.get(), NoProbeSelected); // IMPORTANT! BNO polling thread must be started after this return true; @@ -437,12 +483,12 @@ void Neuropixels2e::setProbeSupply (bool en) { auto gpo10Config = en ? DefaultGPO10Config | GPO10SupplyMask : DefaultGPO10Config; - selectProbe (NoProbeSelected); + selectProbe (serializer.get(), NoProbeSelected); serializer->WriteByte ((uint32_t) (DS90UB9x::DS90UB9xSerializerI2CRegister::GPIO10), gpo10Config); Thread::sleep (20); } -void Neuropixels2e::selectProbe (uint8_t probeSelect) +void Neuropixels2e::selectProbe (I2CRegisterContext* serializer, uint8_t probeSelect) { if (serializer == nullptr) { @@ -463,33 +509,17 @@ void Neuropixels2e::resetProbes() serializer->WriteByte ((uint32_t) (DS90UB9x::DS90UB9xSerializerI2CRegister::GPIO10), gpo10Config); } -uint64_t Neuropixels2e::getProbeSN (uint8_t probeSelect) +NeuropixelsProbeMetadata Neuropixels2e::readProbeMetadata (I2CRegisterContext* serializer, I2CRegisterContext* flex, uint8_t probeSelect) { - if (flex == nullptr) - { - LOGE ("Flex is not initialized for Neuropixels 2.0"); - return 0ull; - } - - selectProbe (probeSelect); - uint64_t probeSN = 0ull; - int errorCode = 0, rc; - for (unsigned int i = 0; i < sizeof (probeSN); i++) - { - oni_reg_addr_t reg_addr = OFFSET_PROBE_SN + i; - oni_reg_val_t val; + if (serializer == nullptr) + throw error_str ("Serializer was not initialized when trying to read Neuropixels probe metadata."); - rc = flex->ReadByte (reg_addr, &val); + if (flex == nullptr) + throw error_str ("Flex was not initialized when trying to read Neuropixels probe metadata."); - if (rc != ONI_ESUCCESS) - return 0ull; + selectProbe (serializer, probeSelect); - if (val <= 0xFF) - { - probeSN |= (((uint64_t) val) << (i * 8)); - } - } - return probeSN; + return NeuropixelsProbeMetadata (flex, OnixDeviceType::NEUROPIXELSV2E); } void Neuropixels2e::startAcquisition() @@ -510,7 +540,7 @@ void Neuropixels2e::addSourceBuffers (OwnedArray& sourceBuffers) if (m_numProbes == 1) { sourceBuffers.add (new DataBuffer (streamInfos.getFirst().getNumChannels(), (int) streamInfos.getFirst().getSampleRate() * bufferSizeInSeconds)); - auto bufferIndex = probeSN[0] != 0 ? 0 : 1; + auto bufferIndex = probeMetadata[0].getProbeSerialNumber() != 0 ? 0 : 1; amplifierBuffer[bufferIndex] = sourceBuffers.getLast(); } else @@ -741,8 +771,10 @@ void Neuropixels2e::setSettings (ProbeSettingsupdateProbeSettings (settings_); } -void Neuropixels2e::defineMetadata (ProbeSettings* settings, int shankCount) +void Neuropixels2e::defineMetadata (ProbeSettings* settings) { + auto shankCount = NeuropixelsV2eValues::numberOfShanks; + settings->probeType = ProbeType::NPX_V2; settings->probeMetadata.name = "Neuropixels 2.0e" + (shankCount == 1) ? " - Single Shank" : " - Quad Shank"; diff --git a/Source/Devices/Neuropixels2e.h b/Source/Devices/Neuropixels2e.h index 1e5f541..3137aac 100644 --- a/Source/Devices/Neuropixels2e.h +++ b/Source/Devices/Neuropixels2e.h @@ -24,8 +24,8 @@ #include "../I2CRegisterContext.h" #include "../NeuropixelsComponents.h" -#include "../OnixDevice.h" #include "DS90UB9x.h" +#include "NeuropixelsProbeMetadata.h" namespace OnixSourcePlugin { @@ -61,6 +61,7 @@ class Neuropixels2e : public INeuropixel& sourceBuffers) override; + std::string createStreamName (int); int getNumProbes() const; @@ -87,7 +88,10 @@ class Neuropixels2e : public INeuropixel selectElectrodeConfiguration (int electrodeConfigurationIndex) override; uint64_t getProbeSerialNumber (int index) override; - void defineMetadata (ProbeSettings*, int); + std::string getProbePartNumber (int index) override; + std::string getFlexPartNumber (int index) override; + std::string getFlexVersion (int index) override; + void defineMetadata (ProbeSettings*) override; void setSettings (ProbeSettings* settings_, int index) override; static OnixDeviceType getDeviceType(); @@ -99,24 +103,25 @@ class Neuropixels2e : public INeuropixel probeSN; + void createDataStream (int n); + + std::array probeMetadata; std::array gainCorrection; std::array gainCorrectionFilePath; - void createDataStream (int n); + NeuropixelsV2Reference getReference (int); + static std::string getShankName (uint32_t shiftRegisterAddress); - uint64_t getProbeSN (uint8_t probeSelect); void configureSerDes(); void setProbeSupply (bool); void resetProbes(); - void selectProbe (uint8_t probeSelect); + static NeuropixelsProbeMetadata readProbeMetadata (I2CRegisterContext* serializer, I2CRegisterContext* flex, uint8_t probeSelect); + static void selectProbe (I2CRegisterContext* serializer, uint8_t probeSelect); + void configureProbeStreaming(); void writeConfiguration (ProbeSettings*); - NeuropixelsV2Reference getReference (int); - static std::string getShankName (uint32_t shiftRegisterAddress); - void selectElectrodesInRange (std::vector& selection, int startIndex, int numberOfElectrodes); void selectElectrodesAcrossShanks (std::vector& selection, int startIndex, int numberOfElectrodes); @@ -227,7 +232,7 @@ class Neuropixels2e : public INeuropixel, AdcsPerProbe> rawToChannel = { { - { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }, // Data Index 9, ADC 0 + { 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 diff --git a/Source/Devices/NeuropixelsProbeMetadata.cpp b/Source/Devices/NeuropixelsProbeMetadata.cpp new file mode 100644 index 0000000..13a66ab --- /dev/null +++ b/Source/Devices/NeuropixelsProbeMetadata.cpp @@ -0,0 +1,187 @@ +/* + ------------------------------------------------------------------ + + 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 . + +*/ + +#include "NeuropixelsProbeMetadata.h" + +using namespace OnixSourcePlugin; + +NeuropixelsProbeMetadata::NeuropixelsProbeMetadata(I2CRegisterContext* flex, OnixDeviceType type) + : deviceType(type) +{ + if (flex == nullptr) + { + throw error_str("Flex is not initialized when trying to read Neuropixels probe metadata."); + } + + if (deviceType != OnixDeviceType::NEUROPIXELSV1E + && deviceType != OnixDeviceType::NEUROPIXELSV1F + && deviceType != OnixDeviceType::NEUROPIXELSV2E) + { + throw error_str("Invalid device type given. Expected the type to be a Neuropixels device."); + } + + try + { + std::vector probeSnBytes; + int rc = flex->readBytes(getProbeSerialNumberOffset(), sizeof(probeSerialNumber), probeSnBytes); + if (rc != ONI_ESUCCESS) + throw error_t(rc); + + for (int i = 0; i < probeSnBytes.size(); i++) + { + if (probeSnBytes[i] <= 0xFF) + { + probeSerialNumber |= (((uint64_t)probeSnBytes[i]) << (i * 8)); + } + } + + const int partNumberLength = 20; + + rc = flex->readString(getProbePartNumberOffset(), partNumberLength, probePartNumber); + if (rc != ONI_ESUCCESS) + throw error_t(rc); + + rc = flex->readString(getFlexPartNumberOffset(), partNumberLength, flexPartNumber); + if (rc != ONI_ESUCCESS) + throw error_t(rc); + + oni_reg_val_t version, revision; + + rc = flex->ReadByte(getFlexVersionOffset(), &version); + if (rc != ONI_ESUCCESS) + throw error_t(rc); + + rc = flex->ReadByte(getFlexRevisionOffset(), &revision); + if (rc != ONI_ESUCCESS) + throw error_t(rc); + + flexVersion = std::to_string(version) + "." + std::to_string(revision); + } + catch (const error_t e) + { + if (e.num() == ONI_EREADFAILURE) + return; + + Onix1::showWarningMessageBoxAsync("Error Reading Probe Metadata", + "Encountered an error while trying to read probe metadata: " + std::string(e.what())); + } +} + +const uint64_t NeuropixelsProbeMetadata::getProbeSerialNumber() const +{ + return probeSerialNumber; +} + +const std::string NeuropixelsProbeMetadata::getProbePartNumber() const +{ + return probePartNumber; +} + +const std::string NeuropixelsProbeMetadata::getFlexPartNumber() const +{ + return flexPartNumber; +} + +const std::string NeuropixelsProbeMetadata::getFlexVersion() const +{ + return flexVersion; +} + +uint32_t NeuropixelsProbeMetadata::getProbeSerialNumberOffset() const +{ + switch (deviceType) + { + case OnixDeviceType::NEUROPIXELSV1E: + case OnixDeviceType::NEUROPIXELSV1F: + return (uint32_t)NeuropixelsV1Offset::PROBE_SN; + + case OnixDeviceType::NEUROPIXELSV2E: + return (uint32_t)NeuropixelsV2Offset::PROBE_SN; + + default: + throw error_str("Invalid device type found in Neuropixels Probe Metadata. Expected a Neuropixels device type."); + } +} + +uint32_t NeuropixelsProbeMetadata::getProbePartNumberOffset() const +{ + switch (deviceType) + { + case OnixDeviceType::NEUROPIXELSV1E: + case OnixDeviceType::NEUROPIXELSV1F: + return (uint32_t)NeuropixelsV1Offset::PROBE_PN; + + case OnixDeviceType::NEUROPIXELSV2E: + return (uint32_t)NeuropixelsV2Offset::PROBE_PN; + + default: + throw error_str("Invalid device type found in Neuropixels Probe Metadata. Expected a Neuropixels device type."); + } +} + +uint32_t NeuropixelsProbeMetadata::getFlexPartNumberOffset() const +{ + switch (deviceType) + { + case OnixDeviceType::NEUROPIXELSV1E: + case OnixDeviceType::NEUROPIXELSV1F: + return (uint32_t)NeuropixelsV1Offset::FLEX_PN; + + case OnixDeviceType::NEUROPIXELSV2E: + return (uint32_t)NeuropixelsV2Offset::FLEX_PN; + + default: + throw error_str("Invalid device type found in Neuropixels Probe Metadata. Expected a Neuropixels device type."); + } +} + +uint32_t NeuropixelsProbeMetadata::getFlexVersionOffset() const +{ + switch (deviceType) + { + case OnixDeviceType::NEUROPIXELSV1E: + case OnixDeviceType::NEUROPIXELSV1F: + return (uint32_t)NeuropixelsV1Offset::FLEX_VERSION; + + case OnixDeviceType::NEUROPIXELSV2E: + return (uint32_t)NeuropixelsV2Offset::FLEX_VERSION; + + default: + throw error_str("Invalid device type found in Neuropixels Probe Metadata. Expected a Neuropixels device type."); + } +} + +uint32_t NeuropixelsProbeMetadata::getFlexRevisionOffset() const +{ + switch (deviceType) + { + case OnixDeviceType::NEUROPIXELSV1E: + case OnixDeviceType::NEUROPIXELSV1F: + return (uint32_t)NeuropixelsV1Offset::FLEX_REVISION; + + case OnixDeviceType::NEUROPIXELSV2E: + return (uint32_t)NeuropixelsV2Offset::FLEX_REVISION; + + default: + throw error_str("Invalid device type found in Neuropixels Probe Metadata. Expected a Neuropixels device type."); + } +} diff --git a/Source/Devices/NeuropixelsProbeMetadata.h b/Source/Devices/NeuropixelsProbeMetadata.h new file mode 100644 index 0000000..fe7873a --- /dev/null +++ b/Source/Devices/NeuropixelsProbeMetadata.h @@ -0,0 +1,73 @@ +/* + ------------------------------------------------------------------ + + 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 "../I2CRegisterContext.h" +#include "../OnixDevice.h" + +namespace OnixSourcePlugin +{ + class NeuropixelsProbeMetadata + { + public: + NeuropixelsProbeMetadata() = default; + NeuropixelsProbeMetadata(I2CRegisterContext* flex, OnixDeviceType type); + + const uint64_t getProbeSerialNumber() const; + const std::string getProbePartNumber() const; + const std::string getFlexPartNumber() const; + const std::string getFlexVersion() const; + + private: + OnixDeviceType deviceType = OnixDeviceType::UNKNOWN; + + std::string probePartNumber = ""; + uint64_t probeSerialNumber = 0ull; + std::string flexPartNumber = ""; + std::string flexVersion = ""; + + uint32_t getProbeSerialNumberOffset() const; + uint32_t getProbePartNumberOffset() const; + uint32_t getFlexPartNumberOffset() const; + uint32_t getFlexVersionOffset() const; + uint32_t getFlexRevisionOffset() const; + + enum class NeuropixelsV1Offset : uint32_t + { + PROBE_SN = 0, + FLEX_VERSION = 10, + FLEX_REVISION = 11, + FLEX_PN = 20, + PROBE_PN = 40, + }; + + enum class NeuropixelsV2Offset : uint32_t + { + PROBE_SN = 0x00, + FLEX_VERSION = 0x10, + FLEX_REVISION = 0x11, + FLEX_PN = 0x20, + PROBE_PN = 0x40, + }; + }; +} diff --git a/Source/Devices/PolledBno055.cpp b/Source/Devices/PolledBno055.cpp index db8cc37..78c3d05 100644 --- a/Source/Devices/PolledBno055.cpp +++ b/Source/Devices/PolledBno055.cpp @@ -33,7 +33,7 @@ PolledBno055::PolledBno055 (std::string name, std::string hubName, const oni_dev std::string port = getPortName (deviceIdx); StreamInfo eulerAngleStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Euler" }), + createStreamName ("Euler"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Euler angle", streamIdentifier, 3, @@ -48,7 +48,7 @@ PolledBno055::PolledBno055 (std::string name, std::string hubName, const oni_dev streamInfos.add (eulerAngleStream); StreamInfo quaternionStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Quaternion" }), + createStreamName ("Quaternion"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Quaternion", streamIdentifier, 4, @@ -63,7 +63,7 @@ PolledBno055::PolledBno055 (std::string name, std::string hubName, const oni_dev streamInfos.add (quaternionStream); StreamInfo accelerationStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Acceleration" }), + createStreamName ("Acceleration"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Acceleration", streamIdentifier, 3, @@ -78,7 +78,7 @@ PolledBno055::PolledBno055 (std::string name, std::string hubName, const oni_dev streamInfos.add (accelerationStream); StreamInfo gravityStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Gravity" }), + createStreamName ("Gravity"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Gravity", streamIdentifier, 3, @@ -93,7 +93,7 @@ PolledBno055::PolledBno055 (std::string name, std::string hubName, const oni_dev streamInfos.add (gravityStream); StreamInfo temperatureStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Temperature" }), + createStreamName ("Temperature"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Temperature", streamIdentifier, 1, @@ -107,7 +107,7 @@ PolledBno055::PolledBno055 (std::string name, std::string hubName, const oni_dev streamInfos.add (temperatureStream); StreamInfo calibrationStatusStream = StreamInfo ( - OnixDevice::createStreamName ({ port, getHubName(), getName(), "Calibration" }), + createStreamName ("Calibration"), "Bosch Bno055 9-axis inertial measurement unit (IMU) Calibration status", streamIdentifier, 4, diff --git a/Source/Formats/ProbeInterface.h b/Source/Formats/ProbeInterface.h index 6c65bd2..ef85ba2 100644 --- a/Source/Formats/ProbeInterface.h +++ b/Source/Formats/ProbeInterface.h @@ -29,6 +29,14 @@ namespace OnixSourcePlugin class ProbeInterfaceJson { public: + static constexpr char* FileExtension = ".json"; + + /** Given a directory and a file name, with no extension, return a filepath that follows the format: /.json*/ + static File createFileName (File recordingDirectory, std::string name) + { + return File (recordingDirectory.getFullPathName() + File::getSeparatorString() + String (name) + String (ProbeInterfaceJson::FileExtension)); + } + template static bool writeProbeSettingsToJson (File& file, ProbeSettings* settings) { @@ -111,6 +119,11 @@ class ProbeInterfaceJson output.writeAsJSON (f, options); + auto& result = f.getStatus(); + + if (result.failed()) + throw error_str ("Failed to open the file '" + file.getFullPathName().toStdString() + "' for writing. Reason: " + result.getErrorMessage().toStdString()); + return true; } diff --git a/Source/I2CRegisterContext.cpp b/Source/I2CRegisterContext.cpp index ab70653..43cdf85 100644 --- a/Source/I2CRegisterContext.cpp +++ b/Source/I2CRegisterContext.cpp @@ -30,6 +30,11 @@ I2CRegisterContext::I2CRegisterContext (uint32_t address_, const oni_dev_idx_t d i2cContext = ctx_; } +oni_dev_idx_t I2CRegisterContext::getDeviceIndex() const +{ + return deviceIndex; +} + int I2CRegisterContext::WriteByte (uint32_t address, uint32_t value, bool sixteenBitAddress) { uint32_t registerAddress = (address << 7) | (i2cAddress & 0x7F); @@ -43,6 +48,25 @@ int I2CRegisterContext::ReadByte (uint32_t address, oni_reg_val_t* value, bool s return ReadWord (address, 1, value, sixteenBitAddress); } +int I2CRegisterContext::readBytes (uint32_t address, int count, std::vector& value, bool sixteenBitAddress) +{ + value.clear(); + + oni_reg_val_t val; + int rc = 0; + + for (uint16_t i = 0; i < count; i++) + { + rc = ReadByte (address + i, &val, sixteenBitAddress); + if (rc != ONI_ESUCCESS) + return rc; + + value.push_back (val); + } + + return rc; +} + int I2CRegisterContext::ReadWord (uint32_t address, uint32_t numBytes, uint32_t* value, bool sixteenBitAddress) { if (numBytes < 1 || numBytes > 4) @@ -58,6 +82,26 @@ int I2CRegisterContext::ReadWord (uint32_t address, uint32_t numBytes, uint32_t* return i2cContext->readRegister (deviceIndex, registerAddress, value); } +int I2CRegisterContext::readString (uint32_t address, int count, std::string& str, bool sixteenBitAddress) +{ + std::vector data; + str = ""; + + int rc = readBytes (address, count, data, sixteenBitAddress); + + if (rc != ONI_ESUCCESS) + { + Onix1::showWarningMessageBoxAsync ("Error Reading String", "Could not read the string at address " + std::to_string (address)); + return rc; + } + + auto it = std::find (data.begin(), data.end(), 0u); + size_t len = (it != data.end()) ? (size_t) std::distance (data.begin(), it) : data.size(); + str = std::string (data.begin(), data.begin() + len); + + return ONI_ESUCCESS; +} + int I2CRegisterContext::set933I2cRate (double rate) { auto sclTimes = (uint32_t) (std::round (1.0 / (100e-9 * rate))); diff --git a/Source/I2CRegisterContext.h b/Source/I2CRegisterContext.h index 9ac19e5..c3d3720 100644 --- a/Source/I2CRegisterContext.h +++ b/Source/I2CRegisterContext.h @@ -37,18 +37,23 @@ class I2CRegisterContext I2CRegisterContext (uint32_t address, const oni_dev_idx_t, std::shared_ptr); int WriteByte (uint32_t address, uint32_t value, bool sixteenBitAddress = false); - int WriteWord (uint32_t address, uint32_t value, uint32_t numBytes, bool sixteenBitAddress = false); + int ReadByte (uint32_t address, oni_reg_val_t* value, bool sixteenBitAddress = false); + int readBytes (uint32_t address, int count, std::vector& value, bool sixteenBitAddress = false); int ReadWord (uint32_t address, uint32_t numBytes, uint32_t* value, bool sixteenBitAddress = false); + int readString (uint32_t address, int count, std::string& str, bool sixteenBitAddress = false); + int set933I2cRate (double); + oni_dev_idx_t getDeviceIndex() const; + protected: std::shared_ptr i2cContext; private: - const oni_dev_idx_t deviceIndex; + const oni_dev_idx_t deviceIndex; const uint32_t i2cAddress; JUCE_LEAK_DETECTOR (I2CRegisterContext); diff --git a/Source/NeuropixelsComponents.h b/Source/NeuropixelsComponents.h index e02d5c7..71ebb79 100644 --- a/Source/NeuropixelsComponents.h +++ b/Source/NeuropixelsComponents.h @@ -346,15 +346,14 @@ std::vector toBitReversedBytes (std::bitset bits) return bytes; } +static const std::string ProbeString = "Probe"; + 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>()); @@ -367,13 +366,41 @@ class INeuropixel std::vector>> settings; - virtual void setSettings (ProbeSettings* settings_, int index) { return; } + virtual void setSettings (ProbeSettings* settings_, int index) = 0; + virtual void defineMetadata (ProbeSettings* settings) = 0; + virtual uint64_t getProbeSerialNumber (int index) = 0; + virtual std::string getProbePartNumber (int index) = 0; + virtual std::string getFlexPartNumber (int index) = 0; + virtual std::string getFlexVersion (int index) = 0; + virtual std::vector selectElectrodeConfiguration (int electrodeConfigurationIndex) = 0; - virtual void defineMetadata (ProbeSettings* settings) { return; } + bool saveProbeInterfaceFile (File recordingDirectory, std::string streamName, int probeIndex = 0) + { + if (streamName != "") + { + File filename = ProbeInterfaceJson::createFileName (recordingDirectory, streamName); - virtual uint64_t getProbeSerialNumber (int index) { return 0; } + LOGC ("Saving " + filename.getFullPathName()); - virtual std::vector selectElectrodeConfiguration (int electrodeConfigurationIndex) { return {}; } + try + { + ProbeInterfaceJson::writeProbeSettingsToJson (filename, settings[probeIndex].get()); + } + catch (const error_str& e) + { + Onix1::showWarningMessageBoxAsync ("Unable to Save Probe JSON File", e.what()); + return false; + } + } + else + { + Onix1::showWarningMessageBoxAsync ("No Valid Stream", + "Could not find a valid data stream when writing the Probe Interface file."); + return false; + } + + return true; + } }; static constexpr int shankConfigurationBitCount = 968; diff --git a/Source/OnixDevice.cpp b/Source/OnixDevice.cpp index fd1f662..2ca2668 100644 --- a/Source/OnixDevice.cpp +++ b/Source/OnixDevice.cpp @@ -72,6 +72,22 @@ std::string OnixDevice::createStreamName (std::vector names) return streamName; } +std::string OnixDevice::createStreamName (std::string suffix, bool usePort) +{ + std::vector names; + + if (usePort) + names.push_back (getPortName (getDeviceIdx())); + + names.push_back (getHubName()); + names.push_back (getName()); + + if (suffix != "") + names.push_back (suffix); + + return createStreamName (names); +} + bool OnixDevice::isValidPassthroughIndex (oni_dev_idx_t passthroughIndex) { return passthroughIndex == (uint32_t) PassthroughIndex::A || passthroughIndex == (uint32_t) PassthroughIndex::B; @@ -126,11 +142,14 @@ std::string OnixDevice::getStreamIdentifier() { std::string streamIdentifier = "onix"; - // Insert the headstage or breakout board if (getHubName() == NEUROPIXELSV1F_HEADSTAGE_NAME) { streamIdentifier += ".npx1f"; } + else if (getHubName() == NEUROPIXELSV1E_HEADSTAGE_NAME) + { + streamIdentifier += ".npx1e"; + } else if (getHubName() == NEUROPIXELSV2E_HEADSTAGE_NAME) { streamIdentifier += ".npx2e"; @@ -141,10 +160,10 @@ std::string OnixDevice::getStreamIdentifier() } else { + LOGD ("Unknown hub name found when creating the stream identifier: " + getHubName()); streamIdentifier += ".headstage"; } - // Insert the device if (getDeviceType() == OnixDeviceType::ANALOGIO) { streamIdentifier += ".analogio"; @@ -169,12 +188,17 @@ std::string OnixDevice::getStreamIdentifier() { streamIdentifier += ".npx1f"; } + else if (getDeviceType() == OnixDeviceType::NEUROPIXELSV1E) + { + streamIdentifier += ".npx1e"; + } else if (getDeviceType() == OnixDeviceType::NEUROPIXELSV2E) { streamIdentifier += ".npx2e"; } else { + LOGD ("Unknown device type found when creating the stream identifier."); streamIdentifier += ".device"; } diff --git a/Source/OnixDevice.h b/Source/OnixDevice.h index 1ff9dcf..4b840f0 100644 --- a/Source/OnixDevice.h +++ b/Source/OnixDevice.h @@ -54,10 +54,9 @@ enum class OnixDeviceType HARPSYNCINPUT, ANALOGIO, DIGITALIO, + UNKNOWN }; -static const std::string ProbeString = "Probe"; - struct StreamInfo { public: @@ -184,11 +183,13 @@ class OnixDevice virtual void setEnabled (bool newState) { enabled = newState; } oni_dev_idx_t getDeviceIdx (bool getPassthroughIndex = false); - /** Creates a stream name using the provided inputs, returning a string following the pattern: name[0]-name[1]-name[2]-etc., with all spaces removed */ - static std::string createStreamName (std::vector names); - Array streamInfos; + /** Static method that creates a stream name using the provided inputs, returning a string following the pattern: name[0]-name[1]-name[2]-etc., with all spaces removed */ + static std::string createStreamName (std::vector names); + /** Instance method that generates a stream with the hub name and device name. */ + std::string createStreamName (std::string suffix = "", bool usePort = true); + const int bufferSizeInSeconds = 10; static constexpr int HubAddressBreakoutBoard = 0; diff --git a/Source/OnixSource.cpp b/Source/OnixSource.cpp index 76946ba..34b90d1 100644 --- a/Source/OnixSource.cpp +++ b/Source/OnixSource.cpp @@ -837,7 +837,7 @@ void OnixSource::updateSettings (OwnedArray* continuousChanne deviceInfos->add (new DeviceInfo (deviceSettings)); DataStream::Settings dataStreamSettings { - OnixDevice::createStreamName ({ OnixDevice::getPortName (source->getDeviceIdx()), source->getHubName(), source->getName() }), + source->createStreamName(), "Continuous data from a Bno055 9-axis IMU", source->getStreamIdentifier(), source->streamInfos[0].getSampleRate(), @@ -901,7 +901,7 @@ void OnixSource::updateSettings (OwnedArray* continuousChanne deviceInfos->add (new DeviceInfo (digitalIODeviceSettings)); DataStream::Settings dataStreamSettings { - OnixDevice::createStreamName ({ source->getHubName(), source->getName() }), + source->createStreamName ("", false), "Digital inputs and buttons", source->getStreamIdentifier(), source->streamInfos[0].getSampleRate(), @@ -1204,6 +1204,94 @@ bool OnixSource::stopAcquisition() return true; } +bool OnixSource::dataStreamExists(std::string streamName, Array dataStreams) +{ + for (const auto& stream : dataStreams) + { + if (stream->getName().contains(streamName)) + { + return true; + } + } + + return false; +} + +void OnixSource::startRecording() +{ + OnixDeviceVector devicesWithProbeInterface; + + for (const auto& device : getEnabledDataSources()) + { + if (device->getDeviceType() == OnixDeviceType::NEUROPIXELSV1E + || device->getDeviceType() == OnixDeviceType::NEUROPIXELSV1F + || device->getDeviceType() == OnixDeviceType::NEUROPIXELSV2E) + { + devicesWithProbeInterface.emplace_back(device); + } + } + + if (devicesWithProbeInterface.empty()) + return; + + File recPath = CoreServices::getRecordingParentDirectory(); + + int recordNodeId = CoreServices::getAvailableRecordNodeIds().getFirst(); + int experimentNumber = CoreServices::RecordNode::getExperimentNumber(recordNodeId); + + auto dir = File( + recPath.getFullPathName() + File::getSeparatorString() + + CoreServices::getRecordingDirectoryName() + File::getSeparatorString() + + PLUGIN_NAME + " " + String(sn->getNodeId()) + File::getSeparatorString() + + "experiment" + String(experimentNumber)); + + if (!dir.exists()) + { + auto result = dir.createDirectory(); + + if (result.failed()) + { + Onix1::showWarningMessageBoxAsync("Unable to Create ONIX Source Folder", + "The plugin was unable to create a recording directory at '" + dir.getFullPathName().toStdString() + "'. No Probe Interface files will be saved for this recording, please stop recording and determine why the directory could not be created."); + return; + } + } + + for (const auto& device : devicesWithProbeInterface) + { + if (device->getDeviceType() == OnixDeviceType::NEUROPIXELSV1E || device->getDeviceType() == OnixDeviceType::NEUROPIXELSV1F) + { + auto npx = std::static_pointer_cast(device); + auto streamName = npx->createStreamName(); + + auto streamExists = dataStreamExists(streamName, sn->getDataStreams()); + + if (!streamExists) + return; + + if (!npx->saveProbeInterfaceFile(dir, streamName)) + return; + } + else if (device->getDeviceType() == OnixDeviceType::NEUROPIXELSV2E) + { + auto npx = std::static_pointer_cast(device); + + for (int i = 0; i < npx->getNumProbes(); i++) + { + auto streamName = npx->createStreamName(i); + + auto streamExists = dataStreamExists(streamName, sn->getDataStreams()); + + if (!streamExists) + return; + + if (!npx->saveProbeInterfaceFile(dir, streamName, i)) + return; + } + } + } +} + bool OnixSource::updateBuffer() { for (const auto& source : enabledSources) diff --git a/Source/OnixSource.h b/Source/OnixSource.h index 9223cd6..c1a4ba9 100644 --- a/Source/OnixSource.h +++ b/Source/OnixSource.h @@ -25,11 +25,14 @@ #include #include "Devices/PortController.h" +#include "Formats/ProbeInterface.h" #include "FrameReader.h" #include "Onix1.h" #include "OnixDevice.h" #include "OnixSourceEditor.h" +#define PLUGIN_NAME "ONIX Source" + namespace OnixSourcePlugin { /** @@ -72,6 +75,8 @@ class OnixSource : public DataThread /** Stops data transfer.*/ bool stopAcquisition() override; + void startRecording(); + void updateDiscoveryParameters (PortName port, DiscoveryParameters parameters); /** Takes a string from the editor. Can be an empty string to allow for automated discovery */ @@ -175,6 +180,8 @@ class OnixSource : public DataThread /** This method is expected to be called in a separate thread, and waits for acquisition to stop before gracefully disconnecting all devices */ static void disconnectDevicesAfterAcquisition (OnixSourceEditor* editor); + static bool dataStreamExists (std::string streamName, Array dataStreams); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnixSource); }; } // namespace OnixSourcePlugin diff --git a/Source/OnixSourceCanvas.cpp b/Source/OnixSourceCanvas.cpp index b011b72..4087595 100644 --- a/Source/OnixSourceCanvas.cpp +++ b/Source/OnixSourceCanvas.cpp @@ -100,7 +100,7 @@ void OnixSourceCanvas::addHub (std::string hubName, int offset) tab = addTopLevelTab (getTopLevelTabName (port, hubName), (int) port); - devices.emplace_back (std::make_shared (ProbeString, hubName, passthroughIndex, context)); + devices.emplace_back (std::make_shared (OnixDevice::TypeString.at (OnixDeviceType::NEUROPIXELSV2E), hubName, passthroughIndex, context)); devices.emplace_back (std::make_shared (OnixDevice::TypeString.at (OnixDeviceType::POLLEDBNO), hubName, passthroughIndex, context)); } diff --git a/Source/OpenEphysLib.cpp b/Source/OpenEphysLib.cpp index ad6e41a..a7784a1 100644 --- a/Source/OpenEphysLib.cpp +++ b/Source/OpenEphysLib.cpp @@ -44,10 +44,10 @@ extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info) info->apiVersion = PLUGIN_API_VER; //Name of the Library, used only for information - info->name = "ONIX Source"; + info->name = PLUGIN_NAME; //Version of the library, used only for information - info->libVersion = "0.2.1"; + info->libVersion = "0.3.0"; info->numPlugins = NUM_PLUGINS; } diff --git a/Source/UI/NeuropixelsV1Interface.cpp b/Source/UI/NeuropixelsV1Interface.cpp index 0146932..50b6e86 100644 --- a/Source/UI/NeuropixelsV1Interface.cpp +++ b/Source/UI/NeuropixelsV1Interface.cpp @@ -74,7 +74,7 @@ NeuropixelsV1Interface::NeuropixelsV1Interface (std::shared_ptr d, infoLabel = std::make_unique