diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index 35add677..b69d801e 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -19,11 +19,21 @@ public partial class ChannelConfigurationDialog : Form internal event EventHandler OnResizeZedGraph; internal event EventHandler OnDrawProbeGroup; - internal ProbeGroup ProbeGroup; + ProbeGroup probeGroup; + + internal ProbeGroup ProbeGroup + { + get => probeGroup; + set + { + probeGroup = value; + SelectedContacts = new bool[probeGroup.NumberOfContacts]; + } + } internal readonly List ReferenceContacts = new(); - internal readonly bool[] SelectedContacts = null; + internal bool[] SelectedContacts { get; private set; } = null; [Obsolete("Designer only.", true)] ChannelConfigurationDialog() @@ -49,8 +59,6 @@ public ChannelConfigurationDialog(ProbeGroup probeGroup) ProbeGroup = probeGroup; } - SelectedContacts = new bool[ProbeGroup.NumberOfContacts]; - ReferenceContacts = new List(); zedGraphChannels.MouseDownEvent += MouseDownEvent; @@ -270,14 +278,26 @@ private void FormShown(object sender, EventArgs e) if (!TopLevel) { menuStrip.Visible = false; - ResizeZedGraph(); } + + ResizeZedGraph(); } - internal virtual bool OpenFile() where T : ProbeGroup + internal virtual bool OpenFile(Type type) { - var newConfiguration = OpenAndParseConfigurationFile(); + var newConfiguration = OpenAndParseConfigurationFile(type); + + if (newConfiguration != null) + { + ProbeGroup = newConfiguration; + return true; + } + return false; + } + + internal bool ValidateProbeGroup(ProbeGroup newConfiguration) + { if (newConfiguration == null) { return false; @@ -287,10 +307,6 @@ internal virtual bool OpenFile() where T : ProbeGroup { newConfiguration.Validate(); - ProbeGroup = newConfiguration; - DrawProbeGroup(); - RefreshZedGraph(); - return true; } else @@ -302,7 +318,7 @@ internal virtual bool OpenFile() where T : ProbeGroup } } - internal T OpenAndParseConfigurationFile() where T : ProbeGroup + internal ProbeGroup OpenAndParseConfigurationFile(Type type) { using OpenFileDialog ofd = new(); @@ -313,9 +329,19 @@ internal T OpenAndParseConfigurationFile() where T : ProbeGroup if (ofd.ShowDialog() == DialogResult.OK && File.Exists(ofd.FileName)) { - var newConfiguration = JsonHelper.DeserializeString(File.ReadAllText(ofd.FileName)); + // NB: This method is called from an open dialog; throwing an exception without handling it would close the dialog. + // Show the resulting exception as a MessageBox to warn the user of the exception with closing the whole dialog. + try + { + var newConfiguration = ProbeInterfaceHelper.LoadExternalProbeInterfaceFile(ofd.FileName, type); - return newConfiguration; + return ValidateProbeGroup(newConfiguration) ? newConfiguration : null; + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error Importing ProbeInterface File"); + return null; + } } return null; @@ -1010,9 +1036,11 @@ void ResizeAxes() private void MenuItemOpenFile(object sender, EventArgs e) { - if (OpenFile()) + if (OpenFile(ProbeGroup.GetType())) { DrawProbeGroup(); + ResetZoom(); + UpdateFontSize(); RefreshZedGraph(); } } @@ -1021,6 +1049,7 @@ private void MenuItemLoadDefaultConfig(object sender, EventArgs e) { LoadDefaultChannelLayout(); DrawProbeGroup(); + ResetZoom(); UpdateFontSize(); RefreshZedGraph(); } diff --git a/OpenEphys.Onix1.Design/INeuropixelsV2ProbeInfo.cs b/OpenEphys.Onix1.Design/INeuropixelsV2ProbeInfo.cs new file mode 100644 index 00000000..19e436e6 --- /dev/null +++ b/OpenEphys.Onix1.Design/INeuropixelsV2ProbeInfo.cs @@ -0,0 +1,15 @@ +using System; + +namespace OpenEphys.Onix1.Design +{ + interface INeuropixelsV2ProbeInfo + { + Array GetReferenceEnumValues(); + + Array GetComboBoxChannelPresets(); + + Enum CheckForExistingChannelPreset(NeuropixelsV2Electrode[] channelMap); + + NeuropixelsV2Electrode[] GetChannelPreset(Enum channelPreset); + } +} diff --git a/OpenEphys.Onix1.Design/NeuropixelsV1ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV1ChannelConfigurationDialog.cs index 41f85ce2..d2dcf8d7 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV1ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV1ChannelConfigurationDialog.cs @@ -58,12 +58,11 @@ internal override void LoadDefaultChannelLayout() OnFileOpenHandler(); } - internal override bool OpenFile() + internal override bool OpenFile(Type type) { - if (base.OpenFile()) + if (base.OpenFile(type)) { ProbeConfiguration.ProbeGroup = (NeuropixelsV1eProbeGroup)ProbeGroup; - OnFileOpenHandler(); return true; diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2QuadShankInfo.cs b/OpenEphys.Onix1.Design/NeuropixelsV2QuadShankInfo.cs new file mode 100644 index 00000000..0d96496c --- /dev/null +++ b/OpenEphys.Onix1.Design/NeuropixelsV2QuadShankInfo.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace OpenEphys.Onix1.Design +{ + internal class NeuropixelsV2QuadShankInfo : INeuropixelsV2ProbeInfo + { + const int BankDStartIndex = 896; + + enum QuadShankChannelPreset + { + Shank0BankA, + Shank0BankB, + Shank0BankC, + Shank0BankD, + Shank1BankA, + Shank1BankB, + Shank1BankC, + Shank1BankD, + Shank2BankA, + Shank2BankB, + Shank2BankC, + Shank2BankD, + Shank3BankA, + Shank3BankB, + Shank3BankC, + Shank3BankD, + AllShanks0_95, + AllShanks96_191, + AllShanks192_287, + AllShanks288_383, + AllShanks384_479, + AllShanks480_575, + AllShanks576_671, + AllShanks672_767, + AllShanks768_863, + AllShanks864_959, + AllShanks960_1055, + AllShanks1056_1151, + AllShanks1152_1247, + None + } + + IEnumerable Electrodes { get; init; } + + public NeuropixelsV2QuadShankInfo(NeuropixelsV2QuadShankProbeConfiguration probeConfiguration) + { + Electrodes = probeConfiguration.ProbeGroup.ToElectrodes(); + } + + public Array GetReferenceEnumValues() + { + return Enum.GetValues(typeof(NeuropixelsV2QuadShankReference)); + } + + public Array GetComboBoxChannelPresets() + { + return Enum.GetValues(typeof(QuadShankChannelPreset)); + } + + public Enum CheckForExistingChannelPreset(NeuropixelsV2Electrode[] channelMap) + { + static bool CheckShankBank(NeuropixelsV2Electrode[] channelMap, int shank, NeuropixelsV2Bank bank, out QuadShankChannelPreset preset) + { + preset = (shank, bank) switch + { + (0, NeuropixelsV2Bank.A) => QuadShankChannelPreset.Shank0BankA, + (0, NeuropixelsV2Bank.B) => QuadShankChannelPreset.Shank0BankB, + (0, NeuropixelsV2Bank.C) => QuadShankChannelPreset.Shank0BankC, + (1, NeuropixelsV2Bank.A) => QuadShankChannelPreset.Shank1BankA, + (1, NeuropixelsV2Bank.B) => QuadShankChannelPreset.Shank1BankB, + (1, NeuropixelsV2Bank.C) => QuadShankChannelPreset.Shank1BankC, + (2, NeuropixelsV2Bank.A) => QuadShankChannelPreset.Shank2BankA, + (2, NeuropixelsV2Bank.B) => QuadShankChannelPreset.Shank2BankB, + (2, NeuropixelsV2Bank.C) => QuadShankChannelPreset.Shank2BankC, + (3, NeuropixelsV2Bank.A) => QuadShankChannelPreset.Shank3BankA, + (3, NeuropixelsV2Bank.B) => QuadShankChannelPreset.Shank3BankB, + (3, NeuropixelsV2Bank.C) => QuadShankChannelPreset.Shank3BankC, + _ => QuadShankChannelPreset.None + }; + + return channelMap.All(e => e.Bank == bank && e.Shank == shank); + } + + static bool CheckShankBankD(NeuropixelsV2Electrode[] channelMap, int shank, out QuadShankChannelPreset preset) + { + preset = shank switch + { + 0 => QuadShankChannelPreset.Shank0BankD, + 1 => QuadShankChannelPreset.Shank1BankD, + 2 => QuadShankChannelPreset.Shank2BankD, + 3 => QuadShankChannelPreset.Shank3BankD, + _ => QuadShankChannelPreset.None + }; + + return channelMap.All(e => (e.Bank == NeuropixelsV2Bank.D || (e.Bank == NeuropixelsV2Bank.C && e.IntraShankElectrodeIndex >= BankDStartIndex)) && e.Shank == shank); + } + + for (int shank = 0; shank <= 3; shank++) + { + if (CheckShankBank(channelMap, shank, NeuropixelsV2Bank.A, out var preset)) + return preset; + + if (CheckShankBank(channelMap, shank, NeuropixelsV2Bank.B, out preset)) + return preset; + + if (CheckShankBank(channelMap, shank, NeuropixelsV2Bank.C, out preset)) + return preset; + + if (CheckShankBankD(channelMap, shank, out preset)) + return preset; + } + + var ranges = new[] + { + (0, 95, QuadShankChannelPreset.AllShanks0_95), + (192, 287, QuadShankChannelPreset.AllShanks192_287), + (288, 383, QuadShankChannelPreset.AllShanks288_383), + (394, 479, QuadShankChannelPreset.AllShanks384_479), + (480, 575, QuadShankChannelPreset.AllShanks480_575), + (576, 671, QuadShankChannelPreset.AllShanks576_671), + (672, 767, QuadShankChannelPreset.AllShanks672_767), + (768, 863, QuadShankChannelPreset.AllShanks768_863), + (864, 959, QuadShankChannelPreset.AllShanks864_959), + (960, 1055, QuadShankChannelPreset.AllShanks960_1055), + (1056, 1151, QuadShankChannelPreset.AllShanks1056_1151), + (1152, 1247, QuadShankChannelPreset.AllShanks1152_1247) + }; + + static bool CheckAllShanksRange(NeuropixelsV2Electrode[] channelMap, int start, int end) + { + return channelMap.All(e => e.Shank >= 0 && e.Shank <= 3 && + e.IntraShankElectrodeIndex >= start && + e.IntraShankElectrodeIndex <= end); + } + + foreach (var (start, end, presetValue) in ranges) + { + if (CheckAllShanksRange(channelMap, start, end)) + return presetValue; + } + + return QuadShankChannelPreset.None; + } + + public NeuropixelsV2Electrode[] GetChannelPreset(Enum channelPreset) + { + var preset = (QuadShankChannelPreset)channelPreset; + + static NeuropixelsV2Electrode[] GetAllShanks(IEnumerable electrodes, int startIndex, int endIndex) + { + return electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= startIndex && e.IntraShankElectrodeIndex <= endIndex) + || (e.Shank == 1 && e.IntraShankElectrodeIndex >= startIndex && e.IntraShankElectrodeIndex <= endIndex) + || (e.Shank == 2 && e.IntraShankElectrodeIndex >= startIndex && e.IntraShankElectrodeIndex <= endIndex) + || (e.Shank == 3 && e.IntraShankElectrodeIndex >= startIndex && e.IntraShankElectrodeIndex <= endIndex)).ToArray(); + }; + + return preset switch + { + QuadShankChannelPreset.Shank0BankA => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.A && e.Shank == 0).ToArray(), + QuadShankChannelPreset.Shank0BankB => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.B && e.Shank == 0).ToArray(), + QuadShankChannelPreset.Shank0BankC => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.C && e.Shank == 0).ToArray(), + QuadShankChannelPreset.Shank0BankD => Electrodes.Where(e => (e.Bank == NeuropixelsV2Bank.D || (e.Bank == NeuropixelsV2Bank.C && e.Index >= BankDStartIndex)) && e.Shank == 0).ToArray(), + QuadShankChannelPreset.Shank1BankA => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.A && e.Shank == 1).ToArray(), + QuadShankChannelPreset.Shank1BankB => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.B && e.Shank == 1).ToArray(), + QuadShankChannelPreset.Shank1BankC => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.C && e.Shank == 1).ToArray(), + QuadShankChannelPreset.Shank1BankD => Electrodes.Where(e => (e.Bank == NeuropixelsV2Bank.D || (e.Bank == NeuropixelsV2Bank.C && e.Index >= BankDStartIndex)) && e.Shank == 1).ToArray(), + QuadShankChannelPreset.Shank2BankA => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.A && e.Shank == 2).ToArray(), + QuadShankChannelPreset.Shank2BankB => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.B && e.Shank == 2).ToArray(), + QuadShankChannelPreset.Shank2BankC => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.C && e.Shank == 2).ToArray(), + QuadShankChannelPreset.Shank2BankD => Electrodes.Where(e => (e.Bank == NeuropixelsV2Bank.D || (e.Bank == NeuropixelsV2Bank.C && e.Index >= BankDStartIndex)) && e.Shank == 2).ToArray(), + QuadShankChannelPreset.Shank3BankA => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.A && e.Shank == 3).ToArray(), + QuadShankChannelPreset.Shank3BankB => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.B && e.Shank == 3).ToArray(), + QuadShankChannelPreset.Shank3BankC => Electrodes.Where(e => e.Bank == NeuropixelsV2Bank.C && e.Shank == 3).ToArray(), + QuadShankChannelPreset.Shank3BankD => Electrodes.Where(e => (e.Bank == NeuropixelsV2Bank.D || (e.Bank == NeuropixelsV2Bank.C && e.Index >= BankDStartIndex)) && e.Shank == 3).ToArray(), + QuadShankChannelPreset.AllShanks0_95 => GetAllShanks(Electrodes, 0, 95), + QuadShankChannelPreset.AllShanks96_191 => GetAllShanks(Electrodes, 96, 191), + QuadShankChannelPreset.AllShanks192_287 => GetAllShanks(Electrodes, 192, 287), + QuadShankChannelPreset.AllShanks288_383 => GetAllShanks(Electrodes, 288, 383), + QuadShankChannelPreset.AllShanks384_479 => GetAllShanks(Electrodes, 384, 479), + QuadShankChannelPreset.AllShanks480_575 => GetAllShanks(Electrodes, 480, 575), + QuadShankChannelPreset.AllShanks576_671 => GetAllShanks(Electrodes, 576, 671), + QuadShankChannelPreset.AllShanks672_767 => GetAllShanks(Electrodes, 672, 767), + QuadShankChannelPreset.AllShanks768_863 => GetAllShanks(Electrodes, 768, 863), + QuadShankChannelPreset.AllShanks864_959 => GetAllShanks(Electrodes, 864, 959), + QuadShankChannelPreset.AllShanks960_1055 => GetAllShanks(Electrodes, 960, 1055), + QuadShankChannelPreset.AllShanks1056_1151 => GetAllShanks(Electrodes, 1056, 1151), + QuadShankChannelPreset.AllShanks1152_1247 => GetAllShanks(Electrodes, 1152, 1247), + QuadShankChannelPreset.None => Array.Empty(), + _ => throw new InvalidEnumArgumentException($"Unknown value of {nameof(QuadShankChannelPreset)}: {channelPreset}") + }; + } + } +} diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index 32d2cd81..7c93f8e1 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Reflection; using System.Windows.Forms; using OpenEphys.ProbeInterface.NET; using ZedGraph; @@ -14,25 +15,26 @@ public partial class NeuropixelsV2eChannelConfigurationDialog : ChannelConfigura internal event EventHandler OnZoom; internal event EventHandler OnFileLoad; - /// - /// Public object that is manipulated by - /// . - /// - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfiguration; + internal NeuropixelsV2ProbeConfiguration ProbeConfiguration; + + readonly Func GetChannelNumberFunc; /// /// Initializes a new instance of . /// - /// A object holding the current configuration settings. - public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2QuadShankProbeConfiguration probeConfiguration) - : base(probeConfiguration.ProbeGroup) + /// A object holding the current configuration settings. + public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2ProbeConfiguration probeConfiguration) + : base(probeConfiguration.ProbeGroup.Clone()) { zedGraphChannels.ZoomButtons = MouseButtons.None; zedGraphChannels.ZoomButtons2 = MouseButtons.None; zedGraphChannels.ZoomStepFraction = 0.5; - ProbeConfiguration = probeConfiguration; + ProbeConfiguration = Activator.CreateInstance(probeConfiguration.GetType(), probeConfiguration) as NeuropixelsV2ProbeConfiguration; + ProbeConfiguration.ProbeGroup = (NeuropixelsV2eProbeGroup)ProbeGroup; + + GetChannelNumberFunc = ProbeConfiguration.ChannelMap[0].GetChannelNumberFunc(); HighlightEnabledContacts(); UpdateContactLabels(); @@ -42,23 +44,21 @@ public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2QuadShankProbeConfi internal override ProbeGroup DefaultChannelLayout() { - return new NeuropixelsV2eProbeGroup(); + return Activator.CreateInstance(ProbeConfiguration.ProbeGroup.GetType()) as NeuropixelsV2eProbeGroup ?? throw new InvalidOperationException("Could not create new probe group of type " + ProbeConfiguration.ProbeGroup.GetType().Name); } internal override void LoadDefaultChannelLayout() { - ProbeConfiguration = new(ProbeConfiguration.Probe, ProbeConfiguration.Reference); - ProbeGroup = ProbeConfiguration.ProbeGroup; + base.LoadDefaultChannelLayout(); OnFileOpenHandler(); } - internal override bool OpenFile() + internal override bool OpenFile(Type type) { - if (base.OpenFile()) + if (base.OpenFile(type)) { - ProbeConfiguration = new((NeuropixelsV2eProbeGroup)ProbeGroup, ProbeConfiguration.Reference, ProbeConfiguration.Probe); - + ProbeConfiguration.ProbeGroup = (NeuropixelsV2eProbeGroup)ProbeGroup; OnFileOpenHandler(); return true; @@ -106,7 +106,7 @@ internal override void DrawScale() internal override void HighlightEnabledContacts() { - if (ProbeConfiguration == null || ProbeConfiguration.ChannelMap == null) + if (ProbeConfiguration == null) return; var contactObjects = zedGraphChannels.GraphPane.GraphObjList.OfType() @@ -119,12 +119,14 @@ internal override void HighlightEnabledContacts() contact.Fill.Color = DisabledContactFill; } + var channelMap = ProbeConfiguration.ChannelMap; + var contactsToEnable = contactObjects.Where(c => - { - var tag = c.Tag as ContactTag; - var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactIndex); - return ProbeConfiguration.ChannelMap[channel].Index == tag.ContactIndex; - }); + { + var tag = c.Tag as ContactTag; + var channel = GetChannelNumberFunc(tag.ContactIndex); + return channelMap[channel].Index == tag.ContactIndex; + }); foreach (var contact in contactsToEnable) { @@ -149,12 +151,14 @@ internal override void UpdateContactLabels() textObj.FontSpec.FontColor = DisabledContactTextColor; } + var channelMap = ProbeConfiguration.ChannelMap; + textObjsToUpdate = textObjs.Where(c => - { - var tag = c.Tag as ContactTag; - var channel = NeuropixelsV2QuadShankElectrode.GetChannelNumber(tag.ContactIndex); - return ProbeConfiguration.ChannelMap[channel].Index == tag.ContactIndex; - }); + { + var tag = c.Tag as ContactTag; + var channel = GetChannelNumberFunc(tag.ContactIndex); + return channelMap[channel].Index == tag.ContactIndex; + }); foreach (var textObj in textObjsToUpdate) { @@ -167,7 +171,7 @@ internal override string ContactString(int deviceChannelIndex, int index) return index.ToString(); } - internal void EnableElectrodes(NeuropixelsV2QuadShankElectrode[] electrodes) + internal void EnableElectrodes(NeuropixelsV2Electrode[] electrodes) { ProbeConfiguration.SelectElectrodes(electrodes); } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs index 66961a6a..6a8112de 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs @@ -1,13 +1,13 @@ using System; +using System.ComponentModel; +using System.IO; using System.Linq; using System.Windows.Forms; -using System.Drawing; -using System.IO; namespace OpenEphys.Onix1.Design { /// - /// Partial class to create a GUI for . + /// Partial class to create a GUI for . /// public partial class NeuropixelsV2eProbeConfigurationDialog : Form { @@ -15,85 +15,50 @@ public partial class NeuropixelsV2eProbeConfigurationDialog : Form internal event EventHandler InvertPolarityChanged; - private enum ChannelPreset - { - Shank0BankA, - Shank0BankB, - Shank0BankC, - Shank0BankD, - Shank1BankA, - Shank1BankB, - Shank1BankC, - Shank1BankD, - Shank2BankA, - Shank2BankB, - Shank2BankC, - Shank2BankD, - Shank3BankA, - Shank3BankB, - Shank3BankC, - Shank3BankD, - AllShanks0_95, - AllShanks96_191, - AllShanks192_287, - AllShanks288_383, - AllShanks384_479, - AllShanks480_575, - AllShanks576_671, - AllShanks672_767, - AllShanks768_863, - AllShanks864_959, - AllShanks960_1055, - AllShanks1056_1151, - AllShanks1152_1247, - None - } - /// - /// Public object that is manipulated by - /// . + /// Public object that is manipulated by + /// . /// - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfiguration { get; set; } + public NeuropixelsV2ProbeConfiguration ProbeConfiguration + { + get => ChannelConfiguration.ProbeConfiguration; + } /// public bool InvertPolarity { get; set; } + INeuropixelsV2ProbeInfo ProbeData { get; set; } + /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// - /// A object holding the current configuration settings. + /// A object holding the current configuration settings. /// String containing the path to the calibration file for this probe. /// Boolean denoting whether or not to invert the polarity of neural data. - public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2QuadShankProbeConfiguration configuration, string calibrationFile, bool invertPolarity) + public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2ProbeConfiguration configuration, string calibrationFile, bool invertPolarity) { InitializeComponent(); Shown += FormShown; - ProbeConfiguration = new(configuration); - textBoxProbeCalibrationFile.Text = calibrationFile; - ChannelConfiguration = new(ProbeConfiguration) - { - TopLevel = false, - FormBorderStyle = FormBorderStyle.None, - Dock = DockStyle.Fill, - Parent = this - }; - InvertPolarity = invertPolarity; - panelProbe.Controls.Add(ChannelConfiguration); + ChannelConfiguration = new(configuration); + ChannelConfiguration.SetChildFormProperties(this).AddDialogToPanel(panelProbe); + this.AddMenuItemsFromDialogToFileOption(ChannelConfiguration); ChannelConfiguration.OnZoom += UpdateTrackBarLocation; ChannelConfiguration.OnFileLoad += OnFileLoadEvent; - comboBoxReference.DataSource = Enum.GetValues(typeof(NeuropixelsV2QuadShankReference)); + ProbeData = ProbeDataFactory(configuration); + + comboBoxReference.DataSource = ProbeData.GetReferenceEnumValues(); comboBoxReference.SelectedItem = ProbeConfiguration.Reference; comboBoxReference.SelectedIndexChanged += SelectedReferenceChanged; - comboBoxChannelPresets.DataSource = Enum.GetValues(typeof(ChannelPreset)); + comboBoxChannelPresets.DataSource = ProbeData.GetComboBoxChannelPresets(); comboBoxChannelPresets.SelectedIndexChanged += SelectedChannelPresetChanged; checkBoxInvertPolarity.Checked = InvertPolarity; @@ -106,6 +71,16 @@ public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2QuadShankProbeConfigu Text += ": " + ProbeConfiguration.Probe.ToString(); } + static INeuropixelsV2ProbeInfo ProbeDataFactory(NeuropixelsV2ProbeConfiguration configuration) + { + if (configuration is NeuropixelsV2QuadShankProbeConfiguration quadShankConfiguration) + { + return new NeuropixelsV2QuadShankInfo(quadShankConfiguration); + } + + throw new NotImplementedException("Unknown configuration found."); + } + private void InvertPolarityIndexChanged(object sender, EventArgs e) { InvertPolarity = ((CheckBox)sender).Checked; @@ -138,205 +113,18 @@ private void FormShown(object sender, EventArgs e) ChannelConfiguration.Show(); ChannelConfiguration.ConnectResizeEventHandler(); + ChannelConfiguration.ResizeZedGraph(); } private void SelectedReferenceChanged(object sender, EventArgs e) { - ProbeConfiguration.Reference = (NeuropixelsV2QuadShankReference)((ComboBox)sender).SelectedItem; + ProbeConfiguration.Reference = (Enum)((ComboBox)sender).SelectedItem; } private void SelectedChannelPresetChanged(object sender, EventArgs e) { - var channelPreset = (ChannelPreset)((ComboBox)sender).SelectedItem; - - if (channelPreset != ChannelPreset.None) - { - SetChannelPreset(channelPreset); - } - } - - private void SetChannelPreset(ChannelPreset preset) - { - var probeConfiguration = ChannelConfiguration.ProbeConfiguration; - var electrodes = NeuropixelsV2eProbeGroup.ToElectrodes(ChannelConfiguration.ProbeConfiguration.ProbeGroup); - - switch (preset) - { - case ChannelPreset.Shank0BankA: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 0).ToArray()); - break; - - case ChannelPreset.Shank0BankB: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 0).ToArray()); - break; - - case ChannelPreset.Shank0BankC: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 0).ToArray()); - break; - - case ChannelPreset.Shank0BankD: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 0).ToArray()); - break; - - case ChannelPreset.Shank1BankA: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 1).ToArray()); - break; - - case ChannelPreset.Shank1BankB: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 1).ToArray()); - break; - - case ChannelPreset.Shank1BankC: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 1).ToArray()); - break; - - case ChannelPreset.Shank1BankD: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 1).ToArray()); - break; - - case ChannelPreset.Shank2BankA: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 2).ToArray()); - break; - - case ChannelPreset.Shank2BankB: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 2).ToArray()); - break; - - case ChannelPreset.Shank2BankC: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 2).ToArray()); - break; - - case ChannelPreset.Shank2BankD: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 2).ToArray()); - break; - - case ChannelPreset.Shank3BankA: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 3).ToArray()); - break; - - case ChannelPreset.Shank3BankB: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 3).ToArray()); - break; - - case ChannelPreset.Shank3BankC: - probeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 3).ToArray()); - break; - - case ChannelPreset.Shank3BankD: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 3).ToArray()); - break; - - case ChannelPreset.AllShanks0_95: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95)).ToArray()); - break; - - case ChannelPreset.AllShanks96_191: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 96 && e.IntraShankElectrodeIndex <= 191) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 96 && e.IntraShankElectrodeIndex <= 191) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 96 && e.IntraShankElectrodeIndex <= 191) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 96 && e.IntraShankElectrodeIndex <= 191)).ToArray()); - break; - - case ChannelPreset.AllShanks192_287: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287)).ToArray()); - break; - - case ChannelPreset.AllShanks288_383: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383)).ToArray()); - break; - - case ChannelPreset.AllShanks384_479: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 384 && e.IntraShankElectrodeIndex <= 479) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 384 && e.IntraShankElectrodeIndex <= 479) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 384 && e.IntraShankElectrodeIndex <= 479) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 384 && e.IntraShankElectrodeIndex <= 479)).ToArray()); - break; - - case ChannelPreset.AllShanks480_575: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575)).ToArray()); - break; - - case ChannelPreset.AllShanks576_671: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671)).ToArray()); - break; - - case ChannelPreset.AllShanks672_767: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767)).ToArray()); - break; - - case ChannelPreset.AllShanks768_863: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863)).ToArray()); - break; - - case ChannelPreset.AllShanks864_959: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959)).ToArray()); - break; - - case ChannelPreset.AllShanks960_1055: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055)).ToArray()); - break; - - case ChannelPreset.AllShanks1056_1151: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151)).ToArray()); - break; - - case ChannelPreset.AllShanks1152_1247: - probeConfiguration.SelectElectrodes(electrodes.Where(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247)).ToArray()); - break; - } + Enum channelPreset = ((ComboBox)sender).SelectedItem as Enum ?? throw new InvalidEnumArgumentException("Invalid argument given for the channel preset."); + ProbeConfiguration.SelectElectrodes(ProbeData.GetChannelPreset(channelPreset)); ChannelConfiguration.HighlightEnabledContacts(); ChannelConfiguration.HighlightSelectedContacts(); @@ -344,188 +132,13 @@ private void SetChannelPreset(ChannelPreset preset) ChannelConfiguration.RefreshZedGraph(); } - private void CheckForExistingChannelPreset() + void CheckForExistingChannelPreset() { - var channelMap = ChannelConfiguration.ProbeConfiguration.ChannelMap; - - if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 0)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank0BankA; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 0)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank0BankB; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 0)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank0BankC; - } - else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 0)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank0BankD; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 1)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank1BankA; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 1)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank1BankB; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 1)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank1BankC; - } - else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 1)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank1BankD; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 2)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank2BankA; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 2)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank2BankB; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 2)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank2BankC; - } - else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 2)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank2BankD; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.A && - e.Shank == 3)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank3BankA; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.B && - e.Shank == 3)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank3BankB; - } - else if (channelMap.All(e => e.Bank == NeuropixelsV2QuadShankBank.C && - e.Shank == 3)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank3BankC; - } - else if (channelMap.All(e => (e.Bank == NeuropixelsV2QuadShankBank.D || - (e.Bank == NeuropixelsV2QuadShankBank.C && e.Index >= 896)) && - e.Shank == 3)) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.Shank3BankD; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 0 && e.IntraShankElectrodeIndex <= 95))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks0_95; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 192 && e.IntraShankElectrodeIndex <= 287))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks192_287; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 288 && e.IntraShankElectrodeIndex <= 383))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks288_383; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 394 && e.IntraShankElectrodeIndex <= 479) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 394 && e.IntraShankElectrodeIndex <= 479) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 394 && e.IntraShankElectrodeIndex <= 479) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 394 && e.IntraShankElectrodeIndex <= 479))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks384_479; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 480 && e.IntraShankElectrodeIndex <= 575))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks480_575; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 576 && e.IntraShankElectrodeIndex <= 671))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks576_671; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 672 && e.IntraShankElectrodeIndex <= 767))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks672_767; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 768 && e.IntraShankElectrodeIndex <= 863))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks768_863; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 864 && e.IntraShankElectrodeIndex <= 959))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks864_959; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 960 && e.IntraShankElectrodeIndex <= 1055))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks960_1055; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 1056 && e.IntraShankElectrodeIndex <= 1151))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks1056_1151; - } - else if (channelMap.All(e => (e.Shank == 0 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || - (e.Shank == 1 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || - (e.Shank == 2 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247) || - (e.Shank == 3 && e.IntraShankElectrodeIndex >= 1152 && e.IntraShankElectrodeIndex <= 1247))) - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.AllShanks1152_1247; - } - else - { - comboBoxChannelPresets.SelectedItem = ChannelPreset.None; - } + comboBoxChannelPresets.SelectedItem = ProbeData.CheckForExistingChannelPreset(ProbeConfiguration.ChannelMap); } private void OnFileLoadEvent(object sender, EventArgs e) { - // NB: Ensure that the newly loaded ProbeConfiguration in the ChannelConfigurationDialog is reflected here. - ProbeConfiguration = ChannelConfiguration.ProbeConfiguration; CheckForExistingChannelPreset(); } @@ -607,9 +220,10 @@ internal void EnableContacts_Click(object sender, EventArgs e) private void EnableSelectedContacts() { - var selected = NeuropixelsV2eProbeGroup.ToElectrodes(ChannelConfiguration.ProbeConfiguration.ProbeGroup) - .Where((e, ind) => ChannelConfiguration.SelectedContacts[ind]) - .ToArray(); + var selected = ProbeConfiguration.ProbeGroup + .ToElectrodes() + .Where((e, ind) => ChannelConfiguration.SelectedContacts[ind]) + .ToArray(); ChannelConfiguration.EnableElectrodes(selected); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs index db034bf6..e613d2c2 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs @@ -8,7 +8,7 @@ namespace OpenEphys.Onix1.Design { /// - /// Class that opens a new dialog for a . + /// Class that opens a new dialog for a . /// public class NeuropixelsV2eProbeConfigurationEditor : UITypeEditor { @@ -27,15 +27,17 @@ public override object EditValue(ITypeDescriptorContext context, IServiceProvide var editorState = (IWorkflowEditorState)provider.GetService(typeof(IWorkflowEditorState)); if (editorService != null && editorState != null && !editorState.WorkflowRunning && - value is NeuropixelsV2QuadShankProbeConfiguration configuration) + value is NeuropixelsV2ProbeConfiguration configuration) { var instance = (IConfigureNeuropixelsV2)context.Instance; var calibrationFile = configuration.Probe == NeuropixelsV2Probe.ProbeA ? instance.GainCalibrationFileA : instance.GainCalibrationFileB; + bool isBeta = instance is ConfigureNeuropixelsV2eBeta; + using var editorDialog = new NeuropixelsV2eProbeConfigurationDialog(configuration, calibrationFile, instance.InvertPolarity); - if (instance is ConfigureNeuropixelsV2eBeta) + if (isBeta) { editorDialog.Text = editorDialog.Text.Replace("NeuropixelsV2e ", "NeuropixelsV2eBeta "); } diff --git a/OpenEphys.Onix1.Design/Rhs2116ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/Rhs2116ChannelConfigurationDialog.cs index 6d99c8e3..42920979 100644 --- a/OpenEphys.Onix1.Design/Rhs2116ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/Rhs2116ChannelConfigurationDialog.cs @@ -13,6 +13,7 @@ public partial class Rhs2116ChannelConfigurationDialog : ChannelConfigurationDia { internal event EventHandler OnSelect; internal event EventHandler OnZoom; + internal event EventHandler OnFileLoad; /// /// Initializes a new instance of . @@ -36,14 +37,33 @@ public Rhs2116ChannelConfigurationDialog(Rhs2116ProbeGroup probeGroup) RefreshZedGraph(); } + internal override void LoadDefaultChannelLayout() + { + ProbeGroup = DefaultChannelLayout(); + + OnFileOpenHandler(); + } + internal override ProbeGroup DefaultChannelLayout() { return new Rhs2116ProbeGroup(); } - internal override bool OpenFile() + internal override bool OpenFile(Type type) + { + if (base.OpenFile(type)) + { + OnFileOpenHandler(); + + return true; + } + + return false; + } + + void OnFileOpenHandler() { - return base.OpenFile(); + OnFileLoad?.Invoke(this, EventArgs.Empty); } internal override void SelectedContactChanged() diff --git a/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.cs b/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.cs index a3e8e3f0..7909238e 100644 --- a/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.cs +++ b/OpenEphys.Onix1.Design/Rhs2116StimulusSequenceDialog.cs @@ -71,8 +71,7 @@ public Rhs2116StimulusSequenceDialog(ConfigureRhs2116Trigger rhs2116Trigger) ChannelDialog.OnSelect += OnSelect; ChannelDialog.OnZoom += OnZoom; - - ChannelDialog.Show(); + ChannelDialog.OnFileLoad += OnFileLoad; StimulusSequenceOptions = new(); groupBoxDefineStimuli.Controls.Add(StimulusSequenceOptions.SetChildFormProperties(this)); @@ -115,6 +114,11 @@ public Rhs2116StimulusSequenceDialog(ConfigureRhs2116Trigger rhs2116Trigger) DrawStimulusWaveform(); } + void OnFileLoad(object sender, EventArgs e) + { + Trigger.ProbeGroup = (Rhs2116ProbeGroup)ChannelDialog.ProbeGroup; + } + internal void OnZoom(object sender, EventArgs e) { ChannelDialog.UpdateFontSize(); @@ -885,9 +889,7 @@ internal override bool IsSequenceValid() internal override void DeserializeStimulusSequence(string fileName) { - var newSequence = JsonHelper.DeserializeString(File.ReadAllText(fileName)); - - if (newSequence != null && newSequence.Stimuli.Length == 32) + if (JsonHelper.DeserializeString(File.ReadAllText(fileName), typeof(Rhs2116StimulusSequencePair)) is Rhs2116StimulusSequencePair newSequence && newSequence.Stimuli.Length == 32) { if (newSequence == new Rhs2116StimulusSequencePair()) { diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs index fcc98d53..b15c98ed 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Drawing.Design; using System.Reactive.Disposables; +using System.Xml.Serialization; using Bonsai; namespace OpenEphys.Onix1 @@ -56,7 +57,8 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) [Category(ConfigurationCategory)] [Description("Probe A configuration.")] [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new(NeuropixelsV2Probe.ProbeA); + [XmlElement(nameof(ProbeConfigurationA), typeof(NeuropixelsV2QuadShankProbeConfiguration))] + public NeuropixelsV2ProbeConfiguration ProbeConfigurationA { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe.ProbeA, NeuropixelsV2QuadShankReference.External); /// [Category(ConfigurationCategory)] @@ -69,7 +71,8 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) [Category(ConfigurationCategory)] [Description("Probe B configuration.")] [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new(NeuropixelsV2Probe.ProbeB); + [XmlElement(nameof(ProbeConfigurationB), typeof(NeuropixelsV2QuadShankProbeConfiguration))] + public NeuropixelsV2ProbeConfiguration ProbeConfigurationB { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe.ProbeB, NeuropixelsV2QuadShankReference.External); /// [Category(ConfigurationCategory)] @@ -109,7 +112,7 @@ public override IObservable Process(IObservable source // set I2C clock rate to ~400 kHz DS90UB9x.Set933I2CRate(device, 400e3); - + // read probe metadata var probeAMetadata = ReadProbeMetadata(serializer, NeuropixelsV2e.ProbeASelected); var probeBMetadata = ReadProbeMetadata(serializer, NeuropixelsV2e.ProbeBSelected); diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs index dc661b64..622c9d90 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Drawing.Design; using System.Reactive.Disposables; +using System.Xml.Serialization; using Bonsai; namespace OpenEphys.Onix1 @@ -71,7 +72,8 @@ public ConfigureNeuropixelsV2eBeta(ConfigureNeuropixelsV2eBeta configureNode) [Category(ConfigurationCategory)] [Description("Probe A configuration.")] [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } = new(NeuropixelsV2Probe.ProbeA); + [XmlElement(nameof(ProbeConfigurationA), typeof(NeuropixelsV2QuadShankProbeConfiguration))] + public NeuropixelsV2ProbeConfiguration ProbeConfigurationA { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe.ProbeA, NeuropixelsV2QuadShankReference.External); /// [Category(ConfigurationCategory)] @@ -84,7 +86,8 @@ public ConfigureNeuropixelsV2eBeta(ConfigureNeuropixelsV2eBeta configureNode) [Category(ConfigurationCategory)] [Description("Probe B configuration.")] [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } = new(NeuropixelsV2Probe.ProbeB); + [XmlElement(nameof(ProbeConfigurationB), typeof(NeuropixelsV2QuadShankProbeConfiguration))] + public NeuropixelsV2ProbeConfiguration ProbeConfigurationB { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe.ProbeB, NeuropixelsV2QuadShankReference.External); /// [Category(ConfigurationCategory)] @@ -146,7 +149,7 @@ public override IObservable Process(IObservable source // configure probe A streaming if (probeAMetadata.ProbeSerialNumber != null) { - if (ProbeConfigurationA.Reference == NeuropixelsV2QuadShankReference.Ground) + if (ProbeConfigurationA.IsGroundReference()) { throw new InvalidOperationException($"Neuropixels 2.0-Beta probes do not provide a Ground reference selection. Please select a different reference" + $" for {NeuropixelsV2Probe.ProbeA}."); @@ -175,7 +178,7 @@ public override IObservable Process(IObservable source // configure probe B streaming if (probeBMetadata.ProbeSerialNumber != null) { - if (ProbeConfigurationB.Reference == NeuropixelsV2QuadShankReference.Ground) + if (ProbeConfigurationB.IsGroundReference()) { throw new InvalidOperationException($"Neuropixels 2.0-Beta probes do not provide a Ground reference selection. Please select a different reference" + $" for {NeuropixelsV2Probe.ProbeB}."); diff --git a/OpenEphys.Onix1/Constants.cs b/OpenEphys.Onix1/Constants.cs new file mode 100644 index 00000000..7d5d1d64 --- /dev/null +++ b/OpenEphys.Onix1/Constants.cs @@ -0,0 +1,7 @@ +namespace OpenEphys.Onix1 +{ + static class Constants + { + public const string XmlNamespace = "clr-namespace:OpenEphys.Onix1;assembly=OpenEphys.Onix1"; + } +} diff --git a/OpenEphys.Onix1/Electrode.cs b/OpenEphys.Onix1/Electrode.cs index 028c56a4..9f5c05ef 100644 --- a/OpenEphys.Onix1/Electrode.cs +++ b/OpenEphys.Onix1/Electrode.cs @@ -13,30 +13,30 @@ public abstract class Electrode /// the context of the entire probe. /// [XmlIgnore] - public int Index { get; internal set; } + public int Index { get; init; } /// /// Gets the shank this electrode belongs to. /// [XmlIgnore] - public int Shank { get; internal set; } + public int Shank { get; init; } /// /// Gets the index of the electrode within the context of . /// [XmlIgnore] - public int IntraShankElectrodeIndex { get; internal set; } + public int IntraShankElectrodeIndex { get; init; } /// /// Gets the electrical channel that this electrode is mapped to. /// [XmlIgnore] - public int Channel { get; internal set; } + public int Channel { get; init; } /// /// Gets the location of the electrode in two-dimensional space in arbitrary units. /// [XmlIgnore] - public PointF Position { get; internal set; } + public PointF Position { get; init; } } } diff --git a/OpenEphys.Onix1/IConfigureNeuropixelsV2.cs b/OpenEphys.Onix1/IConfigureNeuropixelsV2.cs index 43e7e9f6..78e37091 100644 --- a/OpenEphys.Onix1/IConfigureNeuropixelsV2.cs +++ b/OpenEphys.Onix1/IConfigureNeuropixelsV2.cs @@ -13,7 +13,7 @@ public interface IConfigureNeuropixelsV2 /// /// Gets or sets the electrode configuration for Probe A. /// - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationA { get; set; } + public NeuropixelsV2ProbeConfiguration ProbeConfigurationA { get; set; } /// /// Gets or sets the path to the gain calibration file for Probe A. @@ -35,7 +35,7 @@ public interface IConfigureNeuropixelsV2 /// /// Gets or sets the electrode configuration for Probe B. /// - public NeuropixelsV2QuadShankProbeConfiguration ProbeConfigurationB { get; set; } + public NeuropixelsV2ProbeConfiguration ProbeConfigurationB { get; set; } /// /// Gets or sets the path to the gain calibration file for Probe B. diff --git a/OpenEphys.Onix1/JsonHelper.cs b/OpenEphys.Onix1/JsonHelper.cs index f04e2611..644f3477 100644 --- a/OpenEphys.Onix1/JsonHelper.cs +++ b/OpenEphys.Onix1/JsonHelper.cs @@ -7,7 +7,7 @@ namespace OpenEphys.Onix1 { internal static class JsonHelper { - public static T DeserializeString(string jsonString) where T : class + public static object DeserializeString(string jsonString, Type type) { var errors = new List(); @@ -22,7 +22,7 @@ public static T DeserializeString(string jsonString) where T : class try { - var obj = JsonConvert.DeserializeObject(jsonString, serializerSettings); + var obj = JsonConvert.DeserializeObject(jsonString, type, serializerSettings); if (errors.Count > 0) { diff --git a/OpenEphys.Onix1/NeuropixelsV1ProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV1ProbeConfiguration.cs index 3591138b..e430e1d2 100644 --- a/OpenEphys.Onix1/NeuropixelsV1ProbeConfiguration.cs +++ b/OpenEphys.Onix1/NeuropixelsV1ProbeConfiguration.cs @@ -266,7 +266,7 @@ public string ProbeInterfaceLoadFileName if (string.IsNullOrEmpty(probeInterfaceFileName)) return new NeuropixelsV1eProbeGroup(); - return ProbeInterfaceHelper.LoadExternalProbeInterfaceFile(probeInterfaceFileName); + return ProbeInterfaceHelper.LoadExternalProbeInterfaceFile(probeInterfaceFileName, typeof(NeuropixelsV1eProbeGroup)) as NeuropixelsV1eProbeGroup; }); } } diff --git a/OpenEphys.Onix1/NeuropixelsV2.cs b/OpenEphys.Onix1/NeuropixelsV2.cs index b76db70d..c2ec2789 100644 --- a/OpenEphys.Onix1/NeuropixelsV2.cs +++ b/OpenEphys.Onix1/NeuropixelsV2.cs @@ -29,44 +29,10 @@ static class NeuropixelsV2 public const int ChannelCount = 384; public const int BaseBitsPerChannel = 4; public const int ElectrodePerShank = 1280; - public const int ElectrodePerBlock = 48; - public const int ReferencePixelCount = 4; - public const int DummyRegisterCount = 4; - public const int RegistersPerShank = ElectrodePerShank + ReferencePixelCount + DummyRegisterCount; - internal static BitArray[] GenerateShankBits(NeuropixelsV2QuadShankProbeConfiguration probe) + internal static BitArray[] GenerateShankBits(NeuropixelsV2ProbeConfiguration probe) { - BitArray[] shankBits = - { - new(RegistersPerShank, false), - new(RegistersPerShank, false), - new(RegistersPerShank, false), - new(RegistersPerShank, false) - }; - - - if (probe.Reference != NeuropixelsV2QuadShankReference.External) - { - // If tip reference is used, activate the tip electrodes - shankBits[(int)probe.Reference - 1][643] = true; - shankBits[(int)probe.Reference - 1][644] = true; - } - else - { - // TODO: is this the right approach or should only those - // connections to external reference on shanks with active - // electrodes be activated? - - // If external electrode is used, activate on each shank - shankBits[0][2] = true; - shankBits[0][1285] = true; - shankBits[1][2] = true; - shankBits[1][1285] = true; - shankBits[2][2] = true; - shankBits[2][1285] = true; - shankBits[3][2] = true; - shankBits[3][1285] = true; - } + BitArray[] shankBits = probe.CreateShankBits(probe.Reference); const int PixelOffset = (ElectrodePerShank - 1) / 2; const int ReferencePixelOffset = 3; @@ -84,7 +50,7 @@ internal static BitArray[] GenerateShankBits(NeuropixelsV2QuadShankProbeConfigur return shankBits; } - internal static BitArray[] GenerateBaseBits(NeuropixelsV2QuadShankProbeConfiguration probe) + internal static BitArray[] GenerateBaseBits(NeuropixelsV2ProbeConfiguration probe) { BitArray[] baseBits = { @@ -92,16 +58,7 @@ internal static BitArray[] GenerateBaseBits(NeuropixelsV2QuadShankProbeConfigura new(ChannelCount * BaseBitsPerChannel / 2, false) }; - var referenceBit = probe.Reference switch - { - NeuropixelsV2QuadShankReference.External => 1, - NeuropixelsV2QuadShankReference.Tip1 => 2, - NeuropixelsV2QuadShankReference.Tip2 => 2, - NeuropixelsV2QuadShankReference.Tip3 => 2, - NeuropixelsV2QuadShankReference.Tip4 => 2, - NeuropixelsV2QuadShankReference.Ground => 3, - _ => throw new InvalidOperationException("Invalid reference selection."), - }; + var referenceBit = probe.GetReferenceBit(probe.Reference); for (int i = 0; i < ChannelCount; i++) { diff --git a/OpenEphys.Onix1/NeuropixelsV2Electrode.cs b/OpenEphys.Onix1/NeuropixelsV2Electrode.cs new file mode 100644 index 00000000..19c98216 --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV2Electrode.cs @@ -0,0 +1,33 @@ +using System; +using System.Xml.Serialization; + +namespace OpenEphys.Onix1 +{ + /// + /// Class defining a . + /// + [XmlInclude(typeof(NeuropixelsV2QuadShankElectrode))] + [XmlType(Namespace = Constants.XmlNamespace)] + public abstract class NeuropixelsV2Electrode : Electrode + { + /// + /// Gets the bank, or logical block of channels, this electrode belongs to. + /// + [XmlIgnore] + public NeuropixelsV2Bank Bank { get; init; } + + /// + /// Gets the block this electrode belongs to. + /// + [XmlIgnore] + public int Block { get; init; } + + /// + /// Gets the index within the block this electrode belongs to. + /// + [XmlIgnore] + public int BlockIndex { get; init; } + + internal abstract Func GetChannelNumberFunc(); + } +} diff --git a/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs new file mode 100644 index 00000000..aedae6e8 --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Xml; +using System.Xml.Serialization; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// Specifies the bank of electrodes within each shank. + /// + public enum NeuropixelsV2Bank + { + /// + /// Specifies that Bank A is the current bank. + /// + /// Bank A is defined as shank index 0 to 383 along each shank. + A, + /// + /// Specifies that Bank B is the current bank. + /// + /// Bank B is defined as shank index 384 to 767 along each shank. + B, + /// + /// Specifies that Bank C is the current bank. + /// + /// Bank C is defined as shank index 768 to 1151 along each shank. + C, + /// + /// Specifies that Bank D is the current bank. + /// + /// + /// Bank D is defined as shank index 1152 to 1279 along each shank. Note that Bank D is not a full contingent + /// of 384 channels; to compensate for this, electrodes from Bank C (starting at shank index 896) are used to + /// generate a full 384 channel map. + /// + D, + } + + /// + /// Defines a configuration for Neuropixels 2.0 and 2.0-beta probes. + /// + [XmlInclude(typeof(NeuropixelsV2QuadShankProbeConfiguration))] + [XmlType(Namespace = Constants.XmlNamespace)] + public abstract class NeuropixelsV2ProbeConfiguration + { + /// + /// Gets or sets the for this probe. + /// + [Browsable(false)] + public NeuropixelsV2Probe Probe { get; set; } + + /// + /// Gets or sets the reference for all electrodes. + /// + [XmlIgnore] + [Description("Defines the reference for the probe.")] + public Enum Reference { get; set; } + + /// + /// Gets or sets the serialized reference value. + /// + /// + /// Ensures that XML serialization can occur for the generic Enum type . + /// + [XmlElement(nameof(Reference))] + [Browsable(false)] + [Externalizable(false)] + public abstract string ReferenceSerialized { get; set; } + + /// + /// Gets the existing channel map listing all currently enabled electrodes. + /// + /// + /// The channel map will always be 384 channels, and will return the 384 enabled electrodes. + /// + [XmlIgnore] + public abstract NeuropixelsV2Electrode[] ChannelMap { get; } + + /// + /// Update the with the selected electrodes. + /// + /// List of selected electrodes that are being added to the + public abstract void SelectElectrodes(NeuropixelsV2Electrode[] electrodes); + + /// + /// Gets the channel configuration for this probe. + /// + [XmlIgnore] + [Category("Configuration")] + [Description("Defines the shape of the probe, and which contacts are currently selected for streaming.")] + public NeuropixelsV2eProbeGroup ProbeGroup { get; set; } + + /// + /// Gets or sets a string defining the in Base64. + /// This variable is needed to properly save a workflow in Bonsai, but it is not + /// directly accessible in the Bonsai editor. + /// + [Browsable(false)] + [Externalizable(false)] + [XmlElement(nameof(ProbeGroup))] + public abstract string ProbeGroupString { get; set; } + + internal abstract BitArray[] CreateShankBits(Enum reference); + + internal abstract int GetReferenceBit(Enum reference); + + internal abstract bool IsGroundReference(); + } +} diff --git a/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs index a24c01e9..66071568 100644 --- a/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankElectrode.cs @@ -1,106 +1,103 @@ using System; using System.Drawing; -using System.Xml.Serialization; namespace OpenEphys.Onix1 { /// /// Class defining a . /// - public class NeuropixelsV2QuadShankElectrode : Electrode + public class NeuropixelsV2QuadShankElectrode : NeuropixelsV2Electrode { /// - /// Gets the bank, or logical block of channels, this electrode belongs to. + /// Class defining a Neuropixels 2.0 quad-shank electrode. /// - [XmlIgnore] - public NeuropixelsV2QuadShankBank Bank { get; private set; } - - /// - /// Gets the block this electrode belongs to. - /// - [XmlIgnore] - public int Block { get; private set; } - - /// - /// Gets the index within the block this electrode belongs to. - /// - [XmlIgnore] - public int BlockIndex { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// Integer defining the index of the contact. + /// Global index of the electrode. public NeuropixelsV2QuadShankElectrode(int index) { Index = index; - Shank = index / NeuropixelsV2.ElectrodePerShank; - IntraShankElectrodeIndex = index % NeuropixelsV2.ElectrodePerShank; - Bank = (NeuropixelsV2QuadShankBank)(IntraShankElectrodeIndex / NeuropixelsV2.ChannelCount); - Block = IntraShankElectrodeIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; - BlockIndex = IntraShankElectrodeIndex % NeuropixelsV2.ElectrodePerBlock; - Channel = GetChannelNumber(Shank, Block, BlockIndex); + Shank = GetShank(index); + IntraShankElectrodeIndex = GetIntraShankIndex(index); + Bank = GetBank(index); + Block = GetBlock(index); + BlockIndex = GetBlockIndex(index); Position = GetPosition(index); + Channel = GetQuadShankChannelNumber(Shank, Block, BlockIndex); } - private PointF GetPosition(int electrodeNumber) + NeuropixelsV2Bank GetBank(int index) => (NeuropixelsV2Bank)(GetIntraShankIndex(index) / NeuropixelsV2.ChannelCount); + + static int GetShank(int index) => index / NeuropixelsV2.ElectrodePerShank; + + static int GetIntraShankIndex(int index) => index % NeuropixelsV2.ElectrodePerShank; + + const int ElectrodePerBlock = 48; + + static int GetBlock(int index) => (GetIntraShankIndex(index) % NeuropixelsV2.ChannelCount) / ElectrodePerBlock; + + static int GetBlockIndex(int index) => GetIntraShankIndex(index) % ElectrodePerBlock; + + static PointF GetPosition(int index) { - var position = NeuropixelsV2eProbeGroup.DefaultContactPosition(electrodeNumber); + var position = NeuropixelsV2eQuadShankProbeGroup.DefaultContactPosition(index); return new PointF(x: position[0], y: position[1]); } + internal override Func GetChannelNumberFunc() + { + return GetChannelNumber; + } + /// - /// Static method returning the channel number of a given electrode. + /// Gets the channel number of a given electrode. /// - /// Integer defining the index of the electrode in the probe. + /// Integer defining the index of the electrode in the probe. /// An integer between 0 and 383 defining the channel number. - public static int GetChannelNumber(int electrodeIndex) + public static int GetChannelNumber(int index) { - var shank = electrodeIndex / NeuropixelsV2.ElectrodePerShank; - var shankIndex = electrodeIndex % NeuropixelsV2.ElectrodePerShank; - var block = shankIndex % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock; - var blockIndex = shankIndex % NeuropixelsV2.ElectrodePerBlock; + var shank = GetShank(index); + var block = GetBlock(index); + var blockIndex = GetBlockIndex(index); - return GetChannelNumber(shank, block, blockIndex); + return GetQuadShankChannelNumber(shank, block, blockIndex); } - internal static int GetChannelNumber(int shank, int block, int blockIndex) => (shank, block) switch + static int GetQuadShankChannelNumber(int shank, int block, int blockIndex) => (shank, block) switch { - (0, 0) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 0, - (0, 1) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 2, - (0, 2) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 4, - (0, 3) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 6, - (0, 4) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 5, - (0, 5) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 7, - (0, 6) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 1, - (0, 7) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 3, - - (1, 0) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 1, - (1, 1) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 3, - (1, 2) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 5, - (1, 3) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 7, - (1, 4) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 4, - (1, 5) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 6, - (1, 6) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 0, - (1, 7) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 2, - - (2, 0) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 4, - (2, 1) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 6, - (2, 2) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 0, - (2, 3) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 2, - (2, 4) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 1, - (2, 5) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 3, - (2, 6) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 5, - (2, 7) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 7, - - (3, 0) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 5, - (3, 1) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 7, - (3, 2) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 1, - (3, 3) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 3, - (3, 4) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 0, - (3, 5) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 2, - (3, 6) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 4, - (3, 7) => blockIndex + NeuropixelsV2.ElectrodePerBlock * 6, + (0, 0) => blockIndex + ElectrodePerBlock * 0, + (0, 1) => blockIndex + ElectrodePerBlock * 2, + (0, 2) => blockIndex + ElectrodePerBlock * 4, + (0, 3) => blockIndex + ElectrodePerBlock * 6, + (0, 4) => blockIndex + ElectrodePerBlock * 5, + (0, 5) => blockIndex + ElectrodePerBlock * 7, + (0, 6) => blockIndex + ElectrodePerBlock * 1, + (0, 7) => blockIndex + ElectrodePerBlock * 3, + + (1, 0) => blockIndex + ElectrodePerBlock * 1, + (1, 1) => blockIndex + ElectrodePerBlock * 3, + (1, 2) => blockIndex + ElectrodePerBlock * 5, + (1, 3) => blockIndex + ElectrodePerBlock * 7, + (1, 4) => blockIndex + ElectrodePerBlock * 4, + (1, 5) => blockIndex + ElectrodePerBlock * 6, + (1, 6) => blockIndex + ElectrodePerBlock * 0, + (1, 7) => blockIndex + ElectrodePerBlock * 2, + + (2, 0) => blockIndex + ElectrodePerBlock * 4, + (2, 1) => blockIndex + ElectrodePerBlock * 6, + (2, 2) => blockIndex + ElectrodePerBlock * 0, + (2, 3) => blockIndex + ElectrodePerBlock * 2, + (2, 4) => blockIndex + ElectrodePerBlock * 1, + (2, 5) => blockIndex + ElectrodePerBlock * 3, + (2, 6) => blockIndex + ElectrodePerBlock * 5, + (2, 7) => blockIndex + ElectrodePerBlock * 7, + + (3, 0) => blockIndex + ElectrodePerBlock * 5, + (3, 1) => blockIndex + ElectrodePerBlock * 7, + (3, 2) => blockIndex + ElectrodePerBlock * 1, + (3, 3) => blockIndex + ElectrodePerBlock * 3, + (3, 4) => blockIndex + ElectrodePerBlock * 0, + (3, 5) => blockIndex + ElectrodePerBlock * 2, + (3, 6) => blockIndex + ElectrodePerBlock * 4, + (3, 7) => blockIndex + ElectrodePerBlock * 6, _ => throw new ArgumentOutOfRangeException($"Invalid shank and/or electrode value: {(shank, block)}"), }; diff --git a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs index 6a3e9280..44eec4bf 100644 --- a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs @@ -1,17 +1,15 @@ using System; -using System.Collections.Generic; +using System.Collections; using System.ComponentModel; -using Bonsai; -using Newtonsoft.Json; using System.Text; using System.Xml.Serialization; -using System.Linq; -using OpenEphys.ProbeInterface.NET; +using Bonsai; +using Newtonsoft.Json; namespace OpenEphys.Onix1 { /// - /// Specifies the reference for a quad-shank probe. + /// Specifies the reference for a Neuropixels 2.0 probe. /// public enum NeuropixelsV2QuadShankReference : uint { @@ -36,80 +34,35 @@ public enum NeuropixelsV2QuadShankReference : uint /// Tip4, /// - /// Specifies that the ground reference will be used. + /// Specifies that the Ground reference will be used. /// Ground } - /// - /// Specifies the bank of electrodes within each shank. - /// - public enum NeuropixelsV2QuadShankBank - { - /// - /// Specifies that Bank A is the current bank. - /// - /// Bank A is defined as shank index 0 to 383 along each shank. - A, - /// - /// Specifies that Bank B is the current bank. - /// - /// Bank B is defined as shank index 384 to 767 along each shank. - B, - /// - /// Specifies that Bank C is the current bank. - /// - /// Bank C is defined as shank index 768 to 1151 along each shank. - C, - /// - /// Specifies that Bank D is the current bank. - /// - /// - /// Bank D is defined as shank index 1152 to 1279 along each shank. Note that Bank D is not a full contingent - /// of 384 channels; to compensate for this, electrodes from Bank C (starting at shank index 896) are used to - /// generate a full 384 channel map. - /// - D, - } - /// /// Defines a configuration for quad-shank, Neuropixels 2.0 and 2.0-beta probes. /// - public class NeuropixelsV2QuadShankProbeConfiguration + [DisplayName(XmlTypeName)] + [XmlType(TypeName = XmlTypeName, Namespace = Constants.XmlNamespace)] + public class NeuropixelsV2QuadShankProbeConfiguration : NeuropixelsV2ProbeConfiguration { - /// - /// Creates a model of the probe with all electrodes instantiated. - /// - [XmlIgnore] - public static readonly IReadOnlyList ProbeModel = CreateProbeModel(); + const string XmlTypeName = "QuadShank"; /// - /// Initializes a new instance of the class. + /// Initializes a default instance of the class. /// public NeuropixelsV2QuadShankProbeConfiguration() { - ChannelMap = new NeuropixelsV2QuadShankElectrode[NeuropixelsV2.ChannelCount]; - for (int i = 0; i < ChannelMap.Length; i++) - { - ChannelMap[i] = ProbeModel.FirstOrDefault(e => e.Channel == i); - } } /// /// Initializes a new instance of the class. /// - public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe probe) : this() - { - Probe = probe; - } - - /// - /// Initializes a new instance of the class. - /// - public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe probe, NeuropixelsV2QuadShankReference reference) : this() + public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe probe, NeuropixelsV2QuadShankReference reference) { Probe = probe; Reference = reference; + ProbeGroup = new NeuropixelsV2eQuadShankProbeGroup(); } /// @@ -119,56 +72,25 @@ public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe probe, Neurop public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2QuadShankProbeConfiguration probeConfiguration) { Reference = probeConfiguration.Reference; - var probes = probeConfiguration.ProbeGroup.Probes.ToList().Select(probe => new Probe(probe)); - ProbeGroup = new(probeConfiguration.ProbeGroup.Specification, probeConfiguration.ProbeGroup.Version, probes.ToArray()); - ChannelMap = NeuropixelsV2eProbeGroup.ToChannelMap(ProbeGroup); + ProbeGroup = probeConfiguration.ProbeGroup.Clone(); Probe = probeConfiguration.Probe; } /// /// Initializes a new instance of the class with the given - /// channel configuration. The is automatically - /// generated from the . + /// channel configuration. /// - /// The existing instance to use. + /// The existing instance to use. /// The reference value. /// The for this probe. [JsonConstructor] - public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2eProbeGroup probeGroup, NeuropixelsV2QuadShankReference reference, NeuropixelsV2Probe probe) + public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2eQuadShankProbeGroup probeGroup, NeuropixelsV2Probe probe, NeuropixelsV2QuadShankReference reference) { - ChannelMap = NeuropixelsV2eProbeGroup.ToChannelMap(probeGroup); - ProbeGroup = probeGroup; + ProbeGroup = probeGroup.Clone(); Reference = reference; Probe = probe; } - private static List CreateProbeModel() - { - var electrodes = new List(NeuropixelsV2.ElectrodePerShank * 4); - for (int i = 0; i < NeuropixelsV2.ElectrodePerShank * 4; i++) - { - electrodes.Add(new NeuropixelsV2QuadShankElectrode(i)); - } - return electrodes; - } - - /// - /// Gets or sets the for this probe. - /// - public NeuropixelsV2Probe Probe { get; set; } = NeuropixelsV2Probe.ProbeA; - - /// - /// Gets or sets the reference for all electrodes. - /// - /// - /// All electrodes are set to the same reference, which can be - /// or any of the tip references - /// (, , etc.). - /// Setting to will use the external reference, while - /// sets the reference to the electrode at the tip of the first shank. - /// - public NeuropixelsV2QuadShankReference Reference { get; set; } = NeuropixelsV2QuadShankReference.External; - /// /// Gets the existing channel map listing all currently enabled electrodes. /// @@ -176,58 +98,146 @@ private static List CreateProbeModel() /// The channel map will always be 384 channels, and will return the 384 enabled electrodes. /// [XmlIgnore] - public NeuropixelsV2QuadShankElectrode[] ChannelMap { get; } + public override NeuropixelsV2Electrode[] ChannelMap + { + get => NeuropixelsV2eQuadShankProbeGroup.ToChannelMap((NeuropixelsV2eQuadShankProbeGroup)ProbeGroup); + } + + /// + [Browsable(false)] + [Externalizable(false)] + [XmlElement(nameof(ProbeGroup))] + public override string ProbeGroupString + { + get + { + var jsonString = JsonConvert.SerializeObject(ProbeGroup); + return Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString)); + } + set + { + var jsonString = Encoding.UTF8.GetString(Convert.FromBase64String(value)); + ProbeGroup = JsonConvert.DeserializeObject(jsonString); + } + } /// /// Update the with the selected electrodes. /// /// List of selected electrodes that are being added to the - public void SelectElectrodes(NeuropixelsV2QuadShankElectrode[] electrodes) + public override void SelectElectrodes(NeuropixelsV2Electrode[] electrodes) { + var channelMap = ChannelMap; + foreach (var e in electrodes) { try { - ChannelMap[e.Channel] = e; + channelMap[e.Channel] = e; } catch (IndexOutOfRangeException ex) { throw new IndexOutOfRangeException($"Electrode {e.Index} specifies channel {e.Channel} but only channels " + - $"0 to {ChannelMap.Length - 1} are supported.", ex); + $"0 to {channelMap.Length - 1} are supported.", ex); } } - ProbeGroup.UpdateDeviceChannelIndices(ChannelMap); + ProbeGroup.UpdateDeviceChannelIndices(channelMap); } - /// - /// Gets the channel configuration for this probe. - /// - [XmlIgnore] - [Category("Configuration")] - [Description("Defines the shape of the probe, and which contacts are currently selected for streaming")] - public NeuropixelsV2eProbeGroup ProbeGroup { get; private set; } = new(); + internal override BitArray[] CreateShankBits(Enum reference) + { + const int ReferencePixelCount = 4; + const int DummyRegisterCount = 4; + const int RegistersPerShank = NeuropixelsV2.ElectrodePerShank + ReferencePixelCount + DummyRegisterCount; - /// - /// Gets or sets a string defining the in Base64. - /// This variable is needed to properly save a workflow in Bonsai, but it is not - /// directly accessible in the Bonsai editor. - /// + const int ShiftRegisterBitExternalElectrode0 = 1285; + const int ShiftRegisterBitExternalElectrode1 = 2; + + const int ShiftRegisterBitTipElectrode0 = 644; + const int ShiftRegisterBitTipElectrode1 = 643; + + var shankBits = new BitArray[] + { + new(RegistersPerShank, false), + new(RegistersPerShank, false), + new(RegistersPerShank, false), + new(RegistersPerShank, false) + }; + + NeuropixelsV2QuadShankReference quadShankReference = (NeuropixelsV2QuadShankReference)reference; + + if (quadShankReference != NeuropixelsV2QuadShankReference.External && quadShankReference != NeuropixelsV2QuadShankReference.Ground) + { + var shank = reference switch + { + NeuropixelsV2QuadShankReference.Tip1 => 0, + NeuropixelsV2QuadShankReference.Tip2 => 1, + NeuropixelsV2QuadShankReference.Tip3 => 2, + NeuropixelsV2QuadShankReference.Tip4 => 3, + _ => throw new InvalidOperationException($"Invalid reference chosen for quad-shank probe.") + }; + + // If tip reference is used, activate the tip electrode + shankBits[shank][ShiftRegisterBitTipElectrode1] = true; + shankBits[shank][ShiftRegisterBitTipElectrode0] = true; + } + else if (quadShankReference == NeuropixelsV2QuadShankReference.External) + { + // TODO: is this the right approach or should only those + // connections to external reference on shanks with active + // electrodes be activated? + + // If external electrode is used, activate on each shank + shankBits[0][ShiftRegisterBitExternalElectrode1] = true; + shankBits[0][ShiftRegisterBitExternalElectrode0] = true; + shankBits[1][ShiftRegisterBitExternalElectrode1] = true; + shankBits[1][ShiftRegisterBitExternalElectrode0] = true; + shankBits[2][ShiftRegisterBitExternalElectrode1] = true; + shankBits[2][ShiftRegisterBitExternalElectrode0] = true; + shankBits[3][ShiftRegisterBitExternalElectrode1] = true; + shankBits[3][ShiftRegisterBitExternalElectrode0] = true; + } + + return shankBits; + } + + internal override int GetReferenceBit(Enum reference) + { + var quadShankReference = (NeuropixelsV2QuadShankReference)reference; + + return quadShankReference switch + { + NeuropixelsV2QuadShankReference.External => 1, + NeuropixelsV2QuadShankReference.Tip1 => 2, + NeuropixelsV2QuadShankReference.Tip2 => 2, + NeuropixelsV2QuadShankReference.Tip3 => 2, + NeuropixelsV2QuadShankReference.Tip4 => 2, + NeuropixelsV2QuadShankReference.Ground => 3, + _ => throw new InvalidOperationException("Invalid reference selection."), + }; + } + + internal override bool IsGroundReference() => (NeuropixelsV2QuadShankReference)Reference == NeuropixelsV2QuadShankReference.Ground; + + /// + [XmlElement(nameof(Reference))] [Browsable(false)] [Externalizable(false)] - [XmlElement(nameof(ProbeGroup))] - public string ProbeGroupString + public override string ReferenceSerialized { - get - { - var jsonString = JsonConvert.SerializeObject(ProbeGroup); - return Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString)); - } + get => Reference.ToString(); set { - var jsonString = Encoding.UTF8.GetString(Convert.FromBase64String(value)); - ProbeGroup = JsonConvert.DeserializeObject(jsonString); - SelectElectrodes(NeuropixelsV2eProbeGroup.ToChannelMap(ProbeGroup)); + if (string.IsNullOrEmpty(value)) + { + Reference = NeuropixelsV2QuadShankReference.External; + return; + } + + Reference = Enum.TryParse(value, out var result) + ? result + : NeuropixelsV2QuadShankReference.External; } } } diff --git a/OpenEphys.Onix1/NeuropixelsV2eBetaRegisterContext.cs b/OpenEphys.Onix1/NeuropixelsV2eBetaRegisterContext.cs index 48b30a26..4d946aab 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eBetaRegisterContext.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eBetaRegisterContext.cs @@ -15,7 +15,7 @@ public NeuropixelsV2eBetaRegisterContext(DeviceContext deviceContext, uint i2cAd { } - public void WriteConfiguration(NeuropixelsV2QuadShankProbeConfiguration probe) + public void WriteConfiguration(NeuropixelsV2ProbeConfiguration probe) { var baseBits = NeuropixelsV2.GenerateBaseBits(probe); WriteShiftRegister(NeuropixelsV2eBeta.SR_CHAIN5, baseBits[0]); @@ -49,7 +49,7 @@ void WriteShiftRegister(uint srAddress, BitArray data) } } - if (ReadByte(NeuropixelsV2e.STATUS) != (uint) NeuropixelsV2Status.SR_OK) + if (ReadByte(NeuropixelsV2e.STATUS) != (uint)NeuropixelsV2Status.SR_OK) { Console.Error.WriteLine($"Warning: shift register {srAddress:X} status check failed. " + $"{ShankName(srAddress)} may be damaged."); diff --git a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs index ccc4cc20..3b2326c8 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs @@ -1,65 +1,23 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; +using System.Xml.Serialization; using OpenEphys.ProbeInterface.NET; namespace OpenEphys.Onix1 { /// - /// A class for NeuropixelsV2e. + /// Abstract class that defines a Neuropixels 2.0 Probe Group /// - public class NeuropixelsV2eProbeGroup : ProbeGroup + [XmlInclude(typeof(NeuropixelsV2eQuadShankProbeGroup))] + [XmlType(Namespace = Constants.XmlNamespace)] + public abstract class NeuropixelsV2eProbeGroup : ProbeGroup { - const float shankOffsetX = 200f; - const float shankWidthX = 70f; - const float shankPitchX = 250f; - const int numberOfShanks = 4; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The default constructor will initialize the new with - /// the default settings for all contacts, including their positions, shapes, and IDs. - /// - public NeuropixelsV2eProbeGroup() - : base("probeinterface", "0.2.21", DefaultProbes()) - { - } - - private static Probe[] DefaultProbes() - { - var probe = new Probe[1]; - - probe[0] = new(ProbeNdim.Two, - ProbeSiUnits.um, - new ProbeAnnotations("Neuropixels 2.0 - Multishank", "IMEC"), - null, - DefaultContactPositions(NeuropixelsV2.ElectrodePerShank * numberOfShanks), - Probe.DefaultContactPlaneAxes(NeuropixelsV2.ElectrodePerShank * numberOfShanks), - Probe.DefaultContactShapes(NeuropixelsV2.ElectrodePerShank * numberOfShanks, ContactShape.Square), - Probe.DefaultSquareParams(NeuropixelsV2.ElectrodePerShank * numberOfShanks, 12.0f), - DefaultProbePlanarContourQuadShank(), - DefaultDeviceChannelIndices(NeuropixelsV2.ChannelCount, NeuropixelsV2.ElectrodePerShank * numberOfShanks), - Probe.DefaultContactIds(NeuropixelsV2.ElectrodePerShank * numberOfShanks), - DefaultShankIds(NeuropixelsV2.ElectrodePerShank * numberOfShanks)); - - return probe; - } - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of a . /// - /// - /// This constructor is marked with the , and is the - /// entry point for deserializing the JSON data into a C# class. - /// - /// String defining the . - /// String defining the . - /// Array of Probes. - [JsonConstructor] - public NeuropixelsV2eProbeGroup(string specification, string version, Probe[] probes) + /// + public NeuropixelsV2eProbeGroup(string specification, string version, IEnumerable probes) : base(specification, version, probes) { } @@ -67,201 +25,15 @@ public NeuropixelsV2eProbeGroup(string specification, string version, Probe[] pr /// /// Copy constructor that initializes a copied instance of the class. /// - /// An existing object. + /// Existing object. public NeuropixelsV2eProbeGroup(NeuropixelsV2eProbeGroup probeGroup) : base(probeGroup) { } - /// - /// Generates a 2D array of default contact positions based on the given number of channels. - /// - /// Value defining the number of contacts to create positions for. - /// - /// 2D array of floats [N x 2], where the first dimension is the contact index [N] and the second dimension [2] - /// contains the X and Y values, respectively. - /// - public static float[][] DefaultContactPositions(int numberOfContacts) - { - float[][] contactPositions = new float[numberOfContacts][]; - - for (int i = 0; i < numberOfContacts; i++) - { - contactPositions[i] = DefaultContactPosition(i); - } - - return contactPositions; - } - - /// - /// Generates a float array containing the X and Y position of a single contact. - /// - /// Index of the contact. - /// A float array of size [2 x 1] with the X and Y coordinates, respectively. - public static float[] DefaultContactPosition(int contactIndex) - { - return new float[2] { ContactPositionX(contactIndex), contactIndex % NeuropixelsV2.ElectrodePerShank / 2 * 15 + 170 }; - } + internal abstract NeuropixelsV2eProbeGroup Clone(); - private static float ContactPositionX(int index) - { - var shank = index / NeuropixelsV2.ElectrodePerShank; - var offset = shankOffsetX + (shankWidthX + shankPitchX) * shank + 11; - - return (index % 2) switch - { - 0 => offset + 8.0f, - 1 => offset + 40.0f, - _ => throw new ArgumentException("Invalid index given.") - }; - } - - /// - /// Generates a default planar contour for the probe, based on the given probe index - /// - /// - public static float[][] DefaultProbePlanarContourQuadShank() - { - const int numberOfShanks = 4; - const float shankTipY = 0f; - const float shankBaseY = 155f; - const float shankLengthY = 10000f; - const float probeLengthY = 10155f; - - float[][] probePlanarContour = new float[25][]; - - probePlanarContour[0] = new float[2] { 0f, probeLengthY }; - probePlanarContour[1] = new float[2] { 0f, shankLengthY }; - - for (int i = 0; i < numberOfShanks; i++) - { - probePlanarContour[2 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i, shankLengthY }; - probePlanarContour[3 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i, shankBaseY }; - probePlanarContour[4 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX / 2, shankTipY }; - probePlanarContour[5 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX, shankBaseY }; - probePlanarContour[6 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX, shankLengthY }; - } - - probePlanarContour[22] = new float[2] { shankOffsetX * 2 + (shankWidthX + shankPitchX) * (numberOfShanks - 1) + shankWidthX, shankLengthY }; - probePlanarContour[23] = new float[2] { shankOffsetX * 2 + (shankWidthX + shankPitchX) * (numberOfShanks - 1) + shankWidthX, probeLengthY }; - probePlanarContour[24] = new float[2] { 0f, probeLengthY }; - - return probePlanarContour; - } - - /// - /// Generates a default planar contour for the probe, based on the given probe index - /// - /// - public static float[][] DefaultProbePlanarContourSingleShank() - { - float[][] probePlanarContour = new float[6][]; - - probePlanarContour[0] = new float[2] { -11f, 155f }; - probePlanarContour[1] = new float[2] { 24f, 0f }; - probePlanarContour[2] = new float[2] { 59f, 155f }; - probePlanarContour[3] = new float[2] { 59f, 10000f }; - probePlanarContour[4] = new float[2] { -11f, 10000f }; - probePlanarContour[5] = new float[2] { -11f, 155f }; - - return probePlanarContour; - } - - /// - /// Override of the DefaultDeviceChannelIndices function, which initializes a portion of the - /// device channel indices, and leaves the rest at -1 to indicate they are not actively recorded - /// - /// Number of contacts that are connected for recording - /// Total number of physical contacts on the probe - /// - public static int[] DefaultDeviceChannelIndices(int channelCount, int electrodeCount) - { - int[] deviceChannelIndices = new int[electrodeCount]; - - for (int i = 0; i < channelCount; i++) - { - deviceChannelIndices[i] = NeuropixelsV2QuadShankElectrode.GetChannelNumber(i / NeuropixelsV2.ElectrodePerShank, - i % NeuropixelsV2.ChannelCount / NeuropixelsV2.ElectrodePerBlock, - i % NeuropixelsV2.ElectrodePerBlock); - } - - for (int i = channelCount; i < electrodeCount; i++) - { - deviceChannelIndices[i] = -1; - } - - return deviceChannelIndices; - } - - /// - /// Generates an array of strings with the value "0" as the default shank ID - /// - /// Number of contacts in a single probe - /// - public static string[] DefaultShankIds(int numberOfContacts) - { - string[] contactIds = new string[numberOfContacts]; - - for (int i = 0; i < numberOfContacts; i++) - { - var shank = i / NeuropixelsV2.ElectrodePerShank; - contactIds[i] = shank switch - { - 0 => "0", - 1 => "1", - 2 => "2", - 3 => "3", - _ => throw new InvalidOperationException($"Too many shanks; expected four or less zero-indexed shanks, but received {shank} as an index.") - }; - } - - return contactIds; - } - - /// - /// Convert a ProbeInterface object to a list of electrodes, which includes all possible electrodes - /// - /// A object - /// List of electrodes - public static List ToElectrodes(NeuropixelsV2eProbeGroup probeGroup) - { - List electrodes = new(); - - foreach (var c in probeGroup.GetContacts()) - { - electrodes.Add(new NeuropixelsV2QuadShankElectrode(c.Index)); - } - - return electrodes; - } - - /// - /// Convert a object to a list of electrodes, which only includes currently enabled electrodes - /// - /// A object - /// List of electrodes that are enabled - public static NeuropixelsV2QuadShankElectrode[] ToChannelMap(NeuropixelsV2eProbeGroup probeGroup) - { - - var enabledContacts = probeGroup.GetContacts().Where(c => c.DeviceId != -1); - - if (enabledContacts.Count() != NeuropixelsV2.ChannelCount) - { - throw new InvalidOperationException($"Channel configuration must have {NeuropixelsV2.ChannelCount} contacts enabled." + - $"Instead there are {enabledContacts.Count()} contacts enabled. Enabled contacts are designated by a device channel" + - $"index >= 0."); - } - - return enabledContacts.Select(c => new NeuropixelsV2QuadShankElectrode(c.Index)) - .OrderBy(e => e.Channel) - .ToArray(); - } - - /// - /// Updates the based on the given channel map. - /// - /// Existing . - internal void UpdateDeviceChannelIndices(NeuropixelsV2QuadShankElectrode[] channelMap) + internal void UpdateDeviceChannelIndices(NeuropixelsV2Electrode[] channelMap) { int[] newDeviceChannelIndices = new int[NumberOfContacts]; @@ -277,5 +49,11 @@ internal void UpdateDeviceChannelIndices(NeuropixelsV2QuadShankElectrode[] chann UpdateDeviceChannelIndices(0, newDeviceChannelIndices); } + + /// + /// Convert a ProbeInterface object to a list of electrodes, which includes all possible electrodes. + /// + /// List of electrodes + public abstract List ToElectrodes(); } } diff --git a/OpenEphys.Onix1/NeuropixelsV2eQuadShankProbeGroup.cs b/OpenEphys.Onix1/NeuropixelsV2eQuadShankProbeGroup.cs new file mode 100644 index 00000000..0a7d9c6e --- /dev/null +++ b/OpenEphys.Onix1/NeuropixelsV2eQuadShankProbeGroup.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using OpenEphys.ProbeInterface.NET; + +namespace OpenEphys.Onix1 +{ + /// + /// A class for NeuropixelsV2e. + /// + public class NeuropixelsV2eQuadShankProbeGroup : NeuropixelsV2eProbeGroup + { + const float shankOffsetX = 200f; + const float shankWidthX = 70f; + const float shankPitchX = 250f; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The default constructor will initialize the new with + /// the default settings for all contacts, including their positions, shapes, and IDs. + /// + public NeuropixelsV2eQuadShankProbeGroup() + : base("probeinterface", "0.2.21", DefaultProbes()) + { + } + + static Probe[] DefaultProbes() + { + var probe = new Probe[1]; + + const int numberOfShanks = 4; + + probe[0] = new(ProbeNdim.Two, + ProbeSiUnits.um, + new ProbeAnnotations("Neuropixels 2.0 - multishank", "IMEC"), + null, + DefaultContactPositions(NeuropixelsV2.ElectrodePerShank * numberOfShanks), + Probe.DefaultContactPlaneAxes(NeuropixelsV2.ElectrodePerShank * numberOfShanks), + Probe.DefaultContactShapes(NeuropixelsV2.ElectrodePerShank * numberOfShanks, ContactShape.Square), + Probe.DefaultSquareParams(NeuropixelsV2.ElectrodePerShank * numberOfShanks, 12.0f), + DefaultProbePlanarContour(), + DefaultDeviceChannelIndices(NeuropixelsV2.ChannelCount, NeuropixelsV2.ElectrodePerShank * numberOfShanks), + Probe.DefaultContactIds(NeuropixelsV2.ElectrodePerShank * numberOfShanks), + DefaultShankIds(NeuropixelsV2.ElectrodePerShank * numberOfShanks)); + + return probe; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// This constructor is marked with the , and is the + /// entry point for deserializing the JSON data into a C# class. + /// + /// String defining the . + /// String defining the . + /// Array of Probes. + [JsonConstructor] + public NeuropixelsV2eQuadShankProbeGroup(string specification, string version, Probe[] probes) + : base(specification, version, probes) + { + } + + /// + /// Copy constructor that initializes a copied instance of the class. + /// + /// An existing object. + public NeuropixelsV2eQuadShankProbeGroup(NeuropixelsV2eQuadShankProbeGroup probeGroup) + : base(probeGroup) + { + } + + internal override NeuropixelsV2eProbeGroup Clone() + { + return new NeuropixelsV2eQuadShankProbeGroup(Specification, Version, Probes.Select(probe => new Probe(probe)).ToArray()); + } + + /// + /// Generates a 2D array of default contact positions based on the given number of channels. + /// + /// Value defining the number of contacts to create positions for. + /// + /// 2D array of floats [N x 2], where the first dimension is the contact index [N] and the second dimension [2] + /// contains the X and Y values, respectively. + /// + public static float[][] DefaultContactPositions(int numberOfContacts) + { + float[][] contactPositions = new float[numberOfContacts][]; + + for (int i = 0; i < numberOfContacts; i++) + { + contactPositions[i] = DefaultContactPosition(i); + } + + return contactPositions; + } + + /// + /// Generates a float array containing the X and Y position of a single contact. + /// + /// Index of the contact. + /// A float array of size [2 x 1] with the X and Y coordinates, respectively. + public static float[] DefaultContactPosition(int contactIndex) + { + return new float[2] { ContactPositionX(contactIndex), contactIndex % NeuropixelsV2.ElectrodePerShank / 2 * 15 + 170 }; + } + + private static float ContactPositionX(int index) + { + var shank = index / NeuropixelsV2.ElectrodePerShank; + var offset = shankOffsetX + (shankWidthX + shankPitchX) * shank + 11; + + return (index % 2) switch + { + 0 => offset + 8.0f, + 1 => offset + 40.0f, + _ => throw new ArgumentException("Invalid index given.") + }; + } + + /// + /// Generates a default planar contour for the type of probe that is given. + /// + /// + public static float[][] DefaultProbePlanarContour() + { + const int numberOfShanks = 4; + const float shankTipY = 0f; + const float shankBaseY = 155f; + const float shankLengthY = 10000f; + const float probeLengthY = 10155f; + + float[][] probePlanarContour = new float[25][]; + + probePlanarContour[0] = new float[2] { 0f, probeLengthY }; + probePlanarContour[1] = new float[2] { 0f, shankLengthY }; + + for (int i = 0; i < numberOfShanks; i++) + { + probePlanarContour[2 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i, shankLengthY }; + probePlanarContour[3 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i, shankBaseY }; + probePlanarContour[4 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX / 2, shankTipY }; + probePlanarContour[5 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX, shankBaseY }; + probePlanarContour[6 + i * 5] = new float[2] { shankOffsetX + (shankWidthX + shankPitchX) * i + shankWidthX, shankLengthY }; + } + + probePlanarContour[22] = new float[2] { shankOffsetX * 2 + (shankWidthX + shankPitchX) * (numberOfShanks - 1) + shankWidthX, shankLengthY }; + probePlanarContour[23] = new float[2] { shankOffsetX * 2 + (shankWidthX + shankPitchX) * (numberOfShanks - 1) + shankWidthX, probeLengthY }; + probePlanarContour[24] = new float[2] { 0f, probeLengthY }; + + return probePlanarContour; + } + + /// + /// Generates the first default device channel indices, and + /// marks the rest as -1 to indicate they are not actively recorded. + /// + /// Number of contacts that are connected for recording. + /// Total number of physical contacts on the probe. + /// + public static int[] DefaultDeviceChannelIndices(int channelCount, int electrodeCount) + { + int[] deviceChannelIndices = new int[electrodeCount]; + + for (int i = 0; i < channelCount; i++) + { + deviceChannelIndices[i] = NeuropixelsV2QuadShankElectrode.GetChannelNumber(i); + } + + for (int i = channelCount; i < electrodeCount; i++) + { + deviceChannelIndices[i] = -1; + } + + return deviceChannelIndices; + } + + /// + /// Generates an array of strings with the shank value as the default shank ID. + /// + /// Number of contacts in a single probe. + /// + public static string[] DefaultShankIds(int numberOfContacts) + { + string[] contactIds = new string[numberOfContacts]; + + for (int i = 0; i < numberOfContacts; i++) + { + var shank = i / NeuropixelsV2.ElectrodePerShank; + contactIds[i] = shank switch + { + 0 => "0", + 1 => "1", + 2 => "2", + 3 => "3", + _ => throw new InvalidOperationException($"Too many shanks; expected four shanks, but received {shank} as an index.") + }; + } + + return contactIds; + } + + /// + /// Convert a ProbeInterface object to a list of electrodes, which includes all possible electrodes. + /// + /// List of electrodes. + public override List ToElectrodes() + { + List electrodes = new(); + + foreach (var c in GetContacts()) + { + electrodes.Add(new NeuropixelsV2QuadShankElectrode(c.Index)); + } + + return electrodes; + } + + /// + /// Convert a object to a list of electrodes, + /// which only includes currently enabled electrodes. + /// + /// A object. + /// List of electrodes that are enabled. + public static NeuropixelsV2QuadShankElectrode[] ToChannelMap(NeuropixelsV2eQuadShankProbeGroup probeGroup) + { + var enabledContacts = probeGroup.GetContacts().Where(c => c.DeviceId != -1); + + if (enabledContacts.Count() != NeuropixelsV2.ChannelCount) + { + throw new InvalidOperationException($"Channel configuration must have {NeuropixelsV2.ChannelCount} contacts enabled." + + $"Instead there are {enabledContacts.Count()} contacts enabled. Enabled contacts are designated by a device channel" + + $"index >= 0."); + } + + return enabledContacts.Select(c => new NeuropixelsV2QuadShankElectrode(c.Index)) + .OrderBy(e => e.Channel) + .ToArray(); + } + } +} diff --git a/OpenEphys.Onix1/NeuropixelsV2eRegisterContext.cs b/OpenEphys.Onix1/NeuropixelsV2eRegisterContext.cs index 19c5b41a..b7af937b 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eRegisterContext.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eRegisterContext.cs @@ -15,7 +15,7 @@ public NeuropixelsV2eRegisterContext(DeviceContext deviceContext, uint i2cAddres { } - public void WriteConfiguration(NeuropixelsV2QuadShankProbeConfiguration probe) + public void WriteConfiguration(NeuropixelsV2ProbeConfiguration probe) { var baseBits = NeuropixelsV2.GenerateBaseBits(probe); WriteShiftRegister(NeuropixelsV2e.SR_CHAIN5, baseBits[0]); diff --git a/OpenEphys.Onix1/ProbeInterfaceHelper.cs b/OpenEphys.Onix1/ProbeInterfaceHelper.cs index ad408250..df968485 100644 --- a/OpenEphys.Onix1/ProbeInterfaceHelper.cs +++ b/OpenEphys.Onix1/ProbeInterfaceHelper.cs @@ -8,13 +8,15 @@ internal static class ProbeInterfaceHelper { public const string ProbeInterfaceFileNameFilter = "ProbeInterface Files|*.json|All Files|*.*"; - public static T LoadExternalProbeInterfaceFile(string probeInterfaceFileName) where T : ProbeGroup + public static ProbeGroup LoadExternalProbeInterfaceFile(string probeInterfaceFileName, Type type) { if (string.IsNullOrEmpty(probeInterfaceFileName)) { throw new ArgumentNullException(nameof(probeInterfaceFileName), "ProbeInterface file path cannot be null or empty."); } + if (!type.IsAssignableFrom(typeof(ProbeGroup))) throw new InvalidDataException($"Invalid type given: {type.Name} is not a valid {nameof(ProbeGroup)} type."); + if (!File.Exists(probeInterfaceFileName)) { throw new FileNotFoundException($"The ProbeInterface file '{probeInterfaceFileName}' does not exist."); @@ -23,7 +25,7 @@ public static T LoadExternalProbeInterfaceFile(string probeInterfaceFileName) try { string jsonContent = File.ReadAllText(probeInterfaceFileName); - var result = JsonHelper.DeserializeString(jsonContent) ?? throw new InvalidDataException($"Failed to parse ProbeInterface file: {probeInterfaceFileName}"); + var result = JsonHelper.DeserializeString(jsonContent, type) as ProbeGroup ?? throw new InvalidDataException($"Failed to parse ProbeInterface file: {probeInterfaceFileName}"); return result; } catch (UnauthorizedAccessException e)