Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<UseArtifactsOutput>true</UseArtifactsOutput>
<PackageIcon>icon.png</PackageIcon>
<VersionPrefix>0.6.1</VersionPrefix>
<VersionPrefix>0.7.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
<LangVersion>10.0</LangVersion>
<Features>strict</Features>
Expand Down
48 changes: 48 additions & 0 deletions OpenEphys.Onix1/ChannelHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace OpenEphys.Onix1
{
class ChannelHelper
{
internal static int[,] OrderNeuropixelsChannelsByDepth(Electrode[] channelMap, int[,] originalOrder)
{
int rows = originalOrder.GetLength(0);
int cols = originalOrder.GetLength(1);

var channelToPosition = new Dictionary<int, (int row, int col)>();
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
channelToPosition[originalOrder[row, col]] = (row, col);
}
}

var spatiallyOrdered = channelMap
.OrderBy(x => x.Position.Y)
.ThenBy(x => x.Position.X)
.ToArray();

var spatialRawToChannel = new int[rows, cols];
int index = 0;

foreach (var e in spatiallyOrdered)
{
var (newRow, newCol) = channelToPosition[index++];
var (origRow, origCol) = channelToPosition[e.Channel];

spatialRawToChannel[origRow, origCol] = originalOrder[newRow, newCol];
}

if (spatialRawToChannel.Cast<int>().Distinct().Count() != originalOrder.Length)
{
throw new InvalidOperationException($"An error occurred reordering the channels by depth. Expected {originalOrder.Length} channels," +
$" but only found {spatialRawToChannel.Cast<int>().Distinct().Count()} unique channels.");
}

return spatialRawToChannel;
}
}
}
2 changes: 1 addition & 1 deletion OpenEphys.Onix1/ConfigureNeuropixelsV1e.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public override IObservable<ContextTask> Process(IObservable<ContextTask> 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);
Expand Down
54 changes: 53 additions & 1 deletion OpenEphys.Onix1/NeuropixelsV1eData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ public int BufferSize
set => bufferSize = (int)(Math.Ceiling((double)value / NeuropixelsV1.FramesPerRoundRobin) * NeuropixelsV1.FramesPerRoundRobin);
}

/// <summary>
/// Gets or sets a boolean value that controls if the channels are ordered by depth.
/// </summary>
/// <remarks>
/// If <see cref="OrderByDepth"/> is false, then channels are ordered from 0 to 383.
/// If <see cref="OrderByDepth"/> is true, then channels are ordered based on the depth
/// of the electrodes
/// </remarks>
[Description("Determines if the channels are returned ordered by depth.")]
[Category(DeviceFactory.ConfigurationCategory)]
public bool OrderByDepth { get; set; } = false;

/// <summary>
/// Generates a sequence of <see cref="NeuropixelsV1DataFrame"/> objects.
/// </summary>
Expand All @@ -52,13 +64,15 @@ public unsafe override IObservable<NeuropixelsV1DataFrame> Generate()
{
var spikeBufferSize = BufferSize;
var lfpBufferSize = spikeBufferSize / NeuropixelsV1.FramesPerRoundRobin;
var orderByDepth = OrderByDepth;

return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo =>
{
var info = (NeuropixelsV1eDeviceInfo)deviceInfo;
var device = info.GetDeviceContext(typeof(NeuropixelsV1e));
var passthrough = device.GetPassthroughDeviceContext(typeof(DS90UB9x));
var probeData = device.Context.GetDeviceFrames(passthrough.Address);
int[,] channelOrder = orderByDepth ? ChannelHelper.OrderNeuropixelsChannelsByDepth(info.ProbeConfiguration.ChannelMap, RawToChannel) : RawToChannel;

return Observable.Create<NeuropixelsV1DataFrame>(observer =>
{
Expand All @@ -73,7 +87,7 @@ public unsafe override IObservable<NeuropixelsV1DataFrame> 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)
Expand All @@ -93,5 +107,43 @@ public unsafe override IObservable<NeuropixelsV1DataFrame> 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 } };
}
}
48 changes: 5 additions & 43 deletions OpenEphys.Onix1/NeuropixelsV1eDataFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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"
Expand All @@ -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);
Expand All @@ -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"
Expand All @@ -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);
Expand All @@ -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)]
Expand Down
4 changes: 3 additions & 1 deletion OpenEphys.Onix1/NeuropixelsV1eDeviceInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ 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;
LfpGainCorrection = probeControl.LfpGainCorrection;
AdcThresholds = probeControl.AdcThresholds;
AdcOffsets = probeControl.AdcOffsets;
InvertPolarity = invertPolarity;
ProbeConfiguration = probeConfiguration;
}

public double ApGainCorrection { get; }
public double LfpGainCorrection { get; }
public ushort[] AdcThresholds { get; }
public ushort[] AdcOffsets { get; }
public bool InvertPolarity { get; }
public NeuropixelsV1ProbeConfiguration ProbeConfiguration { get; }
}
}