From 6fa65a19a5a6fa3d8aefdf3044506f1d62de17ca Mon Sep 17 00:00:00 2001 From: glopesdev Date: Wed, 24 Jan 2024 15:58:47 +0000 Subject: [PATCH 1/9] Add support for NeuropixelsV2 headstage --- .../OpenEphys.Onix/ConfigureNeuropixelsV2.cs | 215 ++++++++++++++++++ .../ConfigureNeuropixelsV2Headstage.cs | 41 ++++ .../OpenEphys.Onix/NeuropixelsV2Data.cs | 59 +++++ .../OpenEphys.Onix/NeuropixelsV2DataFrame.cs | 104 +++++++++ 4 files changed, 419 insertions(+) create mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs new file mode 100644 index 00000000..a2b8fade --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs @@ -0,0 +1,215 @@ +using System; +using System.ComponentModel; +using System.Reactive.Disposables; + +namespace OpenEphys.Onix +{ + public class ConfigureNeuropixelsV2 : SingleDeviceFactory + { + public ConfigureNeuropixelsV2() + : base(typeof(NeuropixelsV2)) + { + } + + [Category(ConfigurationCategory)] + [Description("Specifies whether the NeuropixelsV2 device is enabled.")] + public bool Enable { get; set; } = true; + + public override IObservable Process(IObservable source) + { + var enable = Enable; + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice(context => + { + // configure device via the DS90UB9x deserializer device + var device = context.GetDeviceContext(deviceAddress, DS90UB9x.ID); + device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); + + // configure deserializer aliases and serializer power supply + ConfigureDeserializer(device); + var serializer = new I2CRegisterContext(device, DS90UB9x.SER_ADDR); + var gpo10Config = EnableProbeSupply(serializer); + + // read probe metadata + var probeAMetadata = ReadProbeMetadata(serializer, NeuropixelsV2.ProbeASelected); + var probeBMetadata = ReadProbeMetadata(serializer, NeuropixelsV2.ProbeBSelected); + + // issue full reset to both probes + ResetProbes(serializer, gpo10Config); + var probeControl = new I2CRegisterContext(device, NeuropixelsV2.ProbeAddress); + + // configure probe A streaming + if (probeAMetadata.ProbeSN != null) + { + SelectProbe(serializer, NeuropixelsV2.ProbeASelected); + ConfigureProbeStreaming(probeControl); + } + + // configure probe B streaming + if (probeBMetadata.ProbeSN != null) + { + SelectProbe(serializer, NeuropixelsV2.ProbeBSelected); + ConfigureProbeStreaming(probeControl); + } + + var disposable = DeviceManager.RegisterDevice(deviceName, device, DeviceType); + var shutdown = Disposable.Create(() => + { + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, NeuropixelsV2.DefaultGPO10Config); + SelectProbe(serializer, NeuropixelsV2.NoProbeSelected); + }); + return new CompositeDisposable( + shutdown, + disposable); + }); + } + + static void ConfigureDeserializer(DeviceContext device) + { + // configure deserializer trigger mode + device.WriteRegister(DS90UB9x.TRIGGEROFF, 0); + device.WriteRegister(DS90UB9x.TRIGGER, (uint)DS90UB9xTriggerMode.Continuous); + device.WriteRegister(DS90UB9x.SYNCBITS, 0); + device.WriteRegister(DS90UB9x.DATAGATE, (uint)DS90UB9xDataGate.Disabled); + device.WriteRegister(DS90UB9x.MARK, (uint)DS90UB9xMarkMode.Disabled); + + // configure two 4-bit magic word-triggered streams, one for each probe + device.WriteRegister(DS90UB9x.READSZ, 0x0010_0009); // 16 frames/superframe, 8x 12-bit words + magic bits + device.WriteRegister(DS90UB9x.MAGIC_MASK, 0xC000003F); // Enable inverse, wait for non-inverse, 14-bit magic word + device.WriteRegister(DS90UB9x.MAGIC, 0b0000_0000_0010_1110); // Super-frame sync word + device.WriteRegister(DS90UB9x.MAGIC_WAIT, 0); + device.WriteRegister(DS90UB9x.DATAMODE, 0b0010_0000_0000_0000_0000_0010_1011_0101); + device.WriteRegister(DS90UB9x.DATALINES0, 0xFFFFF8A6); // NP A + device.WriteRegister(DS90UB9x.DATALINES1, 0xFFFFF97B); // NP B + + // configure deserializer I2C aliases + var deserializer = new I2CRegisterContext(device, DS90UB9x.DES_ADDR); + uint coaxMode = 0x4 + (uint)DS90UB9xMode.Raw12BitHighFrequency; // 0x4 maintains coax mode + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.PortMode, coaxMode); + + uint alias = NeuropixelsV2.ProbeAddress << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID1, alias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias1, alias); + + alias = NeuropixelsV2.FlexEEPROMAddress << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID2, alias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias2, alias); + } + + static uint EnableProbeSupply(I2CRegisterContext serializer) + { + var gpo10Config = NeuropixelsV2.DefaultGPO10Config | NeuropixelsV2.GPO10SupplyMask; + SelectProbe(serializer, NeuropixelsV2.NoProbeSelected); + + // turn on analog supply and wait for boot + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); + System.Threading.Thread.Sleep(20); + return gpo10Config; + } + + NeuropixelsV2Metadata ReadProbeMetadata(I2CRegisterContext serializer, byte probeSelect) + { + SelectProbe(serializer, probeSelect); + return new NeuropixelsV2Metadata(serializer); + } + + static void SelectProbe(I2CRegisterContext serializer, byte probeSelect) + { + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, probeSelect); + System.Threading.Thread.Sleep(20); + } + + static void ResetProbes(I2CRegisterContext serializer, uint gpo10Config) + { + gpo10Config &= ~NeuropixelsV2.GPO10ResetMask; + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); + gpo10Config |= NeuropixelsV2.GPO10ResetMask; + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); + } + + static void ConfigureProbeStreaming(I2CRegisterContext i2cNP) + { + // Write super sync bits into ASIC + i2cNP.WriteByte(0x15, 0b00011000); + i2cNP.WriteByte(0x14, 0b01100001); + i2cNP.WriteByte(0x13, 0b10000110); + i2cNP.WriteByte(0x12, 0b00011000); + i2cNP.WriteByte(0x11, 0b01100001); + i2cNP.WriteByte(0x10, 0b10000110); + i2cNP.WriteByte(0x0F, 0b00011000); + i2cNP.WriteByte(0x0E, 0b01100001); + i2cNP.WriteByte(0x0D, 0b10000110); + i2cNP.WriteByte(0x0C, 0b00011000); + i2cNP.WriteByte(0x0B, 0b01100001); + i2cNP.WriteByte(0x0A, 0b10111001); + + // Activate recording mode on NP + i2cNP.WriteByte(0, 0b0100_0000); + } + } + + static class NeuropixelsV2 + { + public const int ProbeAddress = 0x10; + public const int FlexEEPROMAddress = 0x50; + + public const uint GPO10SupplyMask = 1 << 3; // Used to turn on VDDA analog supply + public const uint GPO10ResetMask = 1 << 7; // Used to issue full reset commands to probes + public const byte DefaultGPO10Config = 0b0001_0001; // NPs in reset, VDDA not enabled + public const byte NoProbeSelected = 0b0001_0001; // No probes selected + public const byte ProbeASelected = 0b0001_1001; // TODO: Changes in Rev. B of headstage + public const byte ProbeBSelected = 0b1001_1001; + + public const int FramesPerSuperFrame = 16; + public const int ADCsPerProbe = 24; + public const int ChannelCount = 384; + public const int FrameWords = 36; // TRASH TRASH TRASH 0 ADC0 ADC8 ADC16 0 ADC1 ADC9 ADC17 0 ... ADC7 ADC15 ADC23 0 + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(NeuropixelsV2)) + { + } + } + } + + class NeuropixelsV2Metadata + { + const uint OFFSET_ID = 0; + const uint OFFSET_VERSION = 10; + const uint OFFSET_REVISION = 11; + const uint OFFSET_FLEXPN = 20; + const uint OFFSET_PROBEPN = 40; + + public NeuropixelsV2Metadata(I2CRegisterContext serializer) + { + var flexI2C = new I2CRegisterContext(serializer, NeuropixelsV2.FlexEEPROMAddress); + try + { + var sn = flexI2C.ReadBytes(OFFSET_ID, 8); + ProbeSN = BitConverter.ToUInt64(sn, 0); + Version = string.Format("{0}.{1}", flexI2C.ReadByte(OFFSET_VERSION), flexI2C.ReadByte(OFFSET_REVISION)); + PartNumber = flexI2C.ReadString(OFFSET_FLEXPN, 20); + ProbePartNumber = flexI2C.ReadString(OFFSET_PROBEPN, 20); + } + catch (oni.ONIException ex) + { + const int FailureToReadRegister = -5; + if (ex.Number != FailureToReadRegister) + { + throw; + } + } + } + + public ulong? ProbeSN { get; } + + public string Version { get; } + + public string PartNumber { get; } + + public string ProbePartNumber { get; } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs new file mode 100644 index 00000000..e82bcfb7 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.ComponentModel; + +namespace OpenEphys.Onix +{ + public class ConfigureNeuropixelsV2Headstage : HubDeviceFactory + { + PortName port; + readonly ConfigureFmcLinkController LinkController = new(); + + public ConfigureNeuropixelsV2Headstage() + { + Port = PortName.PortA; + LinkController.HubConfiguration = HubConfiguration.Standard; + LinkController.MinVoltage = 5.0; + LinkController.MaxVoltage = 7.0; + } + + [Category(ConfigurationCategory)] + [TypeConverter(typeof(HubDeviceConverter))] + public ConfigureNeuropixelsV2 NeuropixelsV2 { get; set; } = new(); + + public PortName Port + { + get { return port; } + set + { + port = value; + var offset = (uint)port << 8; + LinkController.DeviceAddress = (uint)port; + NeuropixelsV2.DeviceAddress = offset + 0; + } + } + + internal override IEnumerable GetDevices() + { + yield return LinkController; + yield return NeuropixelsV2; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs new file mode 100644 index 00000000..fdf6c149 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs @@ -0,0 +1,59 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using Bonsai; +using OpenCV.Net; + +namespace OpenEphys.Onix +{ + public class NeuropixelsV2Data : Source + { + [TypeConverter(typeof(NeuropixelsV2.NameConverter))] + public string DeviceName { get; set; } + + public int BufferSize { get; set; } = 30; + + public unsafe override IObservable Generate() + { + var bufferSize = BufferSize; + return Observable.Using( + () => DeviceManager.ReserveDevice(DeviceName), + disposable => disposable.Subject.SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2)); + return deviceInfo.Context.FrameReceived + .Where(frame => frame.DeviceAddress == device.Address) + .GroupBy(frame => NeuropixelsV2DataFrame.GetProbeIndex(frame)) + .SelectMany(probe => Observable.Create(observer => + { + var sampleIndex = 0; + var amplifierBuffer = new ushort[NeuropixelsV2.ChannelCount, bufferSize]; + var hubClockBuffer = new ulong[bufferSize]; + var clockBuffer = new ulong[bufferSize]; + + var frameObserver = Observer.Create( + frame => + { + var payload = (NeuropixelsV2Payload*)frame.Data.ToPointer(); + NeuropixelsV2DataFrame.CopyAmplifierBuffer(payload->AmplifierData, amplifierBuffer, sampleIndex); + hubClockBuffer[sampleIndex] = BitHelper.SwapEndian(payload->HubClock); + clockBuffer[sampleIndex] = frame.Clock; + if (++sampleIndex >= bufferSize) + { + var amplifierData = Mat.FromArray(amplifierBuffer); + observer.OnNext(new NeuropixelsV2DataFrame(clockBuffer, hubClockBuffer, probe.Key, amplifierData)); + hubClockBuffer = new ulong[bufferSize]; + clockBuffer = new ulong[bufferSize]; + sampleIndex = 0; + } + }, + observer.OnError, + observer.OnCompleted); + return probe.SubscribeSafe(frameObserver); + })); + })); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs new file mode 100644 index 00000000..2ed76201 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs @@ -0,0 +1,104 @@ +using System.Runtime.InteropServices; +using OpenCV.Net; + +namespace OpenEphys.Onix +{ + public class NeuropixelsV2DataFrame + { + public NeuropixelsV2DataFrame(ulong[] clock, ulong[] hubClock, int probeIndex, Mat amplifierData) + { + Clock = clock; + HubClock = hubClock; + ProbeIndex = probeIndex; + AmplifierData = amplifierData; + } + + public ulong[] Clock { get; } + + public ulong[] HubClock { get; } + + public int ProbeIndex { get; } + + public Mat AmplifierData { get; } + + internal static unsafe ushort GetProbeIndex(oni.Frame frame) + { + var data = (NeuropixelsV2Payload*)frame.Data.ToPointer(); + return data->ProbeIndex; + } + + internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, ushort[,] amplifierBuffer, int index) + { + // Loop over 16 "frames" within each "super-frame" + for (int i = 0; i < NeuropixelsV2.FramesPerSuperFrame; i++) + { + // The period of ADC data within data array is 36 words + var adcDataOffset = i * NeuropixelsV2.FrameWords; + + for (int k = 0; k < NeuropixelsV2.ADCsPerProbe; k++) + { + amplifierBuffer[RawToChannel[k, i], index] = amplifierData[ADCIndices[k] + adcDataOffset]; + } + } + } + + // ADC & frame-index to channel mapping + // First dimension: data index + // Second dimension: frame index within super frame + + static readonly int[] 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 readonly int[,] 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 + + }; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + unsafe struct NeuropixelsV2Payload + { + public ulong HubClock; + public ushort ProbeIndex; + public ulong Reserved; + public fixed ushort AmplifierData[NeuropixelsV2.ChannelCount]; + } +} From 543aaa0a22a065f381f4696e5e28b72a70bbe37f Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 25 Jan 2024 10:33:15 +0000 Subject: [PATCH 2/9] Set passthrough mode and serializer device address --- .../OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs index e82bcfb7..84c11b9b 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs @@ -11,7 +11,7 @@ public class ConfigureNeuropixelsV2Headstage : HubDeviceFactory public ConfigureNeuropixelsV2Headstage() { Port = PortName.PortA; - LinkController.HubConfiguration = HubConfiguration.Standard; + LinkController.HubConfiguration = HubConfiguration.Passthrough; LinkController.MinVoltage = 5.0; LinkController.MaxVoltage = 7.0; } @@ -26,9 +26,9 @@ public PortName Port set { port = value; - var offset = (uint)port << 8; + var serializerAddress = ((uint)port - 1) + 8; LinkController.DeviceAddress = (uint)port; - NeuropixelsV2.DeviceAddress = offset + 0; + NeuropixelsV2.DeviceAddress = serializerAddress; } } From 5247f4eb699b1532b6238af81fe50a23f487425c Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 25 Jan 2024 16:35:51 +0000 Subject: [PATCH 3/9] Refactor common probe metadata class --- .../OpenEphys.Onix/ConfigureNeuropixelsV2.cs | 38 ---------------- .../OpenEphys.Onix/NeuropixelsV2Metadata.cs | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 38 deletions(-) create mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Metadata.cs diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs index a2b8fade..868f1773 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs @@ -174,42 +174,4 @@ public NameConverter() } } } - - class NeuropixelsV2Metadata - { - const uint OFFSET_ID = 0; - const uint OFFSET_VERSION = 10; - const uint OFFSET_REVISION = 11; - const uint OFFSET_FLEXPN = 20; - const uint OFFSET_PROBEPN = 40; - - public NeuropixelsV2Metadata(I2CRegisterContext serializer) - { - var flexI2C = new I2CRegisterContext(serializer, NeuropixelsV2.FlexEEPROMAddress); - try - { - var sn = flexI2C.ReadBytes(OFFSET_ID, 8); - ProbeSN = BitConverter.ToUInt64(sn, 0); - Version = string.Format("{0}.{1}", flexI2C.ReadByte(OFFSET_VERSION), flexI2C.ReadByte(OFFSET_REVISION)); - PartNumber = flexI2C.ReadString(OFFSET_FLEXPN, 20); - ProbePartNumber = flexI2C.ReadString(OFFSET_PROBEPN, 20); - } - catch (oni.ONIException ex) - { - const int FailureToReadRegister = -5; - if (ex.Number != FailureToReadRegister) - { - throw; - } - } - } - - public ulong? ProbeSN { get; } - - public string Version { get; } - - public string PartNumber { get; } - - public string ProbePartNumber { get; } - } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Metadata.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Metadata.cs new file mode 100644 index 00000000..c465b85c --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Metadata.cs @@ -0,0 +1,45 @@ +using System; + +namespace OpenEphys.Onix +{ + class NeuropixelsV2Metadata + { + const uint OFFSET_ID = 0; + const uint OFFSET_VERSION = 10; + const uint OFFSET_REVISION = 11; + const uint OFFSET_FLEXPN = 20; + const uint OFFSET_PROBEPN = 40; + + public NeuropixelsV2Metadata(I2CRegisterContext serializer) + { + var flexI2C = new I2CRegisterContext(serializer, NeuropixelsV2.FlexEEPROMAddress); + try + { + var sn = flexI2C.ReadBytes(OFFSET_ID, 8); + ProbeSN = BitConverter.ToUInt64(sn, 0); + Version = flexI2C.ReadByte(OFFSET_VERSION); + Revision = flexI2C.ReadByte(OFFSET_REVISION); + PartNumber = flexI2C.ReadString(OFFSET_FLEXPN, 20); + ProbePartNumber = flexI2C.ReadString(OFFSET_PROBEPN, 20); + } + catch (oni.ONIException ex) + { + const int FailureToReadRegister = -5; + if (ex.Number != FailureToReadRegister) + { + throw; + } + } + } + + public ulong? ProbeSN { get; } + + public byte Version { get; } + + public byte Revision { get; } + + public string PartNumber { get; } + + public string ProbePartNumber { get; } + } +} From 69c0d3546fb9eae5565a370fb7cfe9b182c06475 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 25 Jan 2024 16:37:11 +0000 Subject: [PATCH 4/9] Add support for NeuropixelsV2 beta headstage --- .../ConfigureNeuropixelsV2Beta.cs | 187 ++++++++++++++++++ .../ConfigureNeuropixelsV2BetaHeadstage.cs | 41 ++++ .../OpenEphys.Onix/NeuropixelsV2BetaData.cs | 67 +++++++ .../NeuropixelsV2BetaDataFrame.cs | 92 +++++++++ 4 files changed, 387 insertions(+) create mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs create mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs new file mode 100644 index 00000000..bbee589f --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs @@ -0,0 +1,187 @@ +using System; +using System.ComponentModel; +using System.Reactive.Disposables; + +namespace OpenEphys.Onix +{ + public class ConfigureNeuropixelsV2Beta : SingleDeviceFactory + { + public ConfigureNeuropixelsV2Beta() + : base(typeof(NeuropixelsV2Beta)) + { + } + + [Category(ConfigurationCategory)] + [Description("Specifies whether the NeuropixelsV2Beta device is enabled.")] + public bool Enable { get; set; } = true; + + [Category(ConfigurationCategory)] + [Description("Enable headstage LED when acquiring data.")] + public bool EnableLed { get; set; } = true; + + public override IObservable Process(IObservable source) + { + var enable = Enable; + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + return source.ConfigureDevice(context => + { + // configure device via the DS90UB9x deserializer device + var device = context.GetDeviceContext(deviceAddress, DS90UB9x.ID); + device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); + + // configure deserializer aliases and serializer power supply + ConfigureDeserializer(device); + + // Change all the GPIOs to locally-controlled outputs; output state set to default + var serializer = new I2CRegisterContext(device, DS90UB9x.SER_ADDR); + var gpo10Config = NeuropixelsV2Beta.DefaultGPO10Config; + var gpo32Config = NeuropixelsV2Beta.DefaultGPO32Config; + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, gpo32Config); + + // read probe metadata + var probeAMetadata = ReadProbeMetadata(serializer, ref gpo32Config, NeuropixelsV2Beta.SelectProbeA); + var probeBMetadata = ReadProbeMetadata(serializer, ref gpo32Config, NeuropixelsV2Beta.SelectProbeB); + + // toggle probe LED + gpo32Config = (gpo32Config & ~NeuropixelsV2Beta.GPO32LedMask) | (EnableLed ? 0 : NeuropixelsV2Beta.GPO32LedMask); + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, gpo32Config); + + // REC_NRESET and NRESET go high on both probes to take the ASIC out of reset + // TODO: not sure if REC_NRESET and NRESET are tied together on flex + gpo10Config |= NeuropixelsV2Beta.GPO10ResetMask | NeuropixelsV2Beta.GPO10NResetMask; + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); + System.Threading.Thread.Sleep(20); + + // configure probe streaming + var probeControl = new I2CRegisterContext(device, NeuropixelsV2Beta.ProbeAddress); + + // configure probe A streaming + if (probeAMetadata.Version != byte.MaxValue) + { + SelectProbe(serializer, ref gpo32Config, NeuropixelsV2Beta.SelectProbeA); + ConfigureProbeStreaming(probeControl); + } + + // configure probe B streaming + if (probeBMetadata.Version != byte.MaxValue) + { + SelectProbe(serializer, ref gpo32Config, NeuropixelsV2Beta.SelectProbeB); + ConfigureProbeStreaming(probeControl); + } + + // Both probes are now streaming, hit them with a mux reset to (roughly) sync. + // NB: We have found that this gives PCLK-level synchronization MOST of the time. + // However, this is not required since we have a decoder that can handle async streams. + // Still its good to get them roughly (i.e. within 10 PCLKs) started at the same time. + SyncProbes(serializer, gpo10Config); + + var disposable = DeviceManager.RegisterDevice(deviceName, device, DeviceType); + var shutdown = Disposable.Create(() => + { + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, NeuropixelsV2Beta.DefaultGPO10Config); + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, NeuropixelsV2Beta.DefaultGPO32Config); + }); + return new CompositeDisposable( + shutdown, + disposable); + }); + } + + static void ConfigureDeserializer(DeviceContext device) + { + // configure deserializer trigger mode + device.WriteRegister(DS90UB9x.TRIGGEROFF, 0); + device.WriteRegister(DS90UB9x.TRIGGER, (uint)DS90UB9xTriggerMode.Continuous); + device.WriteRegister(DS90UB9x.SYNCBITS, 0); + device.WriteRegister(DS90UB9x.DATAGATE, (uint)DS90UB9xDataGate.Disabled); + device.WriteRegister(DS90UB9x.MARK, (uint)DS90UB9xMarkMode.Disabled); + + // configure two 4-bit magic word-triggered streams, one for each probe + device.WriteRegister(DS90UB9x.READSZ, 0x0010_0007); // 16 frames/superframe, 8x 12-bit words + magic bits + device.WriteRegister(DS90UB9x.MAGIC_MASK, 0b1100000000000000_0011111111111111); // Enable inverse, wait for non-inverse, 14-bit magic word + device.WriteRegister(DS90UB9x.MAGIC, 0b0011_0011_0011_0000); // Super-frame sync word + device.WriteRegister(DS90UB9x.MAGIC_WAIT, 0); + device.WriteRegister(DS90UB9x.DATAMODE, 0b10_1101_0101); + device.WriteRegister(DS90UB9x.DATALINES0, 0x00007654); // NP A + device.WriteRegister(DS90UB9x.DATALINES1, 0x00000123); // NP B + + // configure deserializer I2C aliases + var deserializer = new I2CRegisterContext(device, DS90UB9x.DES_ADDR); + uint coaxMode = 0x4 + (uint)DS90UB9xMode.Raw12BitHighFrequency; // 0x4 maintains coax mode + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.PortMode, coaxMode); + + uint alias = NeuropixelsV2Beta.ProbeAddress << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID1, alias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias1, alias); + + alias = NeuropixelsV2Beta.FlexEEPROMAddress << 1; + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID2, alias); + deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias2, alias); + } + + NeuropixelsV2Metadata ReadProbeMetadata(I2CRegisterContext serializer, ref uint gpo32Config, byte probeSelect) + { + SelectProbe(serializer, ref gpo32Config, probeSelect); + return new NeuropixelsV2Metadata(serializer); + } + + static void SelectProbe(I2CRegisterContext serializer, ref uint gpo32Config, byte probeSelect) + { + gpo32Config = probeSelect switch + { + NeuropixelsV2Beta.SelectProbeA => gpo32Config | NeuropixelsV2Beta.ProbeSelectMask, + NeuropixelsV2Beta.SelectProbeB => gpo32Config & ~NeuropixelsV2Beta.ProbeSelectMask, + _ => gpo32Config + }; + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, gpo32Config); + System.Threading.Thread.Sleep(20); + } + + static void SyncProbes(I2CRegisterContext serializer, uint gpo10Config) + { + gpo10Config &= ~NeuropixelsV2Beta.GPO10NResetMask; + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); + + gpo10Config |= NeuropixelsV2Beta.GPO10NResetMask; + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); + } + + static void ConfigureProbeStreaming(I2CRegisterContext i2cNP) + { + // Activate recording mode on NP + i2cNP.WriteByte(0, 0b0100_0000); + } + } + + static class NeuropixelsV2Beta + { + public const int ProbeAddress = 0x70; + public const int FlexEEPROMAddress = 0x50; + + public const uint GPO10ResetMask = 1 << 3; // Used to issue full reset commands to probes + public const uint GPO10NResetMask = 1 << 7; // Used to issue full reset commands to probes + public const uint DefaultGPO10Config = 0b0001_0001; // NPs in reset, VDDA not enabled + public const uint DefaultGPO32Config = 0b1001_1001; // LED off, NP_A selected + public const uint ProbeSelectMask = 1 << 3; // Used to select which probe is active + public const uint GPO32LedMask = 1 << 7; // Used to toggle probe LED state + public const byte SelectProbeA = 0; + public const byte SelectProbeB = 1; + + public const int FramesPerSuperFrame = 16; + public const int ADCsPerProbe = 24; + public const int SyncsPerFrame = 2; + public const int CountersPerFrame = 2; + public const int ChannelCount = 384; + public const int FrameWords = 28; + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(NeuropixelsV2Beta)) + { + } + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs new file mode 100644 index 00000000..40e861ee --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.ComponentModel; + +namespace OpenEphys.Onix +{ + public class ConfigureNeuropixelsV2BetaHeadstage : HubDeviceFactory + { + PortName port; + readonly ConfigureFmcLinkController LinkController = new(); + + public ConfigureNeuropixelsV2BetaHeadstage() + { + Port = PortName.PortA; + LinkController.HubConfiguration = HubConfiguration.Passthrough; + LinkController.MinVoltage = 5.0; + LinkController.MaxVoltage = 7.0; + } + + [Category(ConfigurationCategory)] + [TypeConverter(typeof(HubDeviceConverter))] + public ConfigureNeuropixelsV2Beta NeuropixelsV2Beta { get; set; } = new(); + + public PortName Port + { + get { return port; } + set + { + port = value; + var serializerAddress = ((uint)port - 1) + 8; + LinkController.DeviceAddress = (uint)port; + NeuropixelsV2Beta.DeviceAddress = serializerAddress; + } + } + + internal override IEnumerable GetDevices() + { + yield return LinkController; + yield return NeuropixelsV2Beta; + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs new file mode 100644 index 00000000..2c958dd2 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs @@ -0,0 +1,67 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using Bonsai; +using OpenCV.Net; + +namespace OpenEphys.Onix +{ + public class NeuropixelsV2BetaData : Source + { + [TypeConverter(typeof(NeuropixelsV2Beta.NameConverter))] + public string DeviceName { get; set; } + + public int BufferSize { get; set; } = 30; + + public unsafe override IObservable Generate() + { + var bufferSize = BufferSize; + return Observable.Using( + () => DeviceManager.ReserveDevice(DeviceName), + disposable => disposable.Subject.SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2Beta)); + return deviceInfo.Context.FrameReceived + .Where(frame => frame.DeviceAddress == device.Address) + .GroupBy(frame => NeuropixelsV2BetaDataFrame.GetProbeIndex(frame)) + .SelectMany(probe => Observable.Create(observer => + { + var sampleIndex = 0; + var amplifierBuffer = new ushort[NeuropixelsV2Beta.ChannelCount, bufferSize]; + var frameCounter = new int[NeuropixelsV2Beta.FramesPerSuperFrame * bufferSize]; + var hubClockBuffer = new ulong[bufferSize]; + var clockBuffer = new ulong[bufferSize]; + + var frameObserver = Observer.Create( + frame => + { + var payload = (NeuropixelsV2BetaPayload*)frame.Data.ToPointer(); + NeuropixelsV2BetaDataFrame.CopyAmplifierBuffer(payload->SuperFrame, amplifierBuffer, frameCounter, sampleIndex); + hubClockBuffer[sampleIndex] = BitHelper.SwapEndian(payload->HubClock); + clockBuffer[sampleIndex] = frame.Clock; + if (++sampleIndex >= bufferSize) + { + var amplifierData = Mat.FromArray(amplifierBuffer); + var dataFrame = new NeuropixelsV2BetaDataFrame( + clockBuffer, + hubClockBuffer, + probe.Key, + amplifierData, + frameCounter); + observer.OnNext(dataFrame); + frameCounter = new int[NeuropixelsV2Beta.FramesPerSuperFrame * bufferSize]; + hubClockBuffer = new ulong[bufferSize]; + clockBuffer = new ulong[bufferSize]; + sampleIndex = 0; + } + }, + observer.OnError, + observer.OnCompleted); + return probe.SubscribeSafe(frameObserver); + })); + })); + } + } +} diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs new file mode 100644 index 00000000..8599564d --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs @@ -0,0 +1,92 @@ +using System.Runtime.InteropServices; +using OpenCV.Net; + +namespace OpenEphys.Onix +{ + public class NeuropixelsV2BetaDataFrame + { + public NeuropixelsV2BetaDataFrame(ulong[] clock, ulong[] hubClock, int probeIndex, Mat amplifierData, int[] frameCounter) + { + Clock = clock; + HubClock = hubClock; + ProbeIndex = probeIndex; + AmplifierData = amplifierData; + FrameCounter = frameCounter; + } + + public ulong[] Clock { get; } + + public ulong[] HubClock { get; } + + public int ProbeIndex { get; } + + public Mat AmplifierData { get; } + + public int[] FrameCounter { get; } + + internal static unsafe ushort GetProbeIndex(oni.Frame frame) + { + var data = (NeuropixelsV2BetaPayload*)frame.Data.ToPointer(); + return data->ProbeIndex; + } + + internal static unsafe void CopyAmplifierBuffer(ushort* superFrame, ushort[,] amplifierBuffer, int[] frameCounter, int index) + { + // Loop over 16 "frames" within each "super frame" + for (var i = 0; i < NeuropixelsV2Beta.FramesPerSuperFrame; i++) + { + var frameOffset = i * NeuropixelsV2Beta.FrameWords; + var frameCounterIndex = index * NeuropixelsV2Beta.FramesPerSuperFrame + i; + frameCounter[frameCounterIndex] = (superFrame[frameOffset] << 14) | (superFrame[frameOffset + 1] << 0); + + // The period of data within super frame is 28 words (24 ADCs, 2 Syncs, 2 counters) + var adcDataOffset = 2 + frameOffset; + + // Loop over ADC samples within each "frame" and map to channel position + for (var k = 0; k < NeuropixelsV2Beta.ADCsPerProbe; k++) + { + amplifierBuffer[RawToChannel[k, i], index] = superFrame[adcDataOffset + k]; + } + } + } + + // ADC & frame-index to channel mapping + // First dimension: data index + // Second dimension: frame index within super frame + private static readonly int[,] RawToChannel = { + { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }, // Data Index 9, ADC 1 + { 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126 }, // Data Index 10, ADC 7 + { 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222 }, // Data Index 11, ADC 13 + { 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318 }, // Data Index 12, ADC 19 + { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 }, // Data Index 13, ADC 2 + { 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127 }, // Data Index 14, ADC 8 + { 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223 }, // Data Index 15, ADC 14 + { 289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311, 313, 315, 317, 319 }, // Data Index 16, ADC 20 + { 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62 }, // Data Index 17, ADC 3 + { 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158 }, // Data Index 18, ADC 9 + { 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254 }, // Data Index 19, ADC 15 + { 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350 }, // Data Index 20, ADC 21 + { 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63 }, // Data Index 21, ADC 4 + { 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159 }, // Data Index 22, ADC 10 + { 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 }, // Data Index 23, ADC 16 + { 321, 323, 325, 327, 329, 331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351 }, // Data Index 24, ADC 22 + { 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 }, // Data Index 25, ADC 5 + { 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 }, // Data Index 26, ADC 11 + { 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 }, // Data Index 27, ADC 17 + { 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 }, // Data Index 28, ADC 23 + { 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 }, // Data Index 29, ADC 6 + { 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 }, // Data Index 30, ADC 12 + { 257, 259, 261, 263, 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 }, // Data Index 31, ADC 18 + { 353, 355, 357, 359, 361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 } // Data Index 32, ADC 24 + }; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + unsafe struct NeuropixelsV2BetaPayload + { + public ulong HubClock; + public ushort ProbeIndex; + public uint Reserved; + public fixed ushort SuperFrame[NeuropixelsV2Beta.FramesPerSuperFrame * NeuropixelsV2Beta.FrameWords]; + } +} From f0e66c9762846550e7f155a9336cff4399fd2810 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sun, 28 Jan 2024 22:33:09 +0000 Subject: [PATCH 5/9] Ensure devices are disposed in reverse order --- OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs | 4 +- .../OpenEphys.Onix/StackDisposable.cs | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 OpenEphys.Onix/OpenEphys.Onix/StackDisposable.cs diff --git a/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs b/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs index cfbe6691..53ff0611 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs @@ -137,12 +137,12 @@ internal IDisposable Configure() if (deviceAction != null) { var invocationList = deviceAction.GetInvocationList(); - var disposable = new CompositeDisposable(invocationList.Length); + var disposable = new StackDisposable(invocationList.Length); try { foreach (var selector in invocationList.Cast>()) { - disposable.Add(selector(this)); + disposable.Push(selector(this)); } return disposable; } diff --git a/OpenEphys.Onix/OpenEphys.Onix/StackDisposable.cs b/OpenEphys.Onix/OpenEphys.Onix/StackDisposable.cs new file mode 100644 index 00000000..e2a73353 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/StackDisposable.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Disposables; + +namespace OpenEphys.Onix +{ + internal class StackDisposable : IDisposable + { + readonly Stack _disposables; + readonly IDisposable disposable; + + public StackDisposable() + { + _disposables = new Stack(); + disposable = Disposable.Create(DisposeAll); + } + + public StackDisposable(int capacity) + { + _disposables = new Stack(capacity); + disposable = Disposable.Create(DisposeAll); + } + + public void Push(IDisposable item) + { + _disposables.Push(item); + } + + private void DisposeAll() + { + foreach (var disposable in _disposables) + { + disposable?.Dispose(); + } + } + + public void Dispose() + { + disposable.Dispose(); + } + } +} From 15c3b524b03c8279aad604d49762d77abf8ed848 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sun, 28 Jan 2024 23:24:21 +0000 Subject: [PATCH 6/9] Ensure configuration is disposed on context stop --- OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs | 8 +++++++- OpenEphys.Onix/OpenEphys.Onix/StartAcquisition.cs | 6 +----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs b/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs index 53ff0611..2d316e59 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs @@ -32,6 +32,7 @@ public class ContextTask : IDisposable private BlockingCollection FrameQueue; private CancellationTokenSource CollectFramesTokenSource; private CancellationToken CollectFramesToken; + private IDisposable ContextConfiguration; event Action configureHost; event Action configureLink; event Func configureDevice; @@ -117,7 +118,7 @@ internal void ConfigureDevice(Func selector) configureDevice += selector; } - internal IDisposable Configure() + private IDisposable ConfigureContext() { var hostAction = Interlocked.Exchange(ref configureHost, null); var linkAction = Interlocked.Exchange(ref configureLink, null); @@ -163,6 +164,9 @@ internal void Start() { if (running) return; + // NB: Configure context before starting acquisition + ContextConfiguration = ConfigureContext(); + // NB: Stuff related to sync mode is 100% ONIX, not ONI, so long term another place // to do this separation might be needed int addr = ctx.HardwareAddress; @@ -262,6 +266,8 @@ internal void Stop() FrameQueue = null; ctx.Stop(); running = false; + + ContextConfiguration?.Dispose(); } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/StartAcquisition.cs b/OpenEphys.Onix/OpenEphys.Onix/StartAcquisition.cs index 1b2165c1..99c9dddb 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/StartAcquisition.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/StartAcquisition.cs @@ -18,11 +18,7 @@ public class StartAcquisition : Combinator { return Observable.Create(observer => { - var disposable = new CompositeDisposable(capacity: 2) - { - context.Configure(), - context.FrameReceived.SubscribeSafe(observer) - }; + var disposable = context.FrameReceived.SubscribeSafe(observer); try { context.BlockReadSize = ReadSize; From d0e0317b2c1f8092e06188d54a43832cc79178d7 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Wed, 7 Feb 2024 11:01:48 +0000 Subject: [PATCH 7/9] Use hub index to infer passthrough device address --- .../OpenEphys.Onix/ConfigureNeuropixelsV2.cs | 2 +- .../ConfigureNeuropixelsV2Beta.cs | 2 +- .../ConfigureNeuropixelsV2BetaHeadstage.cs | 4 +-- .../ConfigureNeuropixelsV2Headstage.cs | 4 +-- .../OpenEphys.Onix/ContextHelper.cs | 6 ++++ OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs | 29 ++++++++++--------- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs index 868f1773..1a8ef76f 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs @@ -23,7 +23,7 @@ public override IObservable Process(IObservable source return source.ConfigureDevice(context => { // configure device via the DS90UB9x deserializer device - var device = context.GetDeviceContext(deviceAddress, DS90UB9x.ID); + var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID); device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); // configure deserializer aliases and serializer power supply diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs index bbee589f..ac8f7f3e 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs @@ -27,7 +27,7 @@ public override IObservable Process(IObservable source return source.ConfigureDevice(context => { // configure device via the DS90UB9x deserializer device - var device = context.GetDeviceContext(deviceAddress, DS90UB9x.ID); + var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID); device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0); // configure deserializer aliases and serializer power supply diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs index 40e861ee..1d386fe1 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs @@ -26,9 +26,9 @@ public PortName Port set { port = value; - var serializerAddress = ((uint)port - 1) + 8; + var offset = (uint)port << 8; LinkController.DeviceAddress = (uint)port; - NeuropixelsV2Beta.DeviceAddress = serializerAddress; + NeuropixelsV2Beta.DeviceAddress = offset + 0; } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs index 84c11b9b..f1a22335 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs @@ -26,9 +26,9 @@ public PortName Port set { port = value; - var serializerAddress = ((uint)port - 1) + 8; + var offset = (uint)port << 8; LinkController.DeviceAddress = (uint)port; - NeuropixelsV2.DeviceAddress = serializerAddress; + NeuropixelsV2.DeviceAddress = offset + 0; } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ContextHelper.cs b/OpenEphys.Onix/OpenEphys.Onix/ContextHelper.cs index 73ac4f3b..036a2b1d 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ContextHelper.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ContextHelper.cs @@ -32,5 +32,11 @@ public static DeviceContext GetDeviceContext(this DeviceInfo deviceInfo, Type ex return new DeviceContext(deviceInfo.Context, device); } + + public static DeviceContext GetPassthroughDeviceContext(this ContextTask context, uint address, int id) + { + var passthroughDeviceAddress = context.GetPassthroughDeviceAddress(address); + return GetDeviceContext(context, passthroughDeviceAddress, id); + } } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs b/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs index 2d316e59..a83193fa 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ContextTask.cs @@ -222,7 +222,8 @@ internal void Start() { if (FrameQueue.TryTake(out oni.Frame frame, QueueTimeoutMilliseconds, CollectFramesToken)) { - OnFrameReceived(frame); + FrameReceived.OnNext(frame); + frame.Dispose(); } } } @@ -258,9 +259,8 @@ internal void Stop() // Clear queue and free memory while (FrameQueue?.Count > 0) { - oni.Frame frame; - frame = FrameQueue.Take(); - DisposeFrame(frame); + var frame = FrameQueue.Take(); + frame.Dispose(); } FrameQueue?.Dispose(); FrameQueue = null; @@ -381,20 +381,23 @@ public void Write(uint deviceAddress, IntPtr data, int dataSize) } } - public oni.Hub GetHub(uint deviceAddress) { return ctx.GetHub(deviceAddress); } - #endregion + public oni.Hub GetHub(uint deviceAddress) => ctx.GetHub(deviceAddress); - private void OnFrameReceived(oni.Frame frame) + public virtual uint GetPassthroughDeviceAddress(uint deviceAddress) { - FrameReceived.OnNext(frame); - DisposeFrame(frame); - } + var hubAddress = (deviceAddress & 0xFF00u) >> 8; + if (hubAddress == 0) + { + throw new ArgumentException( + "Device addresses on hub zero cannot be used to create passthrough devices.", + nameof(deviceAddress)); + } - private static void DisposeFrame(oni.Frame frame) - { - frame.Dispose(); + return hubAddress + 7; } + #endregion + public void Dispose() { lock (runLock) From 3c34736e7ff3adc46a7a330fb4ec1b8ed3457b32 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Wed, 7 Feb 2024 11:19:21 +0000 Subject: [PATCH 8/9] Ensure data nodes stream data from only one probe --- .../OpenEphys.Onix/NeuropixelsV2BetaData.cs | 73 ++++++++++--------- .../NeuropixelsV2BetaDataFrame.cs | 5 +- .../OpenEphys.Onix/NeuropixelsV2Data.cs | 58 ++++++++------- .../OpenEphys.Onix/NeuropixelsV2DataFrame.cs | 5 +- .../OpenEphys.Onix/NeuropixelsV2Probe.cs | 8 ++ 5 files changed, 77 insertions(+), 72 deletions(-) create mode 100644 OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Probe.cs diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs index 2c958dd2..21f0c835 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs @@ -15,6 +15,8 @@ public class NeuropixelsV2BetaData : Source public int BufferSize { get; set; } = 30; + public NeuropixelsV2Probe ProbeIndex { get; set; } + public unsafe override IObservable Generate() { var bufferSize = BufferSize; @@ -23,44 +25,43 @@ public unsafe override IObservable Generate() disposable => disposable.Subject.SelectMany(deviceInfo => { var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2Beta)); - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .GroupBy(frame => NeuropixelsV2BetaDataFrame.GetProbeIndex(frame)) - .SelectMany(probe => Observable.Create(observer => - { - var sampleIndex = 0; - var amplifierBuffer = new ushort[NeuropixelsV2Beta.ChannelCount, bufferSize]; - var frameCounter = new int[NeuropixelsV2Beta.FramesPerSuperFrame * bufferSize]; - var hubClockBuffer = new ulong[bufferSize]; - var clockBuffer = new ulong[bufferSize]; + var probeData = deviceInfo.Context.FrameReceived.Where(frame => + frame.DeviceAddress == device.Address && + NeuropixelsV2BetaDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); + return Observable.Create(observer => + { + var sampleIndex = 0; + var amplifierBuffer = new ushort[NeuropixelsV2Beta.ChannelCount, bufferSize]; + var frameCounter = new int[NeuropixelsV2Beta.FramesPerSuperFrame * bufferSize]; + var hubClockBuffer = new ulong[bufferSize]; + var clockBuffer = new ulong[bufferSize]; - var frameObserver = Observer.Create( - frame => + var frameObserver = Observer.Create( + frame => + { + var payload = (NeuropixelsV2BetaPayload*)frame.Data.ToPointer(); + NeuropixelsV2BetaDataFrame.CopyAmplifierBuffer(payload->SuperFrame, amplifierBuffer, frameCounter, sampleIndex); + hubClockBuffer[sampleIndex] = BitHelper.SwapEndian(payload->HubClock); + clockBuffer[sampleIndex] = frame.Clock; + if (++sampleIndex >= bufferSize) { - var payload = (NeuropixelsV2BetaPayload*)frame.Data.ToPointer(); - NeuropixelsV2BetaDataFrame.CopyAmplifierBuffer(payload->SuperFrame, amplifierBuffer, frameCounter, sampleIndex); - hubClockBuffer[sampleIndex] = BitHelper.SwapEndian(payload->HubClock); - clockBuffer[sampleIndex] = frame.Clock; - if (++sampleIndex >= bufferSize) - { - var amplifierData = Mat.FromArray(amplifierBuffer); - var dataFrame = new NeuropixelsV2BetaDataFrame( - clockBuffer, - hubClockBuffer, - probe.Key, - amplifierData, - frameCounter); - observer.OnNext(dataFrame); - frameCounter = new int[NeuropixelsV2Beta.FramesPerSuperFrame * bufferSize]; - hubClockBuffer = new ulong[bufferSize]; - clockBuffer = new ulong[bufferSize]; - sampleIndex = 0; - } - }, - observer.OnError, - observer.OnCompleted); - return probe.SubscribeSafe(frameObserver); - })); + var amplifierData = Mat.FromArray(amplifierBuffer); + var dataFrame = new NeuropixelsV2BetaDataFrame( + clockBuffer, + hubClockBuffer, + amplifierData, + frameCounter); + observer.OnNext(dataFrame); + frameCounter = new int[NeuropixelsV2Beta.FramesPerSuperFrame * bufferSize]; + hubClockBuffer = new ulong[bufferSize]; + clockBuffer = new ulong[bufferSize]; + sampleIndex = 0; + } + }, + observer.OnError, + observer.OnCompleted); + return probeData.SubscribeSafe(frameObserver); + }); })); } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs index 8599564d..4a9c04f7 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs @@ -5,11 +5,10 @@ namespace OpenEphys.Onix { public class NeuropixelsV2BetaDataFrame { - public NeuropixelsV2BetaDataFrame(ulong[] clock, ulong[] hubClock, int probeIndex, Mat amplifierData, int[] frameCounter) + public NeuropixelsV2BetaDataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData, int[] frameCounter) { Clock = clock; HubClock = hubClock; - ProbeIndex = probeIndex; AmplifierData = amplifierData; FrameCounter = frameCounter; } @@ -18,8 +17,6 @@ public NeuropixelsV2BetaDataFrame(ulong[] clock, ulong[] hubClock, int probeInde public ulong[] HubClock { get; } - public int ProbeIndex { get; } - public Mat AmplifierData { get; } public int[] FrameCounter { get; } diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs index fdf6c149..104ac04c 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs @@ -15,6 +15,8 @@ public class NeuropixelsV2Data : Source public int BufferSize { get; set; } = 30; + public NeuropixelsV2Probe ProbeIndex { get; set; } + public unsafe override IObservable Generate() { var bufferSize = BufferSize; @@ -23,36 +25,36 @@ public unsafe override IObservable Generate() disposable => disposable.Subject.SelectMany(deviceInfo => { var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2)); - return deviceInfo.Context.FrameReceived - .Where(frame => frame.DeviceAddress == device.Address) - .GroupBy(frame => NeuropixelsV2DataFrame.GetProbeIndex(frame)) - .SelectMany(probe => Observable.Create(observer => - { - var sampleIndex = 0; - var amplifierBuffer = new ushort[NeuropixelsV2.ChannelCount, bufferSize]; - var hubClockBuffer = new ulong[bufferSize]; - var clockBuffer = new ulong[bufferSize]; + var probeData = deviceInfo.Context.FrameReceived.Where(frame => + frame.DeviceAddress == device.Address && + NeuropixelsV2DataFrame.GetProbeIndex(frame) == (int)ProbeIndex); + return Observable.Create(observer => + { + var sampleIndex = 0; + var amplifierBuffer = new ushort[NeuropixelsV2.ChannelCount, bufferSize]; + var hubClockBuffer = new ulong[bufferSize]; + var clockBuffer = new ulong[bufferSize]; - var frameObserver = Observer.Create( - frame => + var frameObserver = Observer.Create( + frame => + { + var payload = (NeuropixelsV2Payload*)frame.Data.ToPointer(); + NeuropixelsV2DataFrame.CopyAmplifierBuffer(payload->AmplifierData, amplifierBuffer, sampleIndex); + hubClockBuffer[sampleIndex] = BitHelper.SwapEndian(payload->HubClock); + clockBuffer[sampleIndex] = frame.Clock; + if (++sampleIndex >= bufferSize) { - var payload = (NeuropixelsV2Payload*)frame.Data.ToPointer(); - NeuropixelsV2DataFrame.CopyAmplifierBuffer(payload->AmplifierData, amplifierBuffer, sampleIndex); - hubClockBuffer[sampleIndex] = BitHelper.SwapEndian(payload->HubClock); - clockBuffer[sampleIndex] = frame.Clock; - if (++sampleIndex >= bufferSize) - { - var amplifierData = Mat.FromArray(amplifierBuffer); - observer.OnNext(new NeuropixelsV2DataFrame(clockBuffer, hubClockBuffer, probe.Key, amplifierData)); - hubClockBuffer = new ulong[bufferSize]; - clockBuffer = new ulong[bufferSize]; - sampleIndex = 0; - } - }, - observer.OnError, - observer.OnCompleted); - return probe.SubscribeSafe(frameObserver); - })); + var amplifierData = Mat.FromArray(amplifierBuffer); + observer.OnNext(new NeuropixelsV2DataFrame(clockBuffer, hubClockBuffer, amplifierData)); + hubClockBuffer = new ulong[bufferSize]; + clockBuffer = new ulong[bufferSize]; + sampleIndex = 0; + } + }, + observer.OnError, + observer.OnCompleted); + return probeData.SubscribeSafe(frameObserver); + }); })); } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs index 2ed76201..9a1f9302 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs @@ -5,11 +5,10 @@ namespace OpenEphys.Onix { public class NeuropixelsV2DataFrame { - public NeuropixelsV2DataFrame(ulong[] clock, ulong[] hubClock, int probeIndex, Mat amplifierData) + public NeuropixelsV2DataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData) { Clock = clock; HubClock = hubClock; - ProbeIndex = probeIndex; AmplifierData = amplifierData; } @@ -17,8 +16,6 @@ public NeuropixelsV2DataFrame(ulong[] clock, ulong[] hubClock, int probeIndex, M public ulong[] HubClock { get; } - public int ProbeIndex { get; } - public Mat AmplifierData { get; } internal static unsafe ushort GetProbeIndex(oni.Frame frame) diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Probe.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Probe.cs new file mode 100644 index 00000000..be39b7e3 --- /dev/null +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Probe.cs @@ -0,0 +1,8 @@ +namespace OpenEphys.Onix +{ + public enum NeuropixelsV2Probe + { + ProbeA = 0, + ProbeB = 1 + } +} From 9cdd4bb6e419ec98ec8c8545b164e4531b5c606a Mon Sep 17 00:00:00 2001 From: glopesdev Date: Wed, 7 Feb 2024 11:30:38 +0000 Subject: [PATCH 9/9] Follow naming convention for passthrough devices --- ...pixelsV2.cs => ConfigureNeuropixelsV2e.cs} | 40 ++++++++-------- ...Beta.cs => ConfigureNeuropixelsV2eBeta.cs} | 48 +++++++++---------- ...> ConfigureNeuropixelsV2eBetaHeadstage.cs} | 6 +-- ...cs => ConfigureNeuropixelsV2eHeadstage.cs} | 6 +-- ...2BetaData.cs => NeuropixelsV2eBetaData.cs} | 22 ++++----- ...rame.cs => NeuropixelsV2eBetaDataFrame.cs} | 14 +++--- ...opixelsV2Data.cs => NeuropixelsV2eData.cs} | 18 +++---- ...ataFrame.cs => NeuropixelsV2eDataFrame.cs} | 12 ++--- ...2Metadata.cs => NeuropixelsV2eMetadata.cs} | 6 +-- 9 files changed, 86 insertions(+), 86 deletions(-) rename OpenEphys.Onix/OpenEphys.Onix/{ConfigureNeuropixelsV2.cs => ConfigureNeuropixelsV2e.cs} (85%) rename OpenEphys.Onix/OpenEphys.Onix/{ConfigureNeuropixelsV2Beta.cs => ConfigureNeuropixelsV2eBeta.cs} (82%) rename OpenEphys.Onix/OpenEphys.Onix/{ConfigureNeuropixelsV2BetaHeadstage.cs => ConfigureNeuropixelsV2eBetaHeadstage.cs} (82%) rename OpenEphys.Onix/OpenEphys.Onix/{ConfigureNeuropixelsV2Headstage.cs => ConfigureNeuropixelsV2eHeadstage.cs} (83%) rename OpenEphys.Onix/OpenEphys.Onix/{NeuropixelsV2BetaData.cs => NeuropixelsV2eBetaData.cs} (78%) rename OpenEphys.Onix/OpenEphys.Onix/{NeuropixelsV2BetaDataFrame.cs => NeuropixelsV2eBetaDataFrame.cs} (89%) rename OpenEphys.Onix/OpenEphys.Onix/{NeuropixelsV2Data.cs => NeuropixelsV2eData.cs} (77%) rename OpenEphys.Onix/OpenEphys.Onix/{NeuropixelsV2DataFrame.cs => NeuropixelsV2eDataFrame.cs} (91%) rename OpenEphys.Onix/OpenEphys.Onix/{NeuropixelsV2Metadata.cs => NeuropixelsV2eMetadata.cs} (90%) diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs similarity index 85% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs rename to OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs index 1a8ef76f..c3505ce4 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2e.cs @@ -4,10 +4,10 @@ namespace OpenEphys.Onix { - public class ConfigureNeuropixelsV2 : SingleDeviceFactory + public class ConfigureNeuropixelsV2e : SingleDeviceFactory { - public ConfigureNeuropixelsV2() - : base(typeof(NeuropixelsV2)) + public ConfigureNeuropixelsV2e() + : base(typeof(NeuropixelsV2e)) { } @@ -32,32 +32,32 @@ public override IObservable Process(IObservable source var gpo10Config = EnableProbeSupply(serializer); // read probe metadata - var probeAMetadata = ReadProbeMetadata(serializer, NeuropixelsV2.ProbeASelected); - var probeBMetadata = ReadProbeMetadata(serializer, NeuropixelsV2.ProbeBSelected); + var probeAMetadata = ReadProbeMetadata(serializer, NeuropixelsV2e.ProbeASelected); + var probeBMetadata = ReadProbeMetadata(serializer, NeuropixelsV2e.ProbeBSelected); // issue full reset to both probes ResetProbes(serializer, gpo10Config); - var probeControl = new I2CRegisterContext(device, NeuropixelsV2.ProbeAddress); + var probeControl = new I2CRegisterContext(device, NeuropixelsV2e.ProbeAddress); // configure probe A streaming if (probeAMetadata.ProbeSN != null) { - SelectProbe(serializer, NeuropixelsV2.ProbeASelected); + SelectProbe(serializer, NeuropixelsV2e.ProbeASelected); ConfigureProbeStreaming(probeControl); } // configure probe B streaming if (probeBMetadata.ProbeSN != null) { - SelectProbe(serializer, NeuropixelsV2.ProbeBSelected); + SelectProbe(serializer, NeuropixelsV2e.ProbeBSelected); ConfigureProbeStreaming(probeControl); } var disposable = DeviceManager.RegisterDevice(deviceName, device, DeviceType); var shutdown = Disposable.Create(() => { - serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, NeuropixelsV2.DefaultGPO10Config); - SelectProbe(serializer, NeuropixelsV2.NoProbeSelected); + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, NeuropixelsV2e.DefaultGPO10Config); + SelectProbe(serializer, NeuropixelsV2e.NoProbeSelected); }); return new CompositeDisposable( shutdown, @@ -88,19 +88,19 @@ static void ConfigureDeserializer(DeviceContext device) uint coaxMode = 0x4 + (uint)DS90UB9xMode.Raw12BitHighFrequency; // 0x4 maintains coax mode deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.PortMode, coaxMode); - uint alias = NeuropixelsV2.ProbeAddress << 1; + uint alias = NeuropixelsV2e.ProbeAddress << 1; deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID1, alias); deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias1, alias); - alias = NeuropixelsV2.FlexEEPROMAddress << 1; + alias = NeuropixelsV2e.FlexEEPROMAddress << 1; deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID2, alias); deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias2, alias); } static uint EnableProbeSupply(I2CRegisterContext serializer) { - var gpo10Config = NeuropixelsV2.DefaultGPO10Config | NeuropixelsV2.GPO10SupplyMask; - SelectProbe(serializer, NeuropixelsV2.NoProbeSelected); + var gpo10Config = NeuropixelsV2e.DefaultGPO10Config | NeuropixelsV2e.GPO10SupplyMask; + SelectProbe(serializer, NeuropixelsV2e.NoProbeSelected); // turn on analog supply and wait for boot serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); @@ -108,10 +108,10 @@ static uint EnableProbeSupply(I2CRegisterContext serializer) return gpo10Config; } - NeuropixelsV2Metadata ReadProbeMetadata(I2CRegisterContext serializer, byte probeSelect) + NeuropixelsV2eMetadata ReadProbeMetadata(I2CRegisterContext serializer, byte probeSelect) { SelectProbe(serializer, probeSelect); - return new NeuropixelsV2Metadata(serializer); + return new NeuropixelsV2eMetadata(serializer); } static void SelectProbe(I2CRegisterContext serializer, byte probeSelect) @@ -122,9 +122,9 @@ static void SelectProbe(I2CRegisterContext serializer, byte probeSelect) static void ResetProbes(I2CRegisterContext serializer, uint gpo10Config) { - gpo10Config &= ~NeuropixelsV2.GPO10ResetMask; + gpo10Config &= ~NeuropixelsV2e.GPO10ResetMask; serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); - gpo10Config |= NeuropixelsV2.GPO10ResetMask; + gpo10Config |= NeuropixelsV2e.GPO10ResetMask; serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); } @@ -149,7 +149,7 @@ static void ConfigureProbeStreaming(I2CRegisterContext i2cNP) } } - static class NeuropixelsV2 + static class NeuropixelsV2e { public const int ProbeAddress = 0x10; public const int FlexEEPROMAddress = 0x50; @@ -169,7 +169,7 @@ static class NeuropixelsV2 internal class NameConverter : DeviceNameConverter { public NameConverter() - : base(typeof(NeuropixelsV2)) + : base(typeof(NeuropixelsV2e)) { } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBeta.cs similarity index 82% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs rename to OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBeta.cs index ac8f7f3e..9f5f7dc4 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Beta.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBeta.cs @@ -4,10 +4,10 @@ namespace OpenEphys.Onix { - public class ConfigureNeuropixelsV2Beta : SingleDeviceFactory + public class ConfigureNeuropixelsV2eBeta : SingleDeviceFactory { - public ConfigureNeuropixelsV2Beta() - : base(typeof(NeuropixelsV2Beta)) + public ConfigureNeuropixelsV2eBeta() + : base(typeof(NeuropixelsV2eBeta)) { } @@ -35,39 +35,39 @@ public override IObservable Process(IObservable source // Change all the GPIOs to locally-controlled outputs; output state set to default var serializer = new I2CRegisterContext(device, DS90UB9x.SER_ADDR); - var gpo10Config = NeuropixelsV2Beta.DefaultGPO10Config; - var gpo32Config = NeuropixelsV2Beta.DefaultGPO32Config; + var gpo10Config = NeuropixelsV2eBeta.DefaultGPO10Config; + var gpo32Config = NeuropixelsV2eBeta.DefaultGPO32Config; serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, gpo32Config); // read probe metadata - var probeAMetadata = ReadProbeMetadata(serializer, ref gpo32Config, NeuropixelsV2Beta.SelectProbeA); - var probeBMetadata = ReadProbeMetadata(serializer, ref gpo32Config, NeuropixelsV2Beta.SelectProbeB); + var probeAMetadata = ReadProbeMetadata(serializer, ref gpo32Config, NeuropixelsV2eBeta.SelectProbeA); + var probeBMetadata = ReadProbeMetadata(serializer, ref gpo32Config, NeuropixelsV2eBeta.SelectProbeB); // toggle probe LED - gpo32Config = (gpo32Config & ~NeuropixelsV2Beta.GPO32LedMask) | (EnableLed ? 0 : NeuropixelsV2Beta.GPO32LedMask); + gpo32Config = (gpo32Config & ~NeuropixelsV2eBeta.GPO32LedMask) | (EnableLed ? 0 : NeuropixelsV2eBeta.GPO32LedMask); serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, gpo32Config); // REC_NRESET and NRESET go high on both probes to take the ASIC out of reset // TODO: not sure if REC_NRESET and NRESET are tied together on flex - gpo10Config |= NeuropixelsV2Beta.GPO10ResetMask | NeuropixelsV2Beta.GPO10NResetMask; + gpo10Config |= NeuropixelsV2eBeta.GPO10ResetMask | NeuropixelsV2eBeta.GPO10NResetMask; serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); System.Threading.Thread.Sleep(20); // configure probe streaming - var probeControl = new I2CRegisterContext(device, NeuropixelsV2Beta.ProbeAddress); + var probeControl = new I2CRegisterContext(device, NeuropixelsV2eBeta.ProbeAddress); // configure probe A streaming if (probeAMetadata.Version != byte.MaxValue) { - SelectProbe(serializer, ref gpo32Config, NeuropixelsV2Beta.SelectProbeA); + SelectProbe(serializer, ref gpo32Config, NeuropixelsV2eBeta.SelectProbeA); ConfigureProbeStreaming(probeControl); } // configure probe B streaming if (probeBMetadata.Version != byte.MaxValue) { - SelectProbe(serializer, ref gpo32Config, NeuropixelsV2Beta.SelectProbeB); + SelectProbe(serializer, ref gpo32Config, NeuropixelsV2eBeta.SelectProbeB); ConfigureProbeStreaming(probeControl); } @@ -80,8 +80,8 @@ public override IObservable Process(IObservable source var disposable = DeviceManager.RegisterDevice(deviceName, device, DeviceType); var shutdown = Disposable.Create(() => { - serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, NeuropixelsV2Beta.DefaultGPO10Config); - serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, NeuropixelsV2Beta.DefaultGPO32Config); + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, NeuropixelsV2eBeta.DefaultGPO10Config); + serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, NeuropixelsV2eBeta.DefaultGPO32Config); }); return new CompositeDisposable( shutdown, @@ -112,27 +112,27 @@ static void ConfigureDeserializer(DeviceContext device) uint coaxMode = 0x4 + (uint)DS90UB9xMode.Raw12BitHighFrequency; // 0x4 maintains coax mode deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.PortMode, coaxMode); - uint alias = NeuropixelsV2Beta.ProbeAddress << 1; + uint alias = NeuropixelsV2eBeta.ProbeAddress << 1; deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID1, alias); deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias1, alias); - alias = NeuropixelsV2Beta.FlexEEPROMAddress << 1; + alias = NeuropixelsV2eBeta.FlexEEPROMAddress << 1; deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID2, alias); deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias2, alias); } - NeuropixelsV2Metadata ReadProbeMetadata(I2CRegisterContext serializer, ref uint gpo32Config, byte probeSelect) + NeuropixelsV2eMetadata ReadProbeMetadata(I2CRegisterContext serializer, ref uint gpo32Config, byte probeSelect) { SelectProbe(serializer, ref gpo32Config, probeSelect); - return new NeuropixelsV2Metadata(serializer); + return new NeuropixelsV2eMetadata(serializer); } static void SelectProbe(I2CRegisterContext serializer, ref uint gpo32Config, byte probeSelect) { gpo32Config = probeSelect switch { - NeuropixelsV2Beta.SelectProbeA => gpo32Config | NeuropixelsV2Beta.ProbeSelectMask, - NeuropixelsV2Beta.SelectProbeB => gpo32Config & ~NeuropixelsV2Beta.ProbeSelectMask, + NeuropixelsV2eBeta.SelectProbeA => gpo32Config | NeuropixelsV2eBeta.ProbeSelectMask, + NeuropixelsV2eBeta.SelectProbeB => gpo32Config & ~NeuropixelsV2eBeta.ProbeSelectMask, _ => gpo32Config }; serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO32, gpo32Config); @@ -141,10 +141,10 @@ static void SelectProbe(I2CRegisterContext serializer, ref uint gpo32Config, byt static void SyncProbes(I2CRegisterContext serializer, uint gpo10Config) { - gpo10Config &= ~NeuropixelsV2Beta.GPO10NResetMask; + gpo10Config &= ~NeuropixelsV2eBeta.GPO10NResetMask; serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); - gpo10Config |= NeuropixelsV2Beta.GPO10NResetMask; + gpo10Config |= NeuropixelsV2eBeta.GPO10NResetMask; serializer.WriteByte((uint)DS90UB9xSerializerI2CRegister.GPIO10, gpo10Config); } @@ -155,7 +155,7 @@ static void ConfigureProbeStreaming(I2CRegisterContext i2cNP) } } - static class NeuropixelsV2Beta + static class NeuropixelsV2eBeta { public const int ProbeAddress = 0x70; public const int FlexEEPROMAddress = 0x50; @@ -179,7 +179,7 @@ static class NeuropixelsV2Beta internal class NameConverter : DeviceNameConverter { public NameConverter() - : base(typeof(NeuropixelsV2Beta)) + : base(typeof(NeuropixelsV2eBeta)) { } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBetaHeadstage.cs similarity index 82% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs rename to OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBetaHeadstage.cs index 1d386fe1..295367fc 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2BetaHeadstage.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eBetaHeadstage.cs @@ -3,12 +3,12 @@ namespace OpenEphys.Onix { - public class ConfigureNeuropixelsV2BetaHeadstage : HubDeviceFactory + public class ConfigureNeuropixelsV2eBetaHeadstage : HubDeviceFactory { PortName port; readonly ConfigureFmcLinkController LinkController = new(); - public ConfigureNeuropixelsV2BetaHeadstage() + public ConfigureNeuropixelsV2eBetaHeadstage() { Port = PortName.PortA; LinkController.HubConfiguration = HubConfiguration.Passthrough; @@ -18,7 +18,7 @@ public ConfigureNeuropixelsV2BetaHeadstage() [Category(ConfigurationCategory)] [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureNeuropixelsV2Beta NeuropixelsV2Beta { get; set; } = new(); + public ConfigureNeuropixelsV2eBeta NeuropixelsV2Beta { get; set; } = new(); public PortName Port { diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs similarity index 83% rename from OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs rename to OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs index f1a22335..941bb374 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2Headstage.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureNeuropixelsV2eHeadstage.cs @@ -3,12 +3,12 @@ namespace OpenEphys.Onix { - public class ConfigureNeuropixelsV2Headstage : HubDeviceFactory + public class ConfigureNeuropixelsV2eHeadstage : HubDeviceFactory { PortName port; readonly ConfigureFmcLinkController LinkController = new(); - public ConfigureNeuropixelsV2Headstage() + public ConfigureNeuropixelsV2eHeadstage() { Port = PortName.PortA; LinkController.HubConfiguration = HubConfiguration.Passthrough; @@ -18,7 +18,7 @@ public ConfigureNeuropixelsV2Headstage() [Category(ConfigurationCategory)] [TypeConverter(typeof(HubDeviceConverter))] - public ConfigureNeuropixelsV2 NeuropixelsV2 { get; set; } = new(); + public ConfigureNeuropixelsV2e NeuropixelsV2 { get; set; } = new(); public PortName Port { diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaData.cs similarity index 78% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs rename to OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaData.cs index 21f0c835..46c56ac5 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaData.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaData.cs @@ -8,31 +8,31 @@ namespace OpenEphys.Onix { - public class NeuropixelsV2BetaData : Source + public class NeuropixelsV2eBetaData : Source { - [TypeConverter(typeof(NeuropixelsV2Beta.NameConverter))] + [TypeConverter(typeof(NeuropixelsV2eBeta.NameConverter))] public string DeviceName { get; set; } public int BufferSize { get; set; } = 30; public NeuropixelsV2Probe ProbeIndex { get; set; } - public unsafe override IObservable Generate() + public unsafe override IObservable Generate() { var bufferSize = BufferSize; return Observable.Using( () => DeviceManager.ReserveDevice(DeviceName), disposable => disposable.Subject.SelectMany(deviceInfo => { - var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2Beta)); + var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2eBeta)); var probeData = deviceInfo.Context.FrameReceived.Where(frame => frame.DeviceAddress == device.Address && - NeuropixelsV2BetaDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); - return Observable.Create(observer => + NeuropixelsV2eBetaDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); + return Observable.Create(observer => { var sampleIndex = 0; - var amplifierBuffer = new ushort[NeuropixelsV2Beta.ChannelCount, bufferSize]; - var frameCounter = new int[NeuropixelsV2Beta.FramesPerSuperFrame * bufferSize]; + var amplifierBuffer = new ushort[NeuropixelsV2eBeta.ChannelCount, bufferSize]; + var frameCounter = new int[NeuropixelsV2eBeta.FramesPerSuperFrame * bufferSize]; var hubClockBuffer = new ulong[bufferSize]; var clockBuffer = new ulong[bufferSize]; @@ -40,19 +40,19 @@ public unsafe override IObservable Generate() frame => { var payload = (NeuropixelsV2BetaPayload*)frame.Data.ToPointer(); - NeuropixelsV2BetaDataFrame.CopyAmplifierBuffer(payload->SuperFrame, amplifierBuffer, frameCounter, sampleIndex); + NeuropixelsV2eBetaDataFrame.CopyAmplifierBuffer(payload->SuperFrame, amplifierBuffer, frameCounter, sampleIndex); hubClockBuffer[sampleIndex] = BitHelper.SwapEndian(payload->HubClock); clockBuffer[sampleIndex] = frame.Clock; if (++sampleIndex >= bufferSize) { var amplifierData = Mat.FromArray(amplifierBuffer); - var dataFrame = new NeuropixelsV2BetaDataFrame( + var dataFrame = new NeuropixelsV2eBetaDataFrame( clockBuffer, hubClockBuffer, amplifierData, frameCounter); observer.OnNext(dataFrame); - frameCounter = new int[NeuropixelsV2Beta.FramesPerSuperFrame * bufferSize]; + frameCounter = new int[NeuropixelsV2eBeta.FramesPerSuperFrame * bufferSize]; hubClockBuffer = new ulong[bufferSize]; clockBuffer = new ulong[bufferSize]; sampleIndex = 0; diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaDataFrame.cs similarity index 89% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs rename to OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaDataFrame.cs index 4a9c04f7..93ae4db3 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2BetaDataFrame.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eBetaDataFrame.cs @@ -3,9 +3,9 @@ namespace OpenEphys.Onix { - public class NeuropixelsV2BetaDataFrame + public class NeuropixelsV2eBetaDataFrame { - public NeuropixelsV2BetaDataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData, int[] frameCounter) + public NeuropixelsV2eBetaDataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData, int[] frameCounter) { Clock = clock; HubClock = hubClock; @@ -30,17 +30,17 @@ internal static unsafe ushort GetProbeIndex(oni.Frame frame) internal static unsafe void CopyAmplifierBuffer(ushort* superFrame, ushort[,] amplifierBuffer, int[] frameCounter, int index) { // Loop over 16 "frames" within each "super frame" - for (var i = 0; i < NeuropixelsV2Beta.FramesPerSuperFrame; i++) + for (var i = 0; i < NeuropixelsV2eBeta.FramesPerSuperFrame; i++) { - var frameOffset = i * NeuropixelsV2Beta.FrameWords; - var frameCounterIndex = index * NeuropixelsV2Beta.FramesPerSuperFrame + i; + var frameOffset = i * NeuropixelsV2eBeta.FrameWords; + var frameCounterIndex = index * NeuropixelsV2eBeta.FramesPerSuperFrame + i; frameCounter[frameCounterIndex] = (superFrame[frameOffset] << 14) | (superFrame[frameOffset + 1] << 0); // The period of data within super frame is 28 words (24 ADCs, 2 Syncs, 2 counters) var adcDataOffset = 2 + frameOffset; // Loop over ADC samples within each "frame" and map to channel position - for (var k = 0; k < NeuropixelsV2Beta.ADCsPerProbe; k++) + for (var k = 0; k < NeuropixelsV2eBeta.ADCsPerProbe; k++) { amplifierBuffer[RawToChannel[k, i], index] = superFrame[adcDataOffset + k]; } @@ -84,6 +84,6 @@ unsafe struct NeuropixelsV2BetaPayload public ulong HubClock; public ushort ProbeIndex; public uint Reserved; - public fixed ushort SuperFrame[NeuropixelsV2Beta.FramesPerSuperFrame * NeuropixelsV2Beta.FrameWords]; + public fixed ushort SuperFrame[NeuropixelsV2eBeta.FramesPerSuperFrame * NeuropixelsV2eBeta.FrameWords]; } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eData.cs similarity index 77% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs rename to OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eData.cs index 104ac04c..efe03b26 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Data.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eData.cs @@ -8,30 +8,30 @@ namespace OpenEphys.Onix { - public class NeuropixelsV2Data : Source + public class NeuropixelsV2eData : Source { - [TypeConverter(typeof(NeuropixelsV2.NameConverter))] + [TypeConverter(typeof(NeuropixelsV2e.NameConverter))] public string DeviceName { get; set; } public int BufferSize { get; set; } = 30; public NeuropixelsV2Probe ProbeIndex { get; set; } - public unsafe override IObservable Generate() + public unsafe override IObservable Generate() { var bufferSize = BufferSize; return Observable.Using( () => DeviceManager.ReserveDevice(DeviceName), disposable => disposable.Subject.SelectMany(deviceInfo => { - var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2)); + var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV2e)); var probeData = deviceInfo.Context.FrameReceived.Where(frame => frame.DeviceAddress == device.Address && - NeuropixelsV2DataFrame.GetProbeIndex(frame) == (int)ProbeIndex); - return Observable.Create(observer => + NeuropixelsV2eDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); + return Observable.Create(observer => { var sampleIndex = 0; - var amplifierBuffer = new ushort[NeuropixelsV2.ChannelCount, bufferSize]; + var amplifierBuffer = new ushort[NeuropixelsV2e.ChannelCount, bufferSize]; var hubClockBuffer = new ulong[bufferSize]; var clockBuffer = new ulong[bufferSize]; @@ -39,13 +39,13 @@ public unsafe override IObservable Generate() frame => { var payload = (NeuropixelsV2Payload*)frame.Data.ToPointer(); - NeuropixelsV2DataFrame.CopyAmplifierBuffer(payload->AmplifierData, amplifierBuffer, sampleIndex); + NeuropixelsV2eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, amplifierBuffer, sampleIndex); hubClockBuffer[sampleIndex] = BitHelper.SwapEndian(payload->HubClock); clockBuffer[sampleIndex] = frame.Clock; if (++sampleIndex >= bufferSize) { var amplifierData = Mat.FromArray(amplifierBuffer); - observer.OnNext(new NeuropixelsV2DataFrame(clockBuffer, hubClockBuffer, amplifierData)); + observer.OnNext(new NeuropixelsV2eDataFrame(clockBuffer, hubClockBuffer, amplifierData)); hubClockBuffer = new ulong[bufferSize]; clockBuffer = new ulong[bufferSize]; sampleIndex = 0; diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eDataFrame.cs similarity index 91% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs rename to OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eDataFrame.cs index 9a1f9302..0a611cff 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2DataFrame.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eDataFrame.cs @@ -3,9 +3,9 @@ namespace OpenEphys.Onix { - public class NeuropixelsV2DataFrame + public class NeuropixelsV2eDataFrame { - public NeuropixelsV2DataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData) + public NeuropixelsV2eDataFrame(ulong[] clock, ulong[] hubClock, Mat amplifierData) { Clock = clock; HubClock = hubClock; @@ -27,12 +27,12 @@ internal static unsafe ushort GetProbeIndex(oni.Frame frame) internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, ushort[,] amplifierBuffer, int index) { // Loop over 16 "frames" within each "super-frame" - for (int i = 0; i < NeuropixelsV2.FramesPerSuperFrame; i++) + for (int i = 0; i < NeuropixelsV2e.FramesPerSuperFrame; i++) { // The period of ADC data within data array is 36 words - var adcDataOffset = i * NeuropixelsV2.FrameWords; + var adcDataOffset = i * NeuropixelsV2e.FrameWords; - for (int k = 0; k < NeuropixelsV2.ADCsPerProbe; k++) + for (int k = 0; k < NeuropixelsV2e.ADCsPerProbe; k++) { amplifierBuffer[RawToChannel[k, i], index] = amplifierData[ADCIndices[k] + adcDataOffset]; } @@ -96,6 +96,6 @@ unsafe struct NeuropixelsV2Payload public ulong HubClock; public ushort ProbeIndex; public ulong Reserved; - public fixed ushort AmplifierData[NeuropixelsV2.ChannelCount]; + public fixed ushort AmplifierData[NeuropixelsV2e.ChannelCount]; } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Metadata.cs b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eMetadata.cs similarity index 90% rename from OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Metadata.cs rename to OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eMetadata.cs index c465b85c..85f6535f 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2Metadata.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/NeuropixelsV2eMetadata.cs @@ -2,7 +2,7 @@ namespace OpenEphys.Onix { - class NeuropixelsV2Metadata + class NeuropixelsV2eMetadata { const uint OFFSET_ID = 0; const uint OFFSET_VERSION = 10; @@ -10,9 +10,9 @@ class NeuropixelsV2Metadata const uint OFFSET_FLEXPN = 20; const uint OFFSET_PROBEPN = 40; - public NeuropixelsV2Metadata(I2CRegisterContext serializer) + public NeuropixelsV2eMetadata(I2CRegisterContext serializer) { - var flexI2C = new I2CRegisterContext(serializer, NeuropixelsV2.FlexEEPROMAddress); + var flexI2C = new I2CRegisterContext(serializer, NeuropixelsV2e.FlexEEPROMAddress); try { var sn = flexI2C.ReadBytes(OFFSET_ID, 8);