diff --git a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs index 08aa2399..28bb44b9 100644 --- a/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/ChannelConfigurationDialog.cs @@ -291,7 +291,21 @@ internal virtual bool OpenFile() where T : ProbeGroup return false; } - if (ProbeGroup.NumberOfContacts == newConfiguration.NumberOfContacts) + bool skipContactNumberMismatchCheck = false; + + if (ProbeGroup.Probes.First().Annotations.Name != newConfiguration.Probes.First().Annotations.Name) + { + var result = MessageBox.Show($"There is a mismatch between the current probe type ({ProbeGroup.Probes.First().Annotations.Name})" + + $" and the new probe type ({newConfiguration.Probes.First().Annotations.Name}). Continue loading?", "Probe Type Mismatch", MessageBoxButtons.YesNo); + + if (result == DialogResult.No) + return false; + + skipContactNumberMismatchCheck = true; // NB: If the probe names do not match, skip the check to see if the number of contacts match. + // Example: loading a Neuropixels single-shank 2.0 probe, but the current probe is a quad-shank 2.0 probe. + } + + if (skipContactNumberMismatchCheck || ProbeGroup.NumberOfContacts == newConfiguration.NumberOfContacts) { newConfiguration.Validate(); diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs index b80302d9..134a2192 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs @@ -26,8 +26,11 @@ public NeuropixelsV2eDialog(IConfigureNeuropixelsV2 configureNode) InitializeComponent(); Shown += FormShown; + bool isBeta = false; + if (configureNode is ConfigureNeuropixelsV2eBeta configureV2eBeta) { + isBeta = true; ConfigureNode = new ConfigureNeuropixelsV2eBeta(configureV2eBeta); Text = Text.Replace("NeuropixelsV2e ", "NeuropixelsV2eBeta "); } @@ -38,7 +41,7 @@ public NeuropixelsV2eDialog(IConfigureNeuropixelsV2 configureNode) ProbeConfigurations = new List { - new(ConfigureNode.ProbeConfigurationA, ConfigureNode.GainCalibrationFileA, ConfigureNode.InvertPolarity) + new(ConfigureNode.ProbeConfigurationA, ConfigureNode.GainCalibrationFileA, ConfigureNode.InvertPolarity, isBeta) { TopLevel = false, FormBorderStyle = FormBorderStyle.None, @@ -46,7 +49,7 @@ public NeuropixelsV2eDialog(IConfigureNeuropixelsV2 configureNode) Parent = this, Tag = NeuropixelsV2Probe.ProbeA }, - new(ConfigureNode.ProbeConfigurationB, ConfigureNode.GainCalibrationFileB, ConfigureNode.InvertPolarity) + new(ConfigureNode.ProbeConfigurationB, ConfigureNode.GainCalibrationFileB, ConfigureNode.InvertPolarity, isBeta) { TopLevel = false, FormBorderStyle = FormBorderStyle.None, diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs index f1e2c72a..a26c95eb 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.Designer.cs @@ -35,6 +35,7 @@ private void InitializeComponent() System.Windows.Forms.Label label1; System.Windows.Forms.Label invertPolarity; System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NeuropixelsV2eProbeConfigurationDialog)); + this.labelProbeType = new System.Windows.Forms.Label(); this.toolStripLabelGainCalibrationSN = new System.Windows.Forms.ToolStripStatusLabel(); this.menuStrip = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -46,6 +47,7 @@ private void InitializeComponent() this.panelTrackBar = new System.Windows.Forms.Panel(); this.trackBarProbePosition = new System.Windows.Forms.TrackBar(); this.panelChannelOptions = new System.Windows.Forms.Panel(); + this.comboBoxProbeType = new System.Windows.Forms.ComboBox(); this.checkBoxInvertPolarity = new System.Windows.Forms.CheckBox(); this.textBoxGainCorrection = new System.Windows.Forms.TextBox(); this.textBoxProbeCalibrationFile = new System.Windows.Forms.TextBox(); @@ -84,19 +86,19 @@ private void InitializeComponent() // Reference // Reference.AutoSize = true; - Reference.Location = new System.Drawing.Point(15, 114); + Reference.Location = new System.Drawing.Point(15, 142); Reference.Name = "Reference"; Reference.Size = new System.Drawing.Size(73, 16); - Reference.TabIndex = 5; + Reference.TabIndex = 7; Reference.Text = "Reference:"; // // labelPresets // labelPresets.AutoSize = true; - labelPresets.Location = new System.Drawing.Point(15, 146); + labelPresets.Location = new System.Drawing.Point(15, 170); labelPresets.Name = "labelPresets"; labelPresets.Size = new System.Drawing.Size(59, 32); - labelPresets.TabIndex = 7; + labelPresets.TabIndex = 9; labelPresets.Text = "Channel \nPresets:"; // // label1 @@ -111,12 +113,21 @@ private void InitializeComponent() // invertPolarity // invertPolarity.AutoSize = true; - invertPolarity.Location = new System.Drawing.Point(15, 187); + invertPolarity.Location = new System.Drawing.Point(15, 213); invertPolarity.Name = "invertPolarity"; invertPolarity.Size = new System.Drawing.Size(55, 32); - invertPolarity.TabIndex = 9; + invertPolarity.TabIndex = 11; invertPolarity.Text = "Invert\r\nPolarity:"; // + // labelProbeType + // + this.labelProbeType.AutoSize = true; + this.labelProbeType.Location = new System.Drawing.Point(15, 106); + this.labelProbeType.Name = "labelProbeType"; + this.labelProbeType.Size = new System.Drawing.Size(82, 16); + this.labelProbeType.TabIndex = 5; + this.labelProbeType.Text = "Probe Type:"; + // // toolStripLabelGainCalibrationSN // this.toolStripLabelGainCalibrationSN.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold); @@ -148,11 +159,11 @@ private void InitializeComponent() // this.buttonEnableContacts.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.buttonEnableContacts.Location = new System.Drawing.Point(15, 232); + this.buttonEnableContacts.Location = new System.Drawing.Point(15, 252); this.buttonEnableContacts.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.buttonEnableContacts.Name = "buttonEnableContacts"; this.buttonEnableContacts.Size = new System.Drawing.Size(280, 44); - this.buttonEnableContacts.TabIndex = 11; + this.buttonEnableContacts.TabIndex = 13; this.buttonEnableContacts.Text = "Enable Selected Electrodes"; this.toolTip.SetToolTip(this.buttonEnableContacts, "Click and drag to select electrodes in the probe view. \r\nPress this button to ena" + "ble the selected electrodes. \r\nNot all electrode combinations are possible."); @@ -163,11 +174,11 @@ private void InitializeComponent() // this.buttonClearSelections.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.buttonClearSelections.Location = new System.Drawing.Point(15, 282); + this.buttonClearSelections.Location = new System.Drawing.Point(15, 302); this.buttonClearSelections.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.buttonClearSelections.Name = "buttonClearSelections"; this.buttonClearSelections.Size = new System.Drawing.Size(280, 44); - this.buttonClearSelections.TabIndex = 12; + this.buttonClearSelections.TabIndex = 14; this.buttonClearSelections.Text = "Clear Electrode Selection"; this.toolTip.SetToolTip(this.buttonClearSelections, "Deselect all electrodes in the probe view. \r\nNote that this does not disable elec" + "trodes, but simply deselects them."); @@ -177,7 +188,7 @@ private void InitializeComponent() // buttonChooseCalibrationFile // this.buttonChooseCalibrationFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(258, 29); + this.buttonChooseCalibrationFile.Location = new System.Drawing.Point(257, 30); this.buttonChooseCalibrationFile.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.buttonChooseCalibrationFile.Name = "buttonChooseCalibrationFile"; this.buttonChooseCalibrationFile.Size = new System.Drawing.Size(37, 25); @@ -229,6 +240,8 @@ private void InitializeComponent() this.panelChannelOptions.AutoSize = true; this.panelChannelOptions.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; this.panelChannelOptions.BackColor = System.Drawing.SystemColors.ControlLightLight; + this.panelChannelOptions.Controls.Add(this.comboBoxProbeType); + this.panelChannelOptions.Controls.Add(this.labelProbeType); this.panelChannelOptions.Controls.Add(this.checkBoxInvertPolarity); this.panelChannelOptions.Controls.Add(invertPolarity); this.panelChannelOptions.Controls.Add(this.textBoxGainCorrection); @@ -247,16 +260,28 @@ private void InitializeComponent() this.panelChannelOptions.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.panelChannelOptions.Name = "panelChannelOptions"; this.panelChannelOptions.Size = new System.Drawing.Size(309, 662); - this.panelChannelOptions.TabIndex = 1; + this.panelChannelOptions.TabIndex = 0; + // + // comboBoxProbeType + // + this.comboBoxProbeType.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.comboBoxProbeType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxProbeType.FormattingEnabled = true; + this.comboBoxProbeType.Location = new System.Drawing.Point(107, 102); + this.comboBoxProbeType.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); + this.comboBoxProbeType.Name = "comboBoxProbeType"; + this.comboBoxProbeType.Size = new System.Drawing.Size(188, 24); + this.comboBoxProbeType.TabIndex = 6; // // checkBoxInvertPolarity // this.checkBoxInvertPolarity.AutoSize = true; - this.checkBoxInvertPolarity.Location = new System.Drawing.Point(107, 193); + this.checkBoxInvertPolarity.Location = new System.Drawing.Point(107, 219); this.checkBoxInvertPolarity.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.checkBoxInvertPolarity.Name = "checkBoxInvertPolarity"; this.checkBoxInvertPolarity.Size = new System.Drawing.Size(77, 20); - this.checkBoxInvertPolarity.TabIndex = 10; + this.checkBoxInvertPolarity.TabIndex = 12; this.checkBoxInvertPolarity.Text = "Enabled"; this.checkBoxInvertPolarity.UseVisualStyleBackColor = true; // @@ -280,7 +305,7 @@ private void InitializeComponent() this.textBoxProbeCalibrationFile.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.textBoxProbeCalibrationFile.Name = "textBoxProbeCalibrationFile"; this.textBoxProbeCalibrationFile.ReadOnly = true; - this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(235, 22); + this.textBoxProbeCalibrationFile.Size = new System.Drawing.Size(272, 22); this.textBoxProbeCalibrationFile.TabIndex = 1; this.textBoxProbeCalibrationFile.TabStop = false; this.textBoxProbeCalibrationFile.TextChanged += new System.EventHandler(this.FileTextChanged); @@ -291,11 +316,11 @@ private void InitializeComponent() | System.Windows.Forms.AnchorStyles.Right))); this.comboBoxReference.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxReference.FormattingEnabled = true; - this.comboBoxReference.Location = new System.Drawing.Point(107, 110); + this.comboBoxReference.Location = new System.Drawing.Point(107, 138); this.comboBoxReference.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.comboBoxReference.Name = "comboBoxReference"; this.comboBoxReference.Size = new System.Drawing.Size(188, 24); - this.comboBoxReference.TabIndex = 6; + this.comboBoxReference.TabIndex = 8; // // comboBoxChannelPresets // @@ -303,11 +328,11 @@ private void InitializeComponent() | System.Windows.Forms.AnchorStyles.Right))); this.comboBoxChannelPresets.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxChannelPresets.FormattingEnabled = true; - this.comboBoxChannelPresets.Location = new System.Drawing.Point(107, 150); + this.comboBoxChannelPresets.Location = new System.Drawing.Point(107, 174); this.comboBoxChannelPresets.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); this.comboBoxChannelPresets.Name = "comboBoxChannelPresets"; this.comboBoxChannelPresets.Size = new System.Drawing.Size(188, 24); - this.comboBoxChannelPresets.TabIndex = 8; + this.comboBoxChannelPresets.TabIndex = 10; // // buttonCancel // @@ -354,6 +379,7 @@ private void InitializeComponent() this.tableLayoutPanel1.RowCount = 2; this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 46F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); this.tableLayoutPanel1.Size = new System.Drawing.Size(1234, 712); this.tableLayoutPanel1.TabIndex = 0; // @@ -445,5 +471,7 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripStatusLabel toolStripGainCalSN; private System.Windows.Forms.ToolStripStatusLabel toolStripLabelGainCalibrationSN; private System.Windows.Forms.CheckBox checkBoxInvertPolarity; + private System.Windows.Forms.ComboBox comboBoxProbeType; + private System.Windows.Forms.Label labelProbeType; } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs index 2188fab5..d83553de 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Drawing; using System.IO; using System.Linq; using System.Windows.Forms; @@ -53,6 +52,15 @@ enum QuadShankChannelPreset None } + enum SingleShankChannelPreset + { + BankA, + BankB, + BankC, + BankD, + None + } + /// /// Public object that is manipulated by /// . @@ -62,6 +70,8 @@ public NeuropixelsV2ProbeConfiguration ProbeConfiguration get => ChannelConfiguration.ProbeConfiguration; } + readonly Dictionary probeConfigurations; + /// public bool InvertPolarity { get; set; } @@ -71,14 +81,23 @@ public NeuropixelsV2ProbeConfiguration ProbeConfiguration /// 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(NeuropixelsV2ProbeConfiguration configuration, string calibrationFile, bool invertPolarity) + /// Boolean indicating if this is a beta probe or not. + public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2ProbeConfiguration configuration, string calibrationFile, bool invertPolarity, bool isBeta) { InitializeComponent(); Shown += FormShown; textBoxProbeCalibrationFile.Text = calibrationFile; - ChannelConfiguration = new(configuration) + probeConfigurations = new() + { + [NeuropixelsV2ProbeType.SingleShank] = new(configuration.Probe, NeuropixelsV2ProbeType.SingleShank, configuration.Reference), + [NeuropixelsV2ProbeType.QuadShank] = new(configuration.Probe, NeuropixelsV2ProbeType.QuadShank, configuration.Reference) + }; + + probeConfigurations[configuration.ProbeType].SelectElectrodes(configuration.ChannelMap); + + ChannelConfiguration = new(probeConfigurations[configuration.ProbeType]) { TopLevel = false, FormBorderStyle = FormBorderStyle.None, @@ -94,9 +113,13 @@ public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2ProbeConfiguration co ChannelConfiguration.OnZoom += UpdateTrackBarLocation; ChannelConfiguration.OnFileLoad += OnFileLoadEvent; - comboBoxReference.DataSource = Enum.GetValues(typeof(NeuropixelsV2ShankReference)); - comboBoxReference.SelectedItem = ProbeConfiguration.Reference; - comboBoxReference.SelectedIndexChanged += SelectedReferenceChanged; + comboBoxProbeType.DataSource = Enum.GetValues(typeof(NeuropixelsV2ProbeType)); + comboBoxProbeType.SelectedItem = ProbeConfiguration.ProbeType; + + if (isBeta) + comboBoxProbeType.Enabled = false; + else + comboBoxProbeType.SelectedIndexChanged += SelectedProbeTypeChanged; comboBoxChannelPresets.DataSource = GetComboBoxChannelPresets(ProbeConfiguration.ProbeType); comboBoxChannelPresets.SelectedIndexChanged += SelectedChannelPresetChanged; @@ -104,17 +127,18 @@ public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2ProbeConfiguration co checkBoxInvertPolarity.Checked = InvertPolarity; checkBoxInvertPolarity.CheckedChanged += InvertPolarityIndexChanged; - CheckForExistingChannelPreset(); - CheckStatus(); Text += ": " + ProbeConfiguration.Probe.ToString(); + + UpdateProbeConfiguration(); } static Array GetComboBoxChannelPresets(NeuropixelsV2ProbeType probeType) { return probeType switch { + NeuropixelsV2ProbeType.SingleShank => Enum.GetValues(typeof(SingleShankChannelPreset)), NeuropixelsV2ProbeType.QuadShank => Enum.GetValues(typeof(QuadShankChannelPreset)), _ => throw new InvalidEnumArgumentException(nameof(NeuropixelsV2ProbeType)) }; @@ -154,6 +178,34 @@ private void FormShown(object sender, EventArgs e) ChannelConfiguration.ConnectResizeEventHandler(); } + void UpdateProbeConfiguration() + { + var probeType = (NeuropixelsV2ProbeType)comboBoxProbeType.SelectedItem; + + ChannelConfiguration.ProbeConfiguration = probeConfigurations[probeType]; + ChannelConfiguration.ProbeGroup = ProbeConfiguration.ProbeGroup; + + ChannelConfiguration.DrawProbeGroup(); + ChannelConfiguration.ResetZoom(); + ChannelConfiguration.RefreshZedGraph(); + + comboBoxChannelPresets.SelectedIndexChanged -= SelectedChannelPresetChanged; // NB: Temporarily detach handler so the loaded electrode configuration is respected + comboBoxChannelPresets.DataSource = GetComboBoxChannelPresets(ProbeConfiguration.ProbeType); + comboBoxChannelPresets.SelectedIndexChanged += SelectedChannelPresetChanged; + + comboBoxReference.SelectedIndexChanged -= SelectedReferenceChanged; + comboBoxReference.DataSource = NeuropixelsV2ProbeConfiguration.FilterNeuropixelsV2ShankReference(ProbeConfiguration.ProbeType); + comboBoxReference.SelectedItem = ProbeConfiguration.Reference; + comboBoxReference.SelectedIndexChanged += SelectedReferenceChanged; + + CheckForExistingChannelPreset(); + } + + void SelectedProbeTypeChanged(object sender, EventArgs e) + { + UpdateProbeConfiguration(); + } + private void SelectedReferenceChanged(object sender, EventArgs e) { ProbeConfiguration.Reference = (NeuropixelsV2ShankReference)((ComboBox)sender).SelectedItem; @@ -163,6 +215,9 @@ private void SelectedChannelPresetChanged(object sender, EventArgs e) { switch (ProbeConfiguration.ProbeType) { + case NeuropixelsV2ProbeType.SingleShank: + SetSingleShankChannelPreset((SingleShankChannelPreset)((ComboBox)sender).SelectedItem); + break; case NeuropixelsV2ProbeType.QuadShank: SetQuadShankChannelPreset((QuadShankChannelPreset)((ComboBox)sender).SelectedItem); break; @@ -176,6 +231,32 @@ private void SelectedChannelPresetChanged(object sender, EventArgs e) ChannelConfiguration.RefreshZedGraph(); } + void SetSingleShankChannelPreset(SingleShankChannelPreset preset) + { + var electrodes = NeuropixelsV2eProbeGroup.ToElectrodes(ProbeConfiguration.ProbeGroup, NeuropixelsV2ProbeType.SingleShank); + + switch (preset) + { + case SingleShankChannelPreset.BankA: + ProbeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2Bank.A).ToArray()); + break; + + case SingleShankChannelPreset.BankB: + ProbeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2Bank.B).ToArray()); + break; + + case SingleShankChannelPreset.BankC: + ProbeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2Bank.C).ToArray()); + break; + + case SingleShankChannelPreset.BankD: + ProbeConfiguration.SelectElectrodes(electrodes.Where(e => e.Bank == NeuropixelsV2Bank.D || + (e.Bank == NeuropixelsV2Bank.C && e.Index >= 896)).ToArray()); + break; + } + + } + void SetQuadShankChannelPreset(QuadShankChannelPreset preset) { var electrodes = NeuropixelsV2eProbeGroup.ToElectrodes(ProbeConfiguration.ProbeGroup, NeuropixelsV2ProbeType.QuadShank); @@ -363,6 +444,8 @@ void CheckForExistingChannelPreset() { switch (ProbeConfiguration.ProbeType) { + case NeuropixelsV2ProbeType.SingleShank: + CheckSingleShankForChannelPreset(ProbeConfiguration.ChannelMap); break; case NeuropixelsV2ProbeType.QuadShank: CheckQuadShankForChannelPreset(ProbeConfiguration.ChannelMap); break; default: @@ -370,6 +453,31 @@ void CheckForExistingChannelPreset() } } + void CheckSingleShankForChannelPreset(NeuropixelsV2Electrode[] channelMap) + { + if (channelMap.All(e => e.Bank == NeuropixelsV2Bank.A)) + { + comboBoxChannelPresets.SelectedItem = SingleShankChannelPreset.BankA; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2Bank.B)) + { + comboBoxChannelPresets.SelectedItem = SingleShankChannelPreset.BankB; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2Bank.C)) + { + comboBoxChannelPresets.SelectedItem = SingleShankChannelPreset.BankC; + } + else if (channelMap.All(e => e.Bank == NeuropixelsV2Bank.D || + (e.Bank == NeuropixelsV2Bank.C && e.Index >= BankDStartIndex))) + { + comboBoxChannelPresets.SelectedItem = SingleShankChannelPreset.BankD; + } + else + { + comboBoxChannelPresets.SelectedItem = SingleShankChannelPreset.None; + } + } + void CheckQuadShankForChannelPreset(NeuropixelsV2Electrode[] channelMap) { if (channelMap.All(e => e.Bank == NeuropixelsV2Bank.A && @@ -552,7 +660,25 @@ void CheckQuadShankForChannelPreset(NeuropixelsV2Electrode[] channelMap) private void OnFileLoadEvent(object sender, EventArgs e) { - CheckForExistingChannelPreset(); + NeuropixelsV2ProbeType probeType; + + try + { + probeType = NeuropixelsV2eProbeGroup.GetProbeTypeFromProbeName(ChannelConfiguration.ProbeGroup.Probes.First().Annotations.Name); + } + catch (ArgumentException ex) + { + MessageBox.Show(ex.Message); + return; + } + + probeConfigurations[probeType] = new((NeuropixelsV2eProbeGroup)ChannelConfiguration.ProbeGroup, + probeConfigurations[probeType].Probe, + probeConfigurations[probeType].ProbeType, + probeConfigurations[probeType].Reference); + + comboBoxProbeType.SelectedItem = probeType; + UpdateProbeConfiguration(); } private void FileTextChanged(object sender, EventArgs e) diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs index e613d2c2..8db2a3d4 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs @@ -35,7 +35,7 @@ public override object EditValue(ITypeDescriptorContext context, IServiceProvide bool isBeta = instance is ConfigureNeuropixelsV2eBeta; - using var editorDialog = new NeuropixelsV2eProbeConfigurationDialog(configuration, calibrationFile, instance.InvertPolarity); + using var editorDialog = new NeuropixelsV2eProbeConfigurationDialog(configuration, calibrationFile, instance.InvertPolarity, isBeta); if (isBeta) { diff --git a/OpenEphys.Onix1/NeuropixelsV2.cs b/OpenEphys.Onix1/NeuropixelsV2.cs index 85f92638..5527ea7d 100644 --- a/OpenEphys.Onix1/NeuropixelsV2.cs +++ b/OpenEphys.Onix1/NeuropixelsV2.cs @@ -30,6 +30,7 @@ static class NeuropixelsV2 public const int BaseBitsPerChannel = 4; public const int ElectrodePerShank = 1280; public const int ElectrodePerBlockQuadShank = 48; + public const int ElectrodePerBlockSingleShank = 32; public const int ReferencePixelCount = 4; public const int DummyRegisterCount = 4; public const int RegistersPerShank = ElectrodePerShank + ReferencePixelCount + DummyRegisterCount; @@ -44,7 +45,26 @@ internal static BitArray[] GenerateShankBits(NeuropixelsV2ProbeConfiguration pro const int ShiftRegisterBitTipElectrode0 = 644; const int ShiftRegisterBitTipElectrode1 = 643; - if (probe.ProbeType == NeuropixelsV2ProbeType.QuadShank) + if (probe.ProbeType == NeuropixelsV2ProbeType.SingleShank) + { + shankBits = new BitArray[] + { + new(RegistersPerShank, false) + }; + const int Shank = 0; + + if (probe.Reference == NeuropixelsV2ShankReference.Tip) + { + shankBits[Shank][ShiftRegisterBitTipElectrode1] = true; + shankBits[Shank][ShiftRegisterBitTipElectrode0] = true; + } + else if (probe.Reference == NeuropixelsV2ShankReference.External) + { + shankBits[Shank][ShiftRegisterBitExternalElectrode0] = true; + shankBits[Shank][ShiftRegisterBitExternalElectrode1] = true; + } + } + else if (probe.ProbeType == NeuropixelsV2ProbeType.QuadShank) { shankBits = new BitArray[] { @@ -117,6 +137,13 @@ internal static BitArray[] GenerateBaseBits(NeuropixelsV2ProbeConfiguration prob var referenceBit = probe.ProbeType switch { + NeuropixelsV2ProbeType.SingleShank => probe.Reference switch + { + NeuropixelsV2ShankReference.External => 1, + NeuropixelsV2ShankReference.Tip => 2, + NeuropixelsV2ShankReference.Ground => 3, + _ => throw new InvalidOperationException("Invalid reference selection."), + }, NeuropixelsV2ProbeType.QuadShank => probe.Reference switch { NeuropixelsV2ShankReference.External => 1, diff --git a/OpenEphys.Onix1/NeuropixelsV2Electrode.cs b/OpenEphys.Onix1/NeuropixelsV2Electrode.cs index 04750df3..6a28101e 100644 --- a/OpenEphys.Onix1/NeuropixelsV2Electrode.cs +++ b/OpenEphys.Onix1/NeuropixelsV2Electrode.cs @@ -42,7 +42,11 @@ public NeuropixelsV2Electrode(int index, NeuropixelsV2ProbeType probeType) BlockIndex = GetBlockIndex(index, probeType); Position = GetPosition(index); - if (probeType == NeuropixelsV2ProbeType.QuadShank) + if (probeType == NeuropixelsV2ProbeType.SingleShank) + { + Channel = GetSingleShankChannelNumber(Bank, Block, GetRow(index), index % 2 == 0); + } + else if (probeType == NeuropixelsV2ProbeType.QuadShank) { Channel = GetQuadShankChannelNumber(Shank, Block, BlockIndex); } @@ -64,7 +68,10 @@ private PointF GetPosition(int electrodeNumber) static int GetBlock(int index, NeuropixelsV2ProbeType probeType) { - if (probeType == NeuropixelsV2ProbeType.QuadShank) + if (probeType == NeuropixelsV2ProbeType.SingleShank) + return (index % NeuropixelsV2.ChannelCount) / NeuropixelsV2.ElectrodePerBlockSingleShank; + + else if (probeType == NeuropixelsV2ProbeType.QuadShank) return (GetIntraShankIndex(index) % NeuropixelsV2.ChannelCount) / NeuropixelsV2.ElectrodePerBlockQuadShank; else @@ -73,9 +80,14 @@ static int GetBlock(int index, NeuropixelsV2ProbeType probeType) const int ElectrodesPerRow = 2; + static int GetRow(int index) => (index % NeuropixelsV2.ElectrodePerBlockSingleShank) / ElectrodesPerRow; + static int GetBlockIndex(int index, NeuropixelsV2ProbeType probeType) { - if (probeType == NeuropixelsV2ProbeType.QuadShank) + if (probeType == NeuropixelsV2ProbeType.SingleShank) + return GetIntraShankIndex(index) % NeuropixelsV2.ElectrodePerBlockSingleShank; + + else if (probeType == NeuropixelsV2ProbeType.QuadShank) return GetIntraShankIndex(index) % NeuropixelsV2.ElectrodePerBlockQuadShank; else @@ -90,7 +102,16 @@ static int GetBlockIndex(int index, NeuropixelsV2ProbeType probeType) /// An integer between 0 and 383 defining the channel number. public static int GetChannelNumber(int electrodeIndex, NeuropixelsV2ProbeType probeType) { - if (probeType == NeuropixelsV2ProbeType.QuadShank) + if (probeType == NeuropixelsV2ProbeType.SingleShank) + { + var bank = GetBank(electrodeIndex); + var block = GetBlock(electrodeIndex, probeType); + var row = GetRow(electrodeIndex); + bool isEven = electrodeIndex % 2 == 0; + + return GetSingleShankChannelNumber(bank, block, row, isEven); + } + else if (probeType == NeuropixelsV2ProbeType.QuadShank) { var shank = GetShank(electrodeIndex); var block = GetBlock(electrodeIndex, probeType); @@ -102,6 +123,31 @@ public static int GetChannelNumber(int electrodeIndex, NeuropixelsV2ProbeType pr throw new InvalidOperationException("Unknown probe type given."); } + internal static int GetSingleShankChannelNumber(NeuropixelsV2Bank bank, int block, int row, bool even) + { + const int MaxBlockValue = 11; + const int MaxRowValue = 15; + + if (block > MaxBlockValue || block < 0) + throw new ArgumentOutOfRangeException($"Block value is out of range. Expected to be between 0 and {MaxBlockValue}, but value is {block}"); + + if (row > MaxRowValue || row < 0) + throw new ArgumentOutOfRangeException($"Row value is out of range. Expected to be between 0 and {MaxRowValue}, but value is {row}"); + + int offset = even ? 0 : 1; // NB: Left electrodes (even numbers) have no offset, while right electrodes (odd numbers) have +1 + + const int HalfBlock = 16; + + return bank switch + { + NeuropixelsV2Bank.A => row * ElectrodesPerRow + block * NeuropixelsV2.ElectrodePerBlockSingleShank + offset, + NeuropixelsV2Bank.B => (row * 7 % HalfBlock) * ElectrodesPerRow + block * NeuropixelsV2.ElectrodePerBlockSingleShank + offset, + NeuropixelsV2Bank.C => (row * 5 % HalfBlock) * ElectrodesPerRow + block * NeuropixelsV2.ElectrodePerBlockSingleShank + offset, + NeuropixelsV2Bank.D => (row * 3 % HalfBlock) * ElectrodesPerRow + block * NeuropixelsV2.ElectrodePerBlockSingleShank + offset, + _ => throw new NotImplementedException($"Invalid {nameof(NeuropixelsV2Bank)} value given.") + }; + } + internal static int GetQuadShankChannelNumber(int shank, int block, int blockIndex) => (shank, block) switch { (0, 0) => blockIndex + NeuropixelsV2.ElectrodePerBlockQuadShank * 0, diff --git a/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs index dbc91538..628f29a8 100644 --- a/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs +++ b/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Linq; using System.Text; using System.Xml.Serialization; using Bonsai; @@ -33,6 +34,10 @@ public enum NeuropixelsV2ShankReference : uint /// Tip4, /// + /// Specifies that the tip reference of a single-shank probe will be used. + /// + Tip, + /// /// Specifies that the Ground reference will be used. /// Ground @@ -78,6 +83,10 @@ public enum NeuropixelsV2ProbeType /// Specifies that there are four shanks. /// QuadShank = 0, + /// + /// Specifies that there is one shank. + /// + SingleShank } /// @@ -143,6 +152,39 @@ public NeuropixelsV2ProbeConfiguration(NeuropixelsV2eProbeGroup probeGroup, Neur [Description("Defines the type of probe, differentiated by the number of shanks present.")] public NeuropixelsV2ProbeType ProbeType { get; set; } = NeuropixelsV2ProbeType.QuadShank; + internal static Array FilterNeuropixelsV2ShankReference(NeuropixelsV2ProbeType probeType) + { + if (probeType == NeuropixelsV2ProbeType.SingleShank) + { + return Enum.GetValues(typeof(NeuropixelsV2ShankReference)) + .Cast() + .Where(r => + { + return r == NeuropixelsV2ShankReference.External + || r == NeuropixelsV2ShankReference.Tip + || r == NeuropixelsV2ShankReference.Ground; + }) + .ToArray(); + } + else if (probeType == NeuropixelsV2ProbeType.QuadShank) + { + return Enum.GetValues(typeof(NeuropixelsV2ShankReference)) + .Cast() + .Where(r => + { + return r == NeuropixelsV2ShankReference.External + || r == NeuropixelsV2ShankReference.Tip1 + || r == NeuropixelsV2ShankReference.Tip2 + || r == NeuropixelsV2ShankReference.Tip3 + || r == NeuropixelsV2ShankReference.Tip4 + || r == NeuropixelsV2ShankReference.Ground; + }) + .ToArray(); + } + + throw new InvalidEnumArgumentException("Unknown probe type given."); + } + /// /// Gets or sets the reference for all electrodes. /// @@ -154,6 +196,7 @@ public NeuropixelsV2ProbeConfiguration(NeuropixelsV2eProbeGroup probeGroup, Neur /// sets the reference to the electrode at the tip of the first shank. /// [Description("Defines what the reference for the probe will be, whether it is external, on a shank tip, or the ground reference.")] + [TypeConverter(typeof(ReferenceTypeConverter))] public NeuropixelsV2ShankReference Reference { get; set; } = NeuropixelsV2ShankReference.External; /// @@ -220,4 +263,32 @@ public string ProbeGroupString } } } + + internal class ReferenceTypeConverter : EnumConverter + { + public ReferenceTypeConverter() + : base(typeof(NeuropixelsV2ShankReference)) + { + } + + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + return true; + } + + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + if (context?.Instance == null) + return base.GetStandardValues(context); + + var probeConfiguration = (NeuropixelsV2ProbeConfiguration)context.Instance; + + return new StandardValuesCollection(NeuropixelsV2ProbeConfiguration.FilterNeuropixelsV2ShankReference(probeConfiguration.ProbeType)); + } + } } diff --git a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs index c8535e92..84b43f9f 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eProbeGroup.cs @@ -28,12 +28,14 @@ public NeuropixelsV2eProbeGroup(NeuropixelsV2ProbeType probeType) { } + internal static string SingleShankProbeName = "Neuropixels 2.0 - single shank"; internal static string QuadShankProbeName = "Neuropixels 2.0 - multishank"; internal static string GetProbeName(NeuropixelsV2ProbeType probeType) { return probeType switch { + NeuropixelsV2ProbeType.SingleShank => SingleShankProbeName, NeuropixelsV2ProbeType.QuadShank => QuadShankProbeName, _ => throw new InvalidEnumArgumentException("Unknown probe type given.") }; @@ -41,7 +43,10 @@ internal static string GetProbeName(NeuropixelsV2ProbeType probeType) internal static NeuropixelsV2ProbeType GetProbeTypeFromProbeName(string name) { - if (name == QuadShankProbeName) + if (name == SingleShankProbeName) + return NeuropixelsV2ProbeType.SingleShank; + + else if (name == QuadShankProbeName) return NeuropixelsV2ProbeType.QuadShank; else @@ -54,6 +59,7 @@ private static Probe[] DefaultProbes(NeuropixelsV2ProbeType probeType) int numberOfShanks = probeType switch { + NeuropixelsV2ProbeType.SingleShank => 1, NeuropixelsV2ProbeType.QuadShank => 4, _ => throw new InvalidEnumArgumentException("Unknown probe type given.") }; @@ -157,7 +163,10 @@ private static float ContactPositionX(int index) /// public static float[][] DefaultProbePlanarContour(NeuropixelsV2ProbeType probeType) { - if (probeType == NeuropixelsV2ProbeType.QuadShank) + if (probeType == NeuropixelsV2ProbeType.SingleShank) + return DefaultProbePlanarContourSingleShank(); + + else if (probeType == NeuropixelsV2ProbeType.QuadShank) return DefaultProbePlanarContourQuadShank(); throw new InvalidEnumArgumentException(nameof(probeType)); @@ -202,14 +211,20 @@ public static float[][] DefaultProbePlanarContourQuadShank() /// public static float[][] DefaultProbePlanarContourSingleShank() { + const float shankTipY = 0f; + const float shankBaseY = 155f; + const float shankLengthY = 9770f; + const float shankWidthX = 70f; + const float shankMidX = 35f; + 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 }; + probePlanarContour[0] = new float[2] { shankOffsetX + 0f, shankBaseY }; + probePlanarContour[1] = new float[2] { shankOffsetX + shankMidX, shankTipY }; + probePlanarContour[2] = new float[2] { shankOffsetX + shankWidthX, shankBaseY }; + probePlanarContour[3] = new float[2] { shankOffsetX + shankWidthX, shankLengthY }; + probePlanarContour[4] = new float[2] { shankOffsetX + 0f, shankLengthY }; + probePlanarContour[5] = new float[2] { shankOffsetX + 0f, shankBaseY }; return probePlanarContour; }