Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
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[,] OrderChannelsByDepth(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
16 changes: 15 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 ? NeuropixelsV1eDataFrame.OrderChannelsByDepth(info.ProbeConfiguration.ChannelMap) : null;

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 Down
17 changes: 12 additions & 5 deletions OpenEphys.Onix1/NeuropixelsV1eDataFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ 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 = null)
{
channelOrder ??= RawToChannel;

var frameCountStartIndex = index * NeuropixelsV1.FramesPerSuperFrame;
frameCountBuffer[frameCountStartIndex] = (amplifierData[31] << 10) | (amplifierData[39] << 0);

Expand All @@ -23,7 +25,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 +37,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 +48,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 +60,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 Down Expand Up @@ -114,6 +116,11 @@ internal static unsafe void CopyAmplifierBuffer(ushort* amplifierData, int[] fra
{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 } };

internal static int[,] OrderChannelsByDepth(Electrode[] channelMap)
{
return ChannelHelper.OrderChannelsByDepth(channelMap, RawToChannel);
}
}

[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; }
}
}
Loading