Skip to content

Commit 79c3f41

Browse files
authored
Merge pull request #513 from open-ephys/issue-440
Add option to order Neuropixels channels by depth
2 parents a241bb5 + 79f7ee2 commit 79c3f41

16 files changed

+302
-173
lines changed

OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public override IObservable<ContextTask> Process(IObservable<ContextTask> source
190190
TurnOnLed(serializer, NeuropixelsV1e.DefaultGPO32Config);
191191
}
192192

193-
var deviceInfo = new NeuropixelsV1eDeviceInfo(context, DeviceType, deviceAddress, probeControl, invertPolarity);
193+
var deviceInfo = new NeuropixelsV1eDeviceInfo(context, DeviceType, deviceAddress, probeControl, invertPolarity, ProbeConfiguration);
194194
var shutdown = Disposable.Create(() =>
195195
{
196196
serializer.WriteByte((uint)DS90UB933SerializerI2CRegister.Gpio10, NeuropixelsV1e.DefaultGPO10Config);

OpenEphys.Onix1/ConfigureNeuropixelsV1f.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,9 @@ public override IObservable<ContextTask> Process(IObservable<ContextTask> source
172172
probeControl.WriteShiftRegisters();
173173
}
174174

175-
return DeviceManager.RegisterDevice(deviceName, device, DeviceType);
175+
var deviceInfo = new NeuropixelsV1fDeviceInfo(context, DeviceType, deviceAddress, ProbeConfiguration);
176+
177+
return DeviceManager.RegisterDevice(deviceName, deviceInfo);
176178
});
177179
}
178180
}

OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ public override IObservable<ContextTask> Process(IObservable<ContextTask> source
174174
// disconnect i2c bus from both probes to prevent digital interference during acquisition
175175
SelectProbe(serializer, NeuropixelsV2e.NoProbeSelected);
176176

177-
var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, invertPolarity, probeAMetadata, probeBMetadata);
177+
var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, invertPolarity, probeAMetadata, probeBMetadata, ProbeConfigurationA, ProbeConfigurationB);
178178
var shutdown = Disposable.Create(() =>
179179
{
180180
serializer.WriteByte((uint)DS90UB933SerializerI2CRegister.Gpio10, NeuropixelsV2e.DefaultGPO10Config);

OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public override IObservable<ContextTask> Process(IObservable<ContextTask> source
214214
// Still its good to get them roughly (i.e. within 10 PCLKs) started at the same time.
215215
SyncProbes(serializer, gpo10Config);
216216

217-
var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, invertPolarity, probeAMetadata, probeBMetadata);
217+
var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, invertPolarity, probeAMetadata, probeBMetadata, ProbeConfigurationA, ProbeConfigurationB);
218218
var shutdown = Disposable.Create(() =>
219219
{
220220
serializer.WriteByte((uint)DS90UB933SerializerI2CRegister.Gpio10, NeuropixelsV2eBeta.DefaultGPO10Config);

OpenEphys.Onix1/Neuropixels.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
namespace OpenEphys.Onix1
5+
{
6+
static class Neuropixels
7+
{
8+
internal static int[,] OrderChannelsByDepth(Electrode[] channelMap, int[,] rawToChannel)
9+
{
10+
int adcIndices = rawToChannel.GetLength(0);
11+
int frameIndices = rawToChannel.GetLength(1);
12+
13+
// NB: Create reverse lookup table where the channel number is used to find the ADC index / frame index
14+
var channelToPosition = new Dictionary<int, (int adcIndex, int frameIndex)>();
15+
for (int adc = 0; adc < adcIndices; adc++)
16+
{
17+
for (int frame = 0; frame < frameIndices; frame++)
18+
{
19+
channelToPosition[rawToChannel[adc, frame]] = (adc, frame);
20+
}
21+
}
22+
23+
var spatiallyOrdered = channelMap
24+
.OrderBy(x => x.Position.Y)
25+
.ThenBy(x => x.Position.X)
26+
.ToArray();
27+
28+
// NB: Populate the array with the spatially ordered channel indices by grabbing the original ADC index /
29+
// frame index for that electrode channel number, and writing the new channel number at that index.
30+
// Example:
31+
// rawToChannel = [0, 2, 4; 1, 3, 5] // Channels are in one column, in order 0 -> 2 -> 4 -> 1 -> 3 -> 5
32+
// spatialRawToChannel = [0, 1, 2; 3, 4, 5]
33+
//
34+
// Now, channel 2 is at index 1 in the data frame, channel 4 is index 2, channel 1 is index 3, etc.
35+
var spatialRawToChannel = new int[adcIndices, frameIndices];
36+
int index = 0;
37+
38+
foreach (var e in spatiallyOrdered)
39+
{
40+
var (origAdcIndex, origFrameIndex) = channelToPosition[e.Channel];
41+
42+
spatialRawToChannel[origAdcIndex, origFrameIndex] = index++;
43+
}
44+
45+
return spatialRawToChannel;
46+
}
47+
}
48+
}

OpenEphys.Onix1/NeuropixelsV1eData.cs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ public int BufferSize
4444
set => bufferSize = (int)(Math.Ceiling((double)value / NeuropixelsV1.FramesPerRoundRobin) * NeuropixelsV1.FramesPerRoundRobin);
4545
}
4646

47+
/// <summary>
48+
/// Gets or sets a boolean value that controls if the channels are ordered by depth.
49+
/// </summary>
50+
/// <remarks>
51+
/// If <see cref="OrderByDepth"/> is false, then channels are ordered from 0 to 383.
52+
/// If <see cref="OrderByDepth"/> is true, then channels are ordered based on the depth
53+
/// of the electrodes.
54+
/// </remarks>
55+
[Description("Determines if the channels are returned ordered by depth.")]
56+
[Category(DeviceFactory.ConfigurationCategory)]
57+
public bool OrderByDepth { get; set; } = false;
58+
4759
/// <summary>
4860
/// Generates a sequence of <see cref="NeuropixelsV1DataFrame"/> objects.
4961
/// </summary>
@@ -52,13 +64,15 @@ public unsafe override IObservable<NeuropixelsV1DataFrame> Generate()
5264
{
5365
var spikeBufferSize = BufferSize;
5466
var lfpBufferSize = spikeBufferSize / NeuropixelsV1.FramesPerRoundRobin;
67+
var orderByDepth = OrderByDepth;
5568

5669
return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo =>
5770
{
5871
var info = (NeuropixelsV1eDeviceInfo)deviceInfo;
5972
var device = info.GetDeviceContext(typeof(NeuropixelsV1e));
6073
var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x));
6174
var probeData = device.Context.GetDeviceFrames(passthrough.Address);
75+
int[,] channelOrder = orderByDepth ? Neuropixels.OrderChannelsByDepth(info.ProbeConfiguration.ChannelMap, RawToChannel) : RawToChannel;
6276

6377
return Observable.Create<NeuropixelsV1DataFrame>(observer =>
6478
{
@@ -73,7 +87,7 @@ public unsafe override IObservable<NeuropixelsV1DataFrame> Generate()
7387
frame =>
7488
{
7589
var payload = (NeuropixelsV1ePayload*)frame.Data.ToPointer();
76-
NeuropixelsV1eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, frameCountBuffer, spikeBuffer, lfpBuffer, sampleIndex, info.ApGainCorrection, info.LfpGainCorrection, info.AdcThresholds, info.AdcOffsets, info.InvertPolarity);
90+
NeuropixelsV1eDataFrame.CopyAmplifierBuffer(payload->AmplifierData, frameCountBuffer, spikeBuffer, lfpBuffer, sampleIndex, info.ApGainCorrection, info.LfpGainCorrection, info.AdcThresholds, info.AdcOffsets, info.InvertPolarity, channelOrder);
7791
hubClockBuffer[sampleIndex] = payload->HubClock;
7892
clockBuffer[sampleIndex] = frame.Clock;
7993
if (++sampleIndex >= spikeBufferSize)
@@ -93,5 +107,43 @@ public unsafe override IObservable<NeuropixelsV1DataFrame> Generate()
93107
});
94108
});
95109
}
110+
111+
// ADC to channel
112+
// First dimension: ADC index
113+
// Second dimension: frame index within super frame
114+
// Output: channel number
115+
static readonly int[,] RawToChannel = {
116+
{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22 },
117+
{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 },
118+
{24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46 },
119+
{25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47 },
120+
{48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70 },
121+
{49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71 },
122+
{72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 },
123+
{73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 },
124+
{96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118 },
125+
{97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119 },
126+
{120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142 },
127+
{121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143 },
128+
{144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166 },
129+
{145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167 },
130+
{168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 },
131+
{169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 },
132+
{192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214 },
133+
{193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215 },
134+
{216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238 },
135+
{217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239 },
136+
{240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262 },
137+
{241, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263 },
138+
{264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 },
139+
{265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 },
140+
{288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310 },
141+
{289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311 },
142+
{312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334 },
143+
{313, 315, 317, 319, 321, 323, 325, 327, 329, 331, 333, 335 },
144+
{336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358 },
145+
{337, 339, 341, 343, 345, 347, 349, 351, 353, 355, 357, 359 },
146+
{360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 },
147+
{361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 } };
96148
}
97149
}

OpenEphys.Onix1/NeuropixelsV1eDataFrame.cs

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace OpenEphys.Onix1
44
{
55
class NeuropixelsV1eDataFrame
66
{
7-
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)
7+
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)
88
{
99
var frameCountStartIndex = index * NeuropixelsV1.FramesPerSuperFrame;
1010
frameCountBuffer[frameCountStartIndex] = (amplifierData[31] << 10) | (amplifierData[39] << 0);
@@ -23,7 +23,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra
2323
for (int k = 0; k < NeuropixelsV1.AdcCount; k++)
2424
{
2525
var a = amplifierData[adcToFrameIndex[k]];
26-
lfpBuffer[RawToChannel[k, lfpFrameIndex], lfpBufferIndex] = (ushort)(lfpInversionOffset - lfpGainCorrection * (a > thresholds[k] ? a - offsets[k] : a));
26+
lfpBuffer[channelOrder[k, lfpFrameIndex], lfpBufferIndex] = (ushort)(lfpInversionOffset - lfpGainCorrection * (a > thresholds[k] ? a - offsets[k] : a));
2727
}
2828

2929
// Loop over 12 AP frames within each "super-frame"
@@ -35,7 +35,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra
3535
for (int k = 0; k < NeuropixelsV1.AdcCount; k++)
3636
{
3737
var a = amplifierData[adcToFrameIndex[k] + adcDataOffset];
38-
spikeBuffer[RawToChannel[k, i], index] = (ushort)(apInversionOffset - apGainCorrection * (a > thresholds[k] ? a - offsets[k] : a));
38+
spikeBuffer[channelOrder[k, i], index] = (ushort)(apInversionOffset - apGainCorrection * (a > thresholds[k] ? a - offsets[k] : a));
3939
}
4040

4141
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
4646
for (int k = 0; k < NeuropixelsV1.AdcCount; k++)
4747
{
4848
var a = amplifierData[adcToFrameIndex[k]];
49-
lfpBuffer[RawToChannel[k, lfpFrameIndex], lfpBufferIndex] = (ushort)(lfpGainCorrection * (a > thresholds[k] ? a - offsets[k] : a));
49+
lfpBuffer[channelOrder[k, lfpFrameIndex], lfpBufferIndex] = (ushort)(lfpGainCorrection * (a > thresholds[k] ? a - offsets[k] : a));
5050
}
5151

5252
// Loop over 12 AP frames within each "super-frame"
@@ -58,7 +58,7 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra
5858
for (int k = 0; k < NeuropixelsV1.AdcCount; k++)
5959
{
6060
var a = amplifierData[adcToFrameIndex[k] + adcDataOffset];
61-
spikeBuffer[RawToChannel[k, i], index] = (ushort)(apGainCorrection * (a > thresholds[k] ? a - offsets[k] : a));
61+
spikeBuffer[channelOrder[k, i], index] = (ushort)(apGainCorrection * (a > thresholds[k] ? a - offsets[k] : a));
6262
}
6363

6464
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
7676
5, 13, 21, 29, 37,
7777
6, 14, 22, 30, 38,
7878
7, 15 };
79-
80-
// ADC to channel
81-
// First dimension: ADC index
82-
// Second dimension: frame index within super frame
83-
// Output: channel number
84-
static readonly int[,] RawToChannel = {
85-
{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22 },
86-
{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 },
87-
{24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46 },
88-
{25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47 },
89-
{48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70 },
90-
{49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71 },
91-
{72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 },
92-
{73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 },
93-
{96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118 },
94-
{97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119 },
95-
{120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142 },
96-
{121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143 },
97-
{144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166 },
98-
{145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167 },
99-
{168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 },
100-
{169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 },
101-
{192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214 },
102-
{193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215 },
103-
{216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238 },
104-
{217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239 },
105-
{240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262 },
106-
{241, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263 },
107-
{264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 },
108-
{265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 },
109-
{288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310 },
110-
{289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311 },
111-
{312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334 },
112-
{313, 315, 317, 319, 321, 323, 325, 327, 329, 331, 333, 335 },
113-
{336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358 },
114-
{337, 339, 341, 343, 345, 347, 349, 351, 353, 355, 357, 359 },
115-
{360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 },
116-
{361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 } };
11779
}
11880

11981
[StructLayout(LayoutKind.Sequential, Pack = 1)]

OpenEphys.Onix1/NeuropixelsV1eDeviceInfo.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@ namespace OpenEphys.Onix1
44
{
55
class NeuropixelsV1eDeviceInfo : DeviceInfo
66
{
7-
public NeuropixelsV1eDeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, NeuropixelsV1eRegisterContext probeControl, bool invertPolarity)
7+
public NeuropixelsV1eDeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, NeuropixelsV1eRegisterContext probeControl, bool invertPolarity, NeuropixelsV1ProbeConfiguration probeConfiguration)
88
: base(context, deviceType, deviceAddress)
99
{
1010
ApGainCorrection = probeControl.ApGainCorrection;
1111
LfpGainCorrection = probeControl.LfpGainCorrection;
1212
AdcThresholds = probeControl.AdcThresholds;
1313
AdcOffsets = probeControl.AdcOffsets;
1414
InvertPolarity = invertPolarity;
15+
ProbeConfiguration = probeConfiguration;
1516
}
1617

1718
public double ApGainCorrection { get; }
1819
public double LfpGainCorrection { get; }
1920
public ushort[] AdcThresholds { get; }
2021
public ushort[] AdcOffsets { get; }
2122
public bool InvertPolarity { get; }
23+
public NeuropixelsV1ProbeConfiguration ProbeConfiguration { get; }
2224
}
2325
}

0 commit comments

Comments
 (0)