diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs index 4f4fb519..281b25a7 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs @@ -190,7 +190,7 @@ public override IObservable Process(IObservable source TurnOnLed(serializer, NeuropixelsV1e.DefaultGPO32Config); } - var deviceInfo = new NeuropixelsV1eDeviceInfo(context, DeviceType, deviceAddress, probeControl, invertPolarity); + var deviceInfo = new NeuropixelsV1eDeviceInfo(context, DeviceType, deviceAddress, probeControl, invertPolarity, ProbeConfiguration); var shutdown = Disposable.Create(() => { serializer.WriteByte((uint)DS90UB933SerializerI2CRegister.Gpio10, NeuropixelsV1e.DefaultGPO10Config); diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV1f.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV1f.cs index e4be88ff..66a240d6 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV1f.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV1f.cs @@ -172,7 +172,9 @@ public override IObservable Process(IObservable source probeControl.WriteShiftRegisters(); } - return DeviceManager.RegisterDevice(deviceName, device, DeviceType); + var deviceInfo = new NeuropixelsV1fDeviceInfo(context, DeviceType, deviceAddress, ProbeConfiguration); + + return DeviceManager.RegisterDevice(deviceName, deviceInfo); }); } } diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs index b15c98ed..1ab21793 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs @@ -174,7 +174,7 @@ public override IObservable Process(IObservable source // disconnect i2c bus from both probes to prevent digital interference during acquisition SelectProbe(serializer, NeuropixelsV2e.NoProbeSelected); - var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, invertPolarity, probeAMetadata, probeBMetadata); + var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, invertPolarity, probeAMetadata, probeBMetadata, ProbeConfigurationA, ProbeConfigurationB); var shutdown = Disposable.Create(() => { serializer.WriteByte((uint)DS90UB933SerializerI2CRegister.Gpio10, NeuropixelsV2e.DefaultGPO10Config); diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs index 622c9d90..8687ffc1 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs @@ -214,7 +214,7 @@ public override IObservable Process(IObservable source // Still its good to get them roughly (i.e. within 10 PCLKs) started at the same time. SyncProbes(serializer, gpo10Config); - var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, invertPolarity, probeAMetadata, probeBMetadata); + var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, invertPolarity, probeAMetadata, probeBMetadata, ProbeConfigurationA, ProbeConfigurationB); var shutdown = Disposable.Create(() => { serializer.WriteByte((uint)DS90UB933SerializerI2CRegister.Gpio10, NeuropixelsV2eBeta.DefaultGPO10Config); diff --git a/OpenEphys.Onix1/Neuropixels.cs b/OpenEphys.Onix1/Neuropixels.cs new file mode 100644 index 00000000..97b7a1ee --- /dev/null +++ b/OpenEphys.Onix1/Neuropixels.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OpenEphys.Onix1 +{ + static class Neuropixels + { + internal static int[,] OrderChannelsByDepth(Electrode[] channelMap, int[,] rawToChannel) + { + int adcIndices = rawToChannel.GetLength(0); + int frameIndices = rawToChannel.GetLength(1); + + // NB: Create reverse lookup table where the channel number is used to find the ADC index / frame index + var channelToPosition = new Dictionary(); + for (int adc = 0; adc < adcIndices; adc++) + { + for (int frame = 0; frame < frameIndices; frame++) + { + channelToPosition[rawToChannel[adc, frame]] = (adc, frame); + } + } + + var spatiallyOrdered = channelMap + .OrderBy(x => x.Position.Y) + .ThenBy(x => x.Position.X) + .ToArray(); + + // NB: Populate the array with the spatially ordered channel indices by grabbing the original ADC index / + // frame index for that electrode channel number, and writing the new channel number at that index. + // Example: + // rawToChannel = [0, 2, 4; 1, 3, 5] // Channels are in one column, in order 0 -> 2 -> 4 -> 1 -> 3 -> 5 + // spatialRawToChannel = [0, 1, 2; 3, 4, 5] + // + // Now, channel 2 is at index 1 in the data frame, channel 4 is index 2, channel 1 is index 3, etc. + var spatialRawToChannel = new int[adcIndices, frameIndices]; + int index = 0; + + foreach (var e in spatiallyOrdered) + { + var (origAdcIndex, origFrameIndex) = channelToPosition[e.Channel]; + + spatialRawToChannel[origAdcIndex, origFrameIndex] = index++; + } + + return spatialRawToChannel; + } + } +} diff --git a/OpenEphys.Onix1/NeuropixelsV1eData.cs b/OpenEphys.Onix1/NeuropixelsV1eData.cs index c42de3fd..2949b018 100644 --- a/OpenEphys.Onix1/NeuropixelsV1eData.cs +++ b/OpenEphys.Onix1/NeuropixelsV1eData.cs @@ -44,6 +44,18 @@ public int BufferSize set => bufferSize = (int)(Math.Ceiling((double)value / NeuropixelsV1.FramesPerRoundRobin) * NeuropixelsV1.FramesPerRoundRobin); } + /// + /// Gets or sets a boolean value that controls if the channels are ordered by depth. + /// + /// + /// If is false, then channels are ordered from 0 to 383. + /// If is true, then channels are ordered based on the depth + /// of the electrodes. + /// + [Description("Determines if the channels are returned ordered by depth.")] + [Category(DeviceFactory.ConfigurationCategory)] + public bool OrderByDepth { get; set; } = false; + /// /// Generates a sequence of objects. /// @@ -52,6 +64,7 @@ public unsafe override IObservable Generate() { var spikeBufferSize = BufferSize; var lfpBufferSize = spikeBufferSize / NeuropixelsV1.FramesPerRoundRobin; + var orderByDepth = OrderByDepth; return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => { @@ -59,6 +72,7 @@ public unsafe override IObservable Generate() var device = info.GetDeviceContext(typeof(NeuropixelsV1e)); var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x)); var probeData = device.Context.GetDeviceFrames(passthrough.Address); + int[,] channelOrder = orderByDepth ? Neuropixels.OrderChannelsByDepth(info.ProbeConfiguration.ChannelMap, RawToChannel) : RawToChannel; return Observable.Create(observer => { @@ -73,7 +87,7 @@ public unsafe override IObservable Generate() frame => { var payload = (NeuropixelsV1ePayload*)frame.Data.ToPointer(); - NeuropixelsV1eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, frameCountBuffer, spikeBuffer, lfpBuffer, sampleIndex, info.ApGainCorrection, info.LfpGainCorrection, info.AdcThresholds, info.AdcOffsets, info.InvertPolarity); + NeuropixelsV1eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, frameCountBuffer, spikeBuffer, lfpBuffer, sampleIndex, info.ApGainCorrection, info.LfpGainCorrection, info.AdcThresholds, info.AdcOffsets, info.InvertPolarity, channelOrder); hubClockBuffer[sampleIndex] = payload->HubClock; clockBuffer[sampleIndex] = frame.Clock; if (++sampleIndex >= spikeBufferSize) @@ -93,5 +107,43 @@ public unsafe override IObservable Generate() }); }); } + + // ADC to channel + // First dimension: ADC index + // Second dimension: frame index within super frame + // Output: channel number + static readonly int[,] RawToChannel = { + {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22 }, + {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 }, + {24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46 }, + {25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47 }, + {48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70 }, + {49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71 }, + {72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 }, + {73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 }, + {96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118 }, + {97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119 }, + {120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142 }, + {121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143 }, + {144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166 }, + {145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167 }, + {168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 }, + {169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 }, + {192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214 }, + {193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215 }, + {216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238 }, + {217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239 }, + {240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262 }, + {241, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263 }, + {264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 }, + {265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 }, + {288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310 }, + {289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311 }, + {312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334 }, + {313, 315, 317, 319, 321, 323, 325, 327, 329, 331, 333, 335 }, + {336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358 }, + {337, 339, 341, 343, 345, 347, 349, 351, 353, 355, 357, 359 }, + {360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 }, + {361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 } }; } } diff --git a/OpenEphys.Onix1/NeuropixelsV1eDataFrame.cs b/OpenEphys.Onix1/NeuropixelsV1eDataFrame.cs index 82b71024..2adcbf77 100644 --- a/OpenEphys.Onix1/NeuropixelsV1eDataFrame.cs +++ b/OpenEphys.Onix1/NeuropixelsV1eDataFrame.cs @@ -4,7 +4,7 @@ namespace OpenEphys.Onix1 { class NeuropixelsV1eDataFrame { - internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] frameCountBuffer, ushort[,] spikeBuffer, ushort[,] lfpBuffer, int index, double apGainCorrection, double lfpGainCorrection, ushort[] thresholds, ushort[] offsets, bool invertPolarity) + internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] frameCountBuffer, ushort[,] spikeBuffer, ushort[,] lfpBuffer, int index, double apGainCorrection, double lfpGainCorrection, ushort[] thresholds, ushort[] offsets, bool invertPolarity, int[,] channelOrder) { var frameCountStartIndex = index * NeuropixelsV1.FramesPerSuperFrame; frameCountBuffer[frameCountStartIndex] = (amplifierData[31] << 10) | (amplifierData[39] << 0); @@ -23,7 +23,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra for (int k = 0; k < NeuropixelsV1.AdcCount; k++) { var a = amplifierData[adcToFrameIndex[k]]; - lfpBuffer[RawToChannel[k, lfpFrameIndex], lfpBufferIndex] = (ushort)(lfpInversionOffset - lfpGainCorrection * (a > thresholds[k] ? a - offsets[k] : a)); + lfpBuffer[channelOrder[k, lfpFrameIndex], lfpBufferIndex] = (ushort)(lfpInversionOffset - lfpGainCorrection * (a > thresholds[k] ? a - offsets[k] : a)); } // Loop over 12 AP frames within each "super-frame" @@ -35,7 +35,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra for (int k = 0; k < NeuropixelsV1.AdcCount; k++) { var a = amplifierData[adcToFrameIndex[k] + adcDataOffset]; - spikeBuffer[RawToChannel[k, i], index] = (ushort)(apInversionOffset - apGainCorrection * (a > thresholds[k] ? a - offsets[k] : a)); + spikeBuffer[channelOrder[k, i], index] = (ushort)(apInversionOffset - apGainCorrection * (a > thresholds[k] ? a - offsets[k] : a)); } frameCountBuffer[frameCountStartIndex + i + 1] = (amplifierData[adcDataOffset + 31] << 10) | (amplifierData[adcDataOffset + 39] << 0); @@ -46,7 +46,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra for (int k = 0; k < NeuropixelsV1.AdcCount; k++) { var a = amplifierData[adcToFrameIndex[k]]; - lfpBuffer[RawToChannel[k, lfpFrameIndex], lfpBufferIndex] = (ushort)(lfpGainCorrection * (a > thresholds[k] ? a - offsets[k] : a)); + lfpBuffer[channelOrder[k, lfpFrameIndex], lfpBufferIndex] = (ushort)(lfpGainCorrection * (a > thresholds[k] ? a - offsets[k] : a)); } // Loop over 12 AP frames within each "super-frame" @@ -58,7 +58,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra for (int k = 0; k < NeuropixelsV1.AdcCount; k++) { var a = amplifierData[adcToFrameIndex[k] + adcDataOffset]; - spikeBuffer[RawToChannel[k, i], index] = (ushort)(apGainCorrection * (a > thresholds[k] ? a - offsets[k] : a)); + spikeBuffer[channelOrder[k, i], index] = (ushort)(apGainCorrection * (a > thresholds[k] ? a - offsets[k] : a)); } frameCountBuffer[frameCountStartIndex + i + 1] = (amplifierData[adcDataOffset + 31] << 10) | (amplifierData[adcDataOffset + 39] << 0); @@ -76,44 +76,6 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra 5, 13, 21, 29, 37, 6, 14, 22, 30, 38, 7, 15 }; - - // ADC to channel - // First dimension: ADC index - // Second dimension: frame index within super frame - // Output: channel number - static readonly int[,] RawToChannel = { - {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22 }, - {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 }, - {24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46 }, - {25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47 }, - {48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70 }, - {49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71 }, - {72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 }, - {73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 }, - {96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118 }, - {97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119 }, - {120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142 }, - {121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143 }, - {144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166 }, - {145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167 }, - {168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 }, - {169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 }, - {192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214 }, - {193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215 }, - {216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238 }, - {217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239 }, - {240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262 }, - {241, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263 }, - {264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 }, - {265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 }, - {288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310 }, - {289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311 }, - {312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334 }, - {313, 315, 317, 319, 321, 323, 325, 327, 329, 331, 333, 335 }, - {336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358 }, - {337, 339, 341, 343, 345, 347, 349, 351, 353, 355, 357, 359 }, - {360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 }, - {361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 } }; } [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/OpenEphys.Onix1/NeuropixelsV1eDeviceInfo.cs b/OpenEphys.Onix1/NeuropixelsV1eDeviceInfo.cs index 50a54f5e..30eb5685 100644 --- a/OpenEphys.Onix1/NeuropixelsV1eDeviceInfo.cs +++ b/OpenEphys.Onix1/NeuropixelsV1eDeviceInfo.cs @@ -4,7 +4,7 @@ namespace OpenEphys.Onix1 { class NeuropixelsV1eDeviceInfo : DeviceInfo { - public NeuropixelsV1eDeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, NeuropixelsV1eRegisterContext probeControl, bool invertPolarity) + public NeuropixelsV1eDeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, NeuropixelsV1eRegisterContext probeControl, bool invertPolarity, NeuropixelsV1ProbeConfiguration probeConfiguration) : base(context, deviceType, deviceAddress) { ApGainCorrection = probeControl.ApGainCorrection; @@ -12,6 +12,7 @@ public NeuropixelsV1eDeviceInfo(ContextTask context, Type deviceType, uint devic AdcThresholds = probeControl.AdcThresholds; AdcOffsets = probeControl.AdcOffsets; InvertPolarity = invertPolarity; + ProbeConfiguration = probeConfiguration; } public double ApGainCorrection { get; } @@ -19,5 +20,6 @@ public NeuropixelsV1eDeviceInfo(ContextTask context, Type deviceType, uint devic public ushort[] AdcThresholds { get; } public ushort[] AdcOffsets { get; } public bool InvertPolarity { get; } + public NeuropixelsV1ProbeConfiguration ProbeConfiguration { get; } } } diff --git a/OpenEphys.Onix1/NeuropixelsV1fData.cs b/OpenEphys.Onix1/NeuropixelsV1fData.cs index bd072e54..b1c4b8c5 100644 --- a/OpenEphys.Onix1/NeuropixelsV1fData.cs +++ b/OpenEphys.Onix1/NeuropixelsV1fData.cs @@ -42,6 +42,18 @@ public int BufferSize set => bufferSize = (int)(Math.Ceiling((double)value / NeuropixelsV1.FramesPerRoundRobin) * NeuropixelsV1.FramesPerRoundRobin); } + /// + /// Gets or sets a boolean value that controls if the channels are ordered by depth. + /// + /// + /// If is false, then channels are ordered from 0 to 383. + /// If is true, then channels are ordered based on the depth + /// of the electrodes. + /// + [Description("Determines if the channels are returned ordered by depth.")] + [Category(DeviceFactory.ConfigurationCategory)] + public bool OrderByDepth { get; set; } = false; + /// /// Generates a sequence of objects. /// @@ -50,24 +62,27 @@ public unsafe override IObservable Generate() { var spikeBufferSize = BufferSize; var lfpBufferSize = spikeBufferSize / NeuropixelsV1.FramesPerRoundRobin; - var bufferSize = BufferSize; + var orderByDepth = OrderByDepth; + return DeviceManager.GetDevice(DeviceName).SelectMany( deviceInfo => Observable.Create(observer => { var sampleIndex = 0; - var device = deviceInfo.GetDeviceContext(typeof(NeuropixelsV1f)); + var info = (NeuropixelsV1fDeviceInfo)deviceInfo; + var device = info.GetDeviceContext(typeof(NeuropixelsV1f)); var spikeBuffer = new ushort[NeuropixelsV1.ChannelCount, spikeBufferSize]; var lfpBuffer = new ushort[NeuropixelsV1.ChannelCount, lfpBufferSize]; var frameCountBuffer = new int[spikeBufferSize * NeuropixelsV1.FramesPerSuperFrame]; var hubClockBuffer = new ulong[spikeBufferSize]; var clockBuffer = new ulong[spikeBufferSize]; + int[,] channelOrder = orderByDepth ? Neuropixels.OrderChannelsByDepth(info.ProbeConfiguration.ChannelMap, RawToChannel) : RawToChannel; var frameObserver = Observer.Create( frame => { var payload = (NeuropixelsV1fPayload*)frame.Data.ToPointer(); - NeuropixelsV1fDataFrame.CopyAmplifierBuffer(payload->AmplifierData, frameCountBuffer, spikeBuffer, lfpBuffer, sampleIndex); + NeuropixelsV1fDataFrame.CopyAmplifierBuffer(payload->AmplifierData, frameCountBuffer, spikeBuffer, lfpBuffer, sampleIndex, channelOrder); hubClockBuffer[sampleIndex] = payload->HubClock; clockBuffer[sampleIndex] = frame.Clock; @@ -89,5 +104,43 @@ public unsafe override IObservable Generate() .SubscribeSafe(frameObserver); })); } + + // ADC to channel + // First dimension: ADC index + // Second dimension: frame index within super frame + // Output: channel number + static readonly int[,] RawToChannel = { + {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22 }, + {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 }, + {24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46 }, + {25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47 }, + {48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70 }, + {49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71 }, + {72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 }, + {73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 }, + {96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118 }, + {97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119 }, + {120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142 }, + {121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143 }, + {144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166 }, + {145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167 }, + {168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 }, + {169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 }, + {192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214 }, + {193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215 }, + {216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238 }, + {217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239 }, + {240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262 }, + {241, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263 }, + {264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 }, + {265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 }, + {288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310 }, + {289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311 }, + {312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334 }, + {313, 315, 317, 319, 321, 323, 325, 327, 329, 331, 333, 335 }, + {336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358 }, + {337, 339, 341, 343, 345, 347, 349, 351, 353, 355, 357, 359 }, + {360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 }, + {361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 } }; } } diff --git a/OpenEphys.Onix1/NeuropixelsV1fDataFrame.cs b/OpenEphys.Onix1/NeuropixelsV1fDataFrame.cs index 6a79a9b4..221b3b37 100644 --- a/OpenEphys.Onix1/NeuropixelsV1fDataFrame.cs +++ b/OpenEphys.Onix1/NeuropixelsV1fDataFrame.cs @@ -3,7 +3,7 @@ namespace OpenEphys.Onix1 { class NeuropixelsV1fDataFrame { - internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] frameCountBuffer, ushort[,] spikeBuffer, ushort[,] lfpBuffer, int index) + internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] frameCountBuffer, ushort[,] spikeBuffer, ushort[,] lfpBuffer, int index, int[,] channelOrder) { var frameCountStartIndex = index * NeuropixelsV1.FramesPerSuperFrame; frameCountBuffer[frameCountStartIndex] = (amplifierData[FrameCounterMsbIndex] << 10) | (amplifierData[FrameCounterLsbIndex] << 0); @@ -16,7 +16,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra for (int k = 0; k < NeuropixelsV1.AdcCount; k++) { // TODO: Why would I not do this bit shift ont the FPGA?? - lfpBuffer[RawToChannel[k, lfpFrameIndex], lfpBufferIndex] = (ushort)(amplifierData[AdcToFrameIndex[k]] >> 5); // Q11.5 -> Q11.0 + lfpBuffer[channelOrder[k, lfpFrameIndex], lfpBufferIndex] = (ushort)(amplifierData[AdcToFrameIndex[k]] >> 5); // Q11.5 -> Q11.0 } // Loop over 12 AP frames within each "super-frame" @@ -27,7 +27,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra for (int k = 0; k < NeuropixelsV1.AdcCount; k++) { - spikeBuffer[RawToChannel[k, i], index] = (ushort)(amplifierData[AdcToFrameIndex[k] + adcDataOffset] >> 5); // Q11.5 -> Q11.0 + spikeBuffer[channelOrder[k, i], index] = (ushort)(amplifierData[AdcToFrameIndex[k] + adcDataOffset] >> 5); // Q11.5 -> Q11.0 } frameCountBuffer[frameCountStartIndex + i + 1] = @@ -48,44 +48,6 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra 5, 12, 19, 26, 33, 6, 13, 20, 27, 34, 7, 14}; - - // ADC to channel - // First dimension: ADC index - // Second dimension: frame index within super frame - // Output: channel number - static readonly int[,] RawToChannel = { - {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22 }, - {1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 }, - {24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46 }, - {25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47 }, - {48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70 }, - {49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71 }, - {72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 }, - {73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 }, - {96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118 }, - {97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119 }, - {120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142 }, - {121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143 }, - {144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166 }, - {145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167 }, - {168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 }, - {169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 }, - {192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214 }, - {193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215 }, - {216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238 }, - {217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239 }, - {240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262 }, - {241, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263 }, - {264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 }, - {265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 }, - {288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310 }, - {289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311 }, - {312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334 }, - {313, 315, 317, 319, 321, 323, 325, 327, 329, 331, 333, 335 }, - {336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358 }, - {337, 339, 341, 343, 345, 347, 349, 351, 353, 355, 357, 359 }, - {360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 }, - {361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 } }; } [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/OpenEphys.Onix1/NeuropixelsV1fDeviceInfo.cs b/OpenEphys.Onix1/NeuropixelsV1fDeviceInfo.cs new file mode 100644 index 00000000..5978b7be --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV1fDeviceInfo.cs @@ -0,0 +1,15 @@ +using System; + +namespace OpenEphys.Onix1 +{ + class NeuropixelsV1fDeviceInfo : DeviceInfo + { + public NeuropixelsV1fDeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, NeuropixelsV1ProbeConfiguration probeConfiguration) + : base(context, deviceType, deviceAddress) + { + ProbeConfiguration = probeConfiguration; + } + + public NeuropixelsV1ProbeConfiguration ProbeConfiguration { get; } + } +} diff --git a/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs b/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs index b347ceb4..d2299291 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs @@ -41,6 +41,18 @@ public class NeuropixelsV2eBetaData : Source [Category(DeviceFactory.ConfigurationCategory)] public NeuropixelsV2Probe ProbeIndex { get; set; } + /// + /// Gets or sets a boolean value that controls if the channels are ordered by depth. + /// + /// + /// If is false, then channels are ordered from 0 to 383. + /// If is true, then channels are ordered based on the depth + /// of the electrodes. + /// + [Description("Determines if the channels are returned ordered by depth.")] + [Category(DeviceFactory.ConfigurationCategory)] + public bool OrderByDepth { get; set; } = false; + /// /// Generates a sequence of objects. /// @@ -51,10 +63,10 @@ public unsafe override IObservable Generate() return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => { var info = (NeuropixelsV2eDeviceInfo)deviceInfo; - var (metadata, gainCorrection) = ProbeIndex switch + var (gainCorrection, probeConfiguration, metadata) = ProbeIndex switch { - NeuropixelsV2Probe.ProbeA => (info.ProbeMetadataA, info.GainCorrectionA), - NeuropixelsV2Probe.ProbeB => (info.ProbeMetadataB, info.GainCorrectionB), + NeuropixelsV2Probe.ProbeA => (info.GainCorrectionA, info.ProbeConfigurationA, info.ProbeMetadataA), + NeuropixelsV2Probe.ProbeB => (info.GainCorrectionB, info.ProbeConfigurationB, info.ProbeMetadataB), _ => throw new InvalidEnumArgumentException($"Unexpected {nameof(ProbeIndex)} value: {ProbeIndex}") }; @@ -73,6 +85,7 @@ public unsafe override IObservable Generate() .GetDeviceFrames(passthrough.Address) .Where(frame => NeuropixelsV2eBetaDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); var invertPolarity = info.InvertPolarity; + var orderByDepth = OrderByDepth; return Observable.Create(observer => { @@ -81,12 +94,13 @@ public unsafe override IObservable Generate() var frameCounter = new int[NeuropixelsV2eBeta.FramesPerSuperFrame * bufferSize]; var hubClockBuffer = new ulong[bufferSize]; var clockBuffer = new ulong[bufferSize]; + int[,] channelOrder = orderByDepth ? Neuropixels.OrderChannelsByDepth(probeConfiguration.ChannelMap, RawToChannel) : RawToChannel; var frameObserver = Observer.Create( frame => { var payload = (NeuropixelsV2BetaPayload*)frame.Data.ToPointer(); - NeuropixelsV2eBetaDataFrame.CopyAmplifierBuffer(payload->SuperFrame, amplifierBuffer, frameCounter, sampleIndex, gainCorrection.Value, invertPolarity); + NeuropixelsV2eBetaDataFrame.CopyAmplifierBuffer(payload->SuperFrame, amplifierBuffer, frameCounter, sampleIndex, gainCorrection.Value, invertPolarity, channelOrder); hubClockBuffer[sampleIndex] = payload->HubClock; clockBuffer[sampleIndex] = frame.Clock; if (++sampleIndex >= bufferSize) @@ -110,5 +124,35 @@ public unsafe override IObservable Generate() }); }); } + + // 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 + }; } } diff --git a/OpenEphys.Onix1/NeuropixelsV2eBetaDataFrame.cs b/OpenEphys.Onix1/NeuropixelsV2eBetaDataFrame.cs index 37ebf3cb..414bccdc 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eBetaDataFrame.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eBetaDataFrame.cs @@ -55,7 +55,7 @@ internal static unsafe ushort GetProbeIndex(oni.Frame frame) return data->ProbeIndex; } - internal static unsafe void CopyAmplifierBuffer(ushort* superFrame, ushort[,] amplifierBuffer, int[] frameCounter, int index, double gainCorrection, bool invertPolarity) + internal static unsafe void CopyAmplifierBuffer(ushort* superFrame, ushort[,] amplifierBuffer, int[] frameCounter, int index, double gainCorrection, bool invertPolarity, int[,] channelOrder) { if (invertPolarity) { @@ -75,7 +75,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* superFrame, ushort[,] am // Loop over ADC samples within each "frame" and map to channel position for (var k = 0; k < NeuropixelsV2eBeta.ADCsPerProbe; k++) { - amplifierBuffer[RawToChannel[k, i], index] = (ushort)(inversionOffset - gainCorrection * superFrame[adcDataOffset + k]); + amplifierBuffer[channelOrder[k, i], index] = (ushort)(inversionOffset - gainCorrection * superFrame[adcDataOffset + k]); } } } @@ -94,41 +94,11 @@ internal static unsafe void CopyAmplifierBuffer(ushort* superFrame, ushort[,] am // Loop over ADC samples within each "frame" and map to channel position for (var k = 0; k < NeuropixelsV2eBeta.ADCsPerProbe; k++) { - amplifierBuffer[RawToChannel[k, i], index] = (ushort)(gainCorrection * superFrame[adcDataOffset + k]); + amplifierBuffer[channelOrder[k, i], index] = (ushort)(gainCorrection * 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)] diff --git a/OpenEphys.Onix1/NeuropixelsV2eData.cs b/OpenEphys.Onix1/NeuropixelsV2eData.cs index 12625c9d..b794d96e 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eData.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eData.cs @@ -45,6 +45,18 @@ public class NeuropixelsV2eData : Source [Category(DeviceFactory.ConfigurationCategory)] public NeuropixelsV2Probe ProbeIndex { get; set; } + /// + /// Gets or sets a boolean value that controls if the channels are ordered by depth. + /// + /// + /// If is false, then channels are ordered from 0 to 383. + /// If is true, then channels are ordered based on the depth + /// of the electrodes. + /// + [Description("Determines if the channels are returned ordered by depth.")] + [Category(DeviceFactory.ConfigurationCategory)] + public bool OrderByDepth { get; set; } = false; + /// /// Generates a sequence of NeuropixelsV2eDataFrames. /// @@ -55,10 +67,10 @@ public unsafe override IObservable Generate() return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => { var info = (NeuropixelsV2eDeviceInfo)deviceInfo; - var (metadata, gainCorrection) = ProbeIndex switch + var (gainCorrection, probeConfiguration, metadata) = ProbeIndex switch { - NeuropixelsV2Probe.ProbeA => (info.ProbeMetadataA, info.GainCorrectionA), - NeuropixelsV2Probe.ProbeB => (info.ProbeMetadataB, info.GainCorrectionB), + NeuropixelsV2Probe.ProbeA => (info.GainCorrectionA, info.ProbeConfigurationA, info.ProbeMetadataA), + NeuropixelsV2Probe.ProbeB => (info.GainCorrectionB, info.ProbeConfigurationB, info.ProbeMetadataB), _ => throw new InvalidEnumArgumentException($"Unexpected {nameof(ProbeIndex)} value: {ProbeIndex}") }; @@ -77,6 +89,7 @@ public unsafe override IObservable Generate() .GetDeviceFrames(passthrough.Address) .Where(frame => NeuropixelsV2eDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); var invertPolarity = info.InvertPolarity; + var orderByDepth = OrderByDepth; return Observable.Create(observer => { @@ -84,12 +97,13 @@ public unsafe override IObservable Generate() var amplifierBuffer = new ushort[NeuropixelsV2e.ChannelCount, bufferSize]; var hubClockBuffer = new ulong[bufferSize]; var clockBuffer = new ulong[bufferSize]; + int[,] channelOrder = orderByDepth ? Neuropixels.OrderChannelsByDepth(probeConfiguration.ChannelMap, RawToChannel) : RawToChannel; var frameObserver = Observer.Create( frame => { var payload = (NeuropixelsV2Payload*)frame.Data.ToPointer(); - NeuropixelsV2eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, amplifierBuffer, sampleIndex, gainCorrection.Value, invertPolarity); + NeuropixelsV2eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, amplifierBuffer, sampleIndex, gainCorrection.Value, invertPolarity, channelOrder); hubClockBuffer[sampleIndex] = payload->HubClock; clockBuffer[sampleIndex] = frame.Clock; if (++sampleIndex >= bufferSize) @@ -107,5 +121,39 @@ public unsafe override IObservable Generate() }); }); } + + 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 + }; } } diff --git a/OpenEphys.Onix1/NeuropixelsV2eDataFrame.cs b/OpenEphys.Onix1/NeuropixelsV2eDataFrame.cs index b560b27f..1e603cd3 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eDataFrame.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eDataFrame.cs @@ -42,7 +42,7 @@ internal static unsafe ushort GetProbeIndex(oni.Frame frame) return data->ProbeIndex; } - internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, ushort[,] amplifierBuffer, int index, double gainCorrection, bool invertPolarity) + internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, ushort[,] amplifierBuffer, int index, double gainCorrection, bool invertPolarity, int[,] channelOrder) { if (invertPolarity) { @@ -57,7 +57,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, ushort[,] for (int k = 0; k < NeuropixelsV2e.AdcsPerProbe; k++) { - amplifierBuffer[RawToChannel[k, i], index] = (ushort)(inversionOffset - gainCorrection * amplifierData[AdcIndicies[k] + adcDataOffset]); + amplifierBuffer[channelOrder[k, i], index] = (ushort)(inversionOffset - gainCorrection * amplifierData[AdcIndicies[k] + adcDataOffset]); } } } @@ -71,7 +71,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, ushort[,] for (int k = 0; k < NeuropixelsV2e.AdcsPerProbe; k++) { - amplifierBuffer[RawToChannel[k, i], index] = (ushort)(gainCorrection * amplifierData[AdcIndicies[k] + adcDataOffset]); + amplifierBuffer[channelOrder[k, i], index] = (ushort)(gainCorrection * amplifierData[AdcIndicies[k] + adcDataOffset]); } } } @@ -91,41 +91,6 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, ushort[,] 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)] diff --git a/OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs b/OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs index 32be6ee8..1c4a3677 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs @@ -4,7 +4,7 @@ namespace OpenEphys.Onix1 { class NeuropixelsV2eDeviceInfo : DeviceInfo { - public NeuropixelsV2eDeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, double? gainCorrectionA, double? gainCorrectionB, bool invertPolarity, INeuropixelsV2eMetadata probeMetadataA, INeuropixelsV2eMetadata probeMetadataB) + public NeuropixelsV2eDeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, double? gainCorrectionA, double? gainCorrectionB, bool invertPolarity, INeuropixelsV2eMetadata probeMetadataA, INeuropixelsV2eMetadata probeMetadataB, NeuropixelsV2ProbeConfiguration probeConfigurationA, NeuropixelsV2ProbeConfiguration probeConfigurationB) : base(context, deviceType, deviceAddress) { GainCorrectionA = gainCorrectionA; @@ -12,6 +12,8 @@ public NeuropixelsV2eDeviceInfo(ContextTask context, Type deviceType, uint devic InvertPolarity = invertPolarity; ProbeMetadataA = probeMetadataA; ProbeMetadataB = probeMetadataB; + ProbeConfigurationA = probeConfigurationA; + ProbeConfigurationB = probeConfigurationB; } public double? GainCorrectionA { get; } @@ -23,5 +25,9 @@ public NeuropixelsV2eDeviceInfo(ContextTask context, Type deviceType, uint devic public INeuropixelsV2eMetadata ProbeMetadataA { get; } public INeuropixelsV2eMetadata ProbeMetadataB { get; } + + public NeuropixelsV2ProbeConfiguration ProbeConfigurationA { get; } + + public NeuropixelsV2ProbeConfiguration ProbeConfigurationB { get; } } }