diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs index 7c93f8e1..f7d1a0d1 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eChannelConfigurationDialog.cs @@ -31,7 +31,7 @@ public NeuropixelsV2eChannelConfigurationDialog(NeuropixelsV2ProbeConfiguration zedGraphChannels.ZoomStepFraction = 0.5; - ProbeConfiguration = Activator.CreateInstance(probeConfiguration.GetType(), probeConfiguration) as NeuropixelsV2ProbeConfiguration; + ProbeConfiguration = probeConfiguration.Clone(); ProbeConfiguration.ProbeGroup = (NeuropixelsV2eProbeGroup)ProbeGroup; GetChannelNumberFunc = ProbeConfiguration.ChannelMap[0].GetChannelNumberFunc(); @@ -49,7 +49,16 @@ internal override ProbeGroup DefaultChannelLayout() internal override void LoadDefaultChannelLayout() { - base.LoadDefaultChannelLayout(); + try + { + ProbeConfiguration.ProbeGroup = DefaultChannelLayout() as NeuropixelsV2eProbeGroup; + ProbeGroup = ProbeConfiguration.ProbeGroup; + } + catch (InvalidOperationException ex) + { + MessageBox.Show("Unable to Load Default", ex.Message); + return; + } OnFileOpenHandler(); } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs index b80302d9..9166e5c3 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eDialog.cs @@ -9,12 +9,33 @@ namespace OpenEphys.Onix1.Design /// public partial class NeuropixelsV2eDialog : Form { - readonly IReadOnlyList ProbeConfigurations; + internal readonly Dictionary ProbeConfigurations; + + internal NeuropixelsV2ProbeConfiguration ProbeConfigurationA + { + get + { + return ProbeConfigurations.TryGetValue(NeuropixelsV2Probe.ProbeA, out var probeConfigurationDialog) + ? probeConfigurationDialog.ProbeConfiguration + : throw new NullReferenceException("Unable to find the probe configuration dialog for Probe A."); + } + } + + internal NeuropixelsV2ProbeConfiguration ProbeConfigurationB + { + get + { + return ProbeConfigurations.TryGetValue(NeuropixelsV2Probe.ProbeB, out var probeConfigurationDialog) + ? probeConfigurationDialog.ProbeConfiguration + : throw new NullReferenceException("Unable to find the probe configuration dialog for Probe B."); + } + } /// /// Public interface that is manipulated by /// . /// + [Obsolete] public IConfigureNeuropixelsV2 ConfigureNode { get; set; } /// @@ -26,75 +47,27 @@ public NeuropixelsV2eDialog(IConfigureNeuropixelsV2 configureNode) InitializeComponent(); Shown += FormShown; - if (configureNode is ConfigureNeuropixelsV2eBeta configureV2eBeta) + if (configureNode is ConfigureNeuropixelsV2eBeta) { - ConfigureNode = new ConfigureNeuropixelsV2eBeta(configureV2eBeta); Text = Text.Replace("NeuropixelsV2e ", "NeuropixelsV2eBeta "); } - else if (configureNode is ConfigureNeuropixelsV2e configureV2e) - { - ConfigureNode = new ConfigureNeuropixelsV2e(configureV2e); - } - ProbeConfigurations = new List + ProbeConfigurations = new() { - new(ConfigureNode.ProbeConfigurationA, ConfigureNode.GainCalibrationFileA, ConfigureNode.InvertPolarity) - { - TopLevel = false, - FormBorderStyle = FormBorderStyle.None, - Dock = DockStyle.Fill, - Parent = this, - Tag = NeuropixelsV2Probe.ProbeA - }, - new(ConfigureNode.ProbeConfigurationB, ConfigureNode.GainCalibrationFileB, ConfigureNode.InvertPolarity) - { - TopLevel = false, - FormBorderStyle = FormBorderStyle.None, - Dock = DockStyle.Fill, - Parent = this, - Tag = NeuropixelsV2Probe.ProbeB - } + { NeuropixelsV2Probe.ProbeA, new(configureNode.ProbeConfigurationA) }, + { NeuropixelsV2Probe.ProbeB, new(configureNode.ProbeConfigurationB) } }; foreach (var channelConfiguration in ProbeConfigurations) { - channelConfiguration.InvertPolarityChanged += InvertPolarityChanged; - - string probeName = GetProbeName((NeuropixelsV2Probe)channelConfiguration.Tag); + string probeName = channelConfiguration.Key.ToString(); tabControlProbe.TabPages.Add(probeName, probeName); - tabControlProbe.TabPages[probeName].Controls.Add(channelConfiguration); - this.AddMenuItemsFromDialogToFileOption(channelConfiguration, probeName); + channelConfiguration.Value.SetChildFormProperties(this).AddDialogToTab(tabControlProbe.TabPages[probeName]); + this.AddMenuItemsFromDialogToFileOption(channelConfiguration.Value, probeName); } } - private void InvertPolarityChanged(object sender, EventArgs e) - { - NeuropixelsV2eProbeConfigurationDialog sendingDialog = (NeuropixelsV2eProbeConfigurationDialog)sender; - foreach (var channelConfiguration in ProbeConfigurations) - { - if (channelConfiguration.Tag != sendingDialog.Tag) - { - channelConfiguration.SetInvertPolarity(sendingDialog.InvertPolarity); - } - } - } - - private string GetProbeName(NeuropixelsV2Probe probe) - { - return probe switch - { - NeuropixelsV2Probe.ProbeA => "Probe A", - NeuropixelsV2Probe.ProbeB => "Probe B", - _ => "Invalid probe was specified." - }; - } - - private int GetProbeIndex(NeuropixelsV2Probe probe) - { - return probe == NeuropixelsV2Probe.ProbeA ? 0 : 1; - } - private void FormShown(object sender, EventArgs e) { if (!TopLevel) @@ -107,26 +80,13 @@ private void FormShown(object sender, EventArgs e) foreach (var channelConfiguration in ProbeConfigurations) { - channelConfiguration.Show(); + channelConfiguration.Value.Show(); } } internal void Okay_Click(object sender, EventArgs e) { - SaveVariables(); - DialogResult = DialogResult.OK; } - - internal void SaveVariables() - { - ConfigureNode.ProbeConfigurationA = ProbeConfigurations[GetProbeIndex(NeuropixelsV2Probe.ProbeA)].ProbeConfiguration; - ConfigureNode.ProbeConfigurationB = ProbeConfigurations[GetProbeIndex(NeuropixelsV2Probe.ProbeB)].ProbeConfiguration; - - ConfigureNode.GainCalibrationFileA = ProbeConfigurations[GetProbeIndex(NeuropixelsV2Probe.ProbeA)].textBoxProbeCalibrationFile.Text; - ConfigureNode.GainCalibrationFileB = ProbeConfigurations[GetProbeIndex(NeuropixelsV2Probe.ProbeB)].textBoxProbeCalibrationFile.Text; - - ConfigureNode.InvertPolarity = ProbeConfigurations[GetProbeIndex(NeuropixelsV2Probe.ProbeA)].InvertPolarity; - } } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs index 96e73078..a9732579 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eEditor.cs @@ -22,12 +22,8 @@ public override bool EditComponent(ITypeDescriptorContext context, object compon if (editorDialog.ShowDialog() == DialogResult.OK) { - configureNeuropixelsV2e.Enable = editorDialog.ConfigureNode.Enable; - configureNeuropixelsV2e.GainCalibrationFileA = editorDialog.ConfigureNode.GainCalibrationFileA; - configureNeuropixelsV2e.GainCalibrationFileB = editorDialog.ConfigureNode.GainCalibrationFileB; - configureNeuropixelsV2e.ProbeConfigurationA = editorDialog.ConfigureNode.ProbeConfigurationA; - configureNeuropixelsV2e.ProbeConfigurationB = editorDialog.ConfigureNode.ProbeConfigurationB; - configureNeuropixelsV2e.InvertPolarity = editorDialog.ConfigureNode.InvertPolarity; + DesignHelper.CopyProperties(editorDialog.ProbeConfigurationA, configureNeuropixelsV2e.ProbeConfigurationA, DesignHelper.PropertiesToIgnore); + DesignHelper.CopyProperties(editorDialog.ProbeConfigurationB, configureNeuropixelsV2e.ProbeConfigurationB, DesignHelper.PropertiesToIgnore); return true; } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs index 34a28daf..ad2a4cfa 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageDialog.cs @@ -48,8 +48,6 @@ public NeuropixelsV2eHeadstageDialog(IConfigureNeuropixelsV2 configureNeuropixel private void Okay_Click(object sender, System.EventArgs e) { - DialogNeuropixelsV2e.SaveVariables(); - DialogResult = DialogResult.OK; } } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs index 638b0b37..687c97a3 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eHeadstageEditor.cs @@ -23,14 +23,10 @@ public override bool EditComponent(ITypeDescriptorContext context, object compon if (editorDialog.ShowDialog() == DialogResult.OK) { - configureV2eHeadstage.Bno055.Enable = ((ConfigurePolledBno055)editorDialog.DialogBno055.Device).Enable; + DesignHelper.CopyProperties((ConfigurePolledBno055)editorDialog.DialogBno055.Device, configureV2eHeadstage.Bno055, DesignHelper.PropertiesToIgnore); - configureV2eHeadstage.NeuropixelsV2e.Enable = editorDialog.DialogNeuropixelsV2e.ConfigureNode.Enable; - configureV2eHeadstage.NeuropixelsV2e.ProbeConfigurationA = editorDialog.DialogNeuropixelsV2e.ConfigureNode.ProbeConfigurationA; - configureV2eHeadstage.NeuropixelsV2e.ProbeConfigurationB = editorDialog.DialogNeuropixelsV2e.ConfigureNode.ProbeConfigurationB; - configureV2eHeadstage.NeuropixelsV2e.GainCalibrationFileA = editorDialog.DialogNeuropixelsV2e.ConfigureNode.GainCalibrationFileA; - configureV2eHeadstage.NeuropixelsV2e.GainCalibrationFileB = editorDialog.DialogNeuropixelsV2e.ConfigureNode.GainCalibrationFileB; - configureV2eHeadstage.NeuropixelsV2e.InvertPolarity = editorDialog.DialogNeuropixelsV2e.ConfigureNode.InvertPolarity; + configureV2eHeadstage.NeuropixelsV2e.ProbeConfigurationA = editorDialog.DialogNeuropixelsV2e.ProbeConfigurationA; + configureV2eHeadstage.NeuropixelsV2e.ProbeConfigurationB = editorDialog.DialogNeuropixelsV2e.ProbeConfigurationB; return true; } @@ -41,14 +37,10 @@ public override bool EditComponent(ITypeDescriptorContext context, object compon if (editorDialog.ShowDialog() == DialogResult.OK) { - configureV2eBetaHeadstage.Bno055.Enable = ((ConfigurePolledBno055)editorDialog.DialogBno055.Device).Enable; - - configureV2eBetaHeadstage.NeuropixelsV2eBeta.Enable = editorDialog.DialogNeuropixelsV2e.ConfigureNode.Enable; - configureV2eBetaHeadstage.NeuropixelsV2eBeta.ProbeConfigurationA = editorDialog.DialogNeuropixelsV2e.ConfigureNode.ProbeConfigurationA; - configureV2eBetaHeadstage.NeuropixelsV2eBeta.ProbeConfigurationB = editorDialog.DialogNeuropixelsV2e.ConfigureNode.ProbeConfigurationB; - configureV2eBetaHeadstage.NeuropixelsV2eBeta.GainCalibrationFileA = editorDialog.DialogNeuropixelsV2e.ConfigureNode.GainCalibrationFileA; - configureV2eBetaHeadstage.NeuropixelsV2eBeta.GainCalibrationFileB = editorDialog.DialogNeuropixelsV2e.ConfigureNode.GainCalibrationFileB; - configureV2eBetaHeadstage.NeuropixelsV2eBeta.InvertPolarity = editorDialog.DialogNeuropixelsV2e.ConfigureNode.InvertPolarity; + DesignHelper.CopyProperties((ConfigurePolledBno055)editorDialog.DialogBno055.Device, configureV2eBetaHeadstage.Bno055, DesignHelper.PropertiesToIgnore); + + configureV2eBetaHeadstage.NeuropixelsV2eBeta.ProbeConfigurationA = editorDialog.DialogNeuropixelsV2e.ProbeConfigurationA; + configureV2eBetaHeadstage.NeuropixelsV2eBeta.ProbeConfigurationB = editorDialog.DialogNeuropixelsV2e.ProbeConfigurationB; return true; } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs index 6a8112de..70f0abf2 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationDialog.cs @@ -25,7 +25,12 @@ public NeuropixelsV2ProbeConfiguration ProbeConfiguration } /// - public bool InvertPolarity { get; set; } + [Obsolete] + public bool InvertPolarity + { + get => ProbeConfiguration.InvertPolarity; + set => ProbeConfiguration.InvertPolarity = value; + } INeuropixelsV2ProbeInfo ProbeData { get; set; } @@ -33,16 +38,13 @@ public NeuropixelsV2ProbeConfiguration ProbeConfiguration /// Initializes a new instance of . /// /// 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) + public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2ProbeConfiguration configuration) { InitializeComponent(); Shown += FormShown; - textBoxProbeCalibrationFile.Text = calibrationFile; - - InvertPolarity = invertPolarity; + textBoxProbeCalibrationFile.Text = configuration.GainCalibrationFileName; + textBoxProbeCalibrationFile.TextChanged += (sender, e) => ProbeConfiguration.GainCalibrationFileName = ((TextBox)sender).Text; ChannelConfiguration = new(configuration); ChannelConfiguration.SetChildFormProperties(this).AddDialogToPanel(panelProbe); @@ -61,7 +63,7 @@ public NeuropixelsV2eProbeConfigurationDialog(NeuropixelsV2ProbeConfiguration co comboBoxChannelPresets.DataSource = ProbeData.GetComboBoxChannelPresets(); comboBoxChannelPresets.SelectedIndexChanged += SelectedChannelPresetChanged; - checkBoxInvertPolarity.Checked = InvertPolarity; + checkBoxInvertPolarity.Checked = ProbeConfiguration.InvertPolarity; checkBoxInvertPolarity.CheckedChanged += InvertPolarityIndexChanged; CheckForExistingChannelPreset(); @@ -83,7 +85,7 @@ static INeuropixelsV2ProbeInfo ProbeDataFactory(NeuropixelsV2ProbeConfiguration private void InvertPolarityIndexChanged(object sender, EventArgs e) { - InvertPolarity = ((CheckBox)sender).Checked; + ProbeConfiguration.InvertPolarity = ((CheckBox)sender).Checked; OnInvertPolarityChangedHandler(); } diff --git a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs index e613d2c2..3198600d 100644 --- a/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs +++ b/OpenEphys.Onix1.Design/NeuropixelsV2eProbeConfigurationEditor.cs @@ -31,11 +31,9 @@ public override object EditValue(ITypeDescriptorContext context, IServiceProvide { 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); + using var editorDialog = new NeuropixelsV2eProbeConfigurationDialog(configuration); if (isBeta) { @@ -44,17 +42,6 @@ public override object EditValue(ITypeDescriptorContext context, IServiceProvide if (editorDialog.ShowDialog() == DialogResult.OK) { - if (configuration.Probe == NeuropixelsV2Probe.ProbeA) - { - instance.GainCalibrationFileA = editorDialog.textBoxProbeCalibrationFile.Text; - } - else - { - instance.GainCalibrationFileB = editorDialog.textBoxProbeCalibrationFile.Text; - } - - instance.InvertPolarity = editorDialog.InvertPolarity; - return editorDialog.ProbeConfiguration; } } diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs index 1ab21793..7de863f1 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2e.cs @@ -30,13 +30,10 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) : base(typeof(NeuropixelsV2e)) { Enable = configureNode.Enable; - ProbeConfigurationA = configureNode.ProbeConfigurationA; - ProbeConfigurationB = configureNode.ProbeConfigurationB; - GainCalibrationFileA = configureNode.GainCalibrationFileA; - GainCalibrationFileB = configureNode.GainCalibrationFileB; + ProbeConfigurationA = configureNode.ProbeConfigurationA.Clone(); + ProbeConfigurationB = configureNode.ProbeConfigurationB.Clone(); DeviceName = configureNode.DeviceName; DeviceAddress = configureNode.DeviceAddress; - InvertPolarity = configureNode.InvertPolarity; } /// @@ -48,38 +45,98 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) [Description("Specifies whether the NeuropixelsV2 device is enabled.")] public bool Enable { get; set; } = true; - /// - [Category(ConfigurationCategory)] - [Description("Invert the polarity of the electrode voltages acquired by the probe.")] - public bool InvertPolarity { get; set; } = true; + /// + /// Gets or sets a value determining if the polarity of the electrode voltages acquired by the probe + /// should be inverted. + /// + /// /// + /// [Obsolete]. Cannot tag this property with the Obsolete attribute due to https://github.com/dotnet/runtime/issues/100453 + /// + [Browsable(false)] + [Externalizable(false)] + public bool InvertPolarity + { + get => ProbeConfigurationA.InvertPolarity; + set + { + ProbeConfigurationA.InvertPolarity = value; + ProbeConfigurationB.InvertPolarity = value; + } + } + + /// + /// Prevent the InvertPolarity property from being serialized. + /// + /// False + [Obsolete] + public bool ShouldSerializeInvertPolarity() + { + return false; + } /// [Category(ConfigurationCategory)] [Description("Probe A configuration.")] [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] [XmlElement(nameof(ProbeConfigurationA), typeof(NeuropixelsV2QuadShankProbeConfiguration))] + [TypeConverter(typeof(GenericPropertyConverter))] public NeuropixelsV2ProbeConfiguration ProbeConfigurationA { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe.ProbeA, NeuropixelsV2QuadShankReference.External); - /// - [Category(ConfigurationCategory)] - [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] - [Description("Path to the gain calibration file for probe A.")] - [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] - public string GainCalibrationFileA { get; set; } + /// + /// Gets or sets the path to the gain calibration file for this probe. + /// + /// + /// [Obsolete]. Cannot tag this property with the Obsolete attribute due to https://github.com/dotnet/runtime/issues/100453 + /// + [Browsable(false)] + [Externalizable(false)] + public string GainCalibrationFileA + { + get => ProbeConfigurationA.GainCalibrationFileName; + set => ProbeConfigurationA.GainCalibrationFileName = value; + } + + /// + /// Prevent the GainCalibrationFile property from being serialized. + /// + /// False + [Obsolete] + public bool ShouldSerializeGainCalibrationFileA() + { + return false; + } /// [Category(ConfigurationCategory)] [Description("Probe B configuration.")] [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] [XmlElement(nameof(ProbeConfigurationB), typeof(NeuropixelsV2QuadShankProbeConfiguration))] + [TypeConverter(typeof(GenericPropertyConverter))] public NeuropixelsV2ProbeConfiguration ProbeConfigurationB { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe.ProbeB, NeuropixelsV2QuadShankReference.External); - /// - [Category(ConfigurationCategory)] - [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] - [Description("Path to the gain calibration file for probe B.")] - [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] - public string GainCalibrationFileB { get; set; } + /// + /// Gets or sets the path to the gain calibration file for this probe. + /// + /// + /// [Obsolete]. Cannot tag this property with the Obsolete attribute due to https://github.com/dotnet/runtime/issues/100453 + /// + [Browsable(false)] + [Externalizable(false)] + public string GainCalibrationFileB + { + get => ProbeConfigurationB.GainCalibrationFileName; + set => ProbeConfigurationB.GainCalibrationFileName = value; + } + + /// + /// Prevent the GainCalibrationFile property from being serialized. + /// + /// False + [Obsolete] + public bool ShouldSerializeGainCalibrationFileB() + { + return false; + } /// /// Configures a NeuropixelsV2e device. @@ -96,7 +153,8 @@ public ConfigureNeuropixelsV2e(ConfigureNeuropixelsV2e configureNode) public override IObservable Process(IObservable source) { var enable = Enable; - var invertPolarity = InvertPolarity; + var probeConfigurationA = ProbeConfigurationA; + var probeConfigurationB = ProbeConfigurationB; var deviceName = DeviceName; var deviceAddress = DeviceAddress; return source.ConfigureDevice(context => @@ -128,11 +186,11 @@ public override IObservable Process(IObservable source // configure probe A streaming if (probeAMetadata.ProbeSerialNumber != null) { - var gainCorrection = NeuropixelsV2Helper.TryParseGainCalibrationFile(GainCalibrationFileA); + var gainCorrection = NeuropixelsV2Helper.TryParseGainCalibrationFile(probeConfigurationA.GainCalibrationFileName); if (!gainCorrection.HasValue) { - throw new ArgumentException($"{NeuropixelsV2Probe.ProbeA}'s calibration file \"{GainCalibrationFileA}\" is invalid."); + throw new ArgumentException($"{NeuropixelsV2Probe.ProbeA}'s calibration file \"{probeConfigurationA.GainCalibrationFileName}\" is invalid."); } if (gainCorrection.Value.SerialNumber != probeAMetadata.ProbeSerialNumber) @@ -144,18 +202,18 @@ public override IObservable Process(IObservable source gainCorrectionA = gainCorrection.Value.GainCorrectionFactor; SelectProbe(serializer, NeuropixelsV2e.ProbeASelected); - probeControl.WriteConfiguration(ProbeConfigurationA); + probeControl.WriteConfiguration(probeConfigurationA); ConfigureProbeStreaming(probeControl); } // configure probe B streaming if (probeBMetadata.ProbeSerialNumber != null) { - var gainCorrection = NeuropixelsV2Helper.TryParseGainCalibrationFile(GainCalibrationFileB); + var gainCorrection = NeuropixelsV2Helper.TryParseGainCalibrationFile(probeConfigurationB.GainCalibrationFileName); if (!gainCorrection.HasValue) { - throw new ArgumentException($"{NeuropixelsV2Probe.ProbeB}'s calibration file \"{GainCalibrationFileB}\" is invalid."); + throw new ArgumentException($"{NeuropixelsV2Probe.ProbeB}'s calibration file \"{probeConfigurationB.GainCalibrationFileName}\" is invalid."); } if (gainCorrection.Value.SerialNumber != probeBMetadata.ProbeSerialNumber) @@ -167,14 +225,14 @@ public override IObservable Process(IObservable source gainCorrectionB = gainCorrection.Value.GainCorrectionFactor; SelectProbe(serializer, NeuropixelsV2e.ProbeBSelected); - probeControl.WriteConfiguration(ProbeConfigurationB); + probeControl.WriteConfiguration(probeConfigurationB); ConfigureProbeStreaming(probeControl); } // disconnect i2c bus from both probes to prevent digital interference during acquisition SelectProbe(serializer, NeuropixelsV2e.NoProbeSelected); - var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, invertPolarity, probeAMetadata, probeBMetadata, ProbeConfigurationA, ProbeConfigurationB); + var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, probeAMetadata, probeBMetadata, probeConfigurationA, probeConfigurationB); var shutdown = Disposable.Create(() => { serializer.WriteByte((uint)DS90UB933SerializerI2CRegister.Gpio10, NeuropixelsV2e.DefaultGPO10Config); diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs index 8687ffc1..e382e969 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2eBeta.cs @@ -35,13 +35,10 @@ public ConfigureNeuropixelsV2eBeta(ConfigureNeuropixelsV2eBeta configureNode) { Enable = configureNode.Enable; EnableLed = configureNode.EnableLed; - ProbeConfigurationA = configureNode.ProbeConfigurationA; - ProbeConfigurationB = configureNode.ProbeConfigurationB; - GainCalibrationFileA = configureNode.GainCalibrationFileA; - GainCalibrationFileB = configureNode.GainCalibrationFileB; + ProbeConfigurationA = configureNode.ProbeConfigurationA.Clone(); + ProbeConfigurationB = configureNode.ProbeConfigurationB.Clone(); DeviceName = configureNode.DeviceName; DeviceAddress = configureNode.DeviceAddress; - InvertPolarity = configureNode.InvertPolarity; } /// @@ -63,38 +60,98 @@ public ConfigureNeuropixelsV2eBeta(ConfigureNeuropixelsV2eBeta configureNode) [Description("Specifies whether the headstage LED will turn on during acquisition.")] public bool EnableLed { get; set; } = true; - /// - [Category(ConfigurationCategory)] - [Description("Invert the polarity of the electrode voltages acquired by the probe.")] - public bool InvertPolarity { get; set; } = true; + /// + /// Gets or sets a value determining if the polarity of the electrode voltages acquired by the probe + /// should be inverted. + /// + /// /// + /// [Obsolete]. Cannot tag this property with the Obsolete attribute due to https://github.com/dotnet/runtime/issues/100453 + /// + [Browsable(false)] + [Externalizable(false)] + public bool InvertPolarity + { + get => ProbeConfigurationA.InvertPolarity; + set + { + ProbeConfigurationA.InvertPolarity = value; + ProbeConfigurationB.InvertPolarity = value; + } + } + + /// + /// Prevent the InvertPolarity property from being serialized. + /// + /// False + [Obsolete] + public bool ShouldSerializeInvertPolarity() + { + return false; + } /// [Category(ConfigurationCategory)] [Description("Probe A configuration.")] [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] [XmlElement(nameof(ProbeConfigurationA), typeof(NeuropixelsV2QuadShankProbeConfiguration))] + [TypeConverter(typeof(GenericPropertyConverter))] public NeuropixelsV2ProbeConfiguration ProbeConfigurationA { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe.ProbeA, NeuropixelsV2QuadShankReference.External); - /// - [Category(ConfigurationCategory)] - [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] - [Description("Path to the gain calibration file for probe A.")] - [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] - public string GainCalibrationFileA { get; set; } + /// + /// Gets or sets the path to the gain calibration file for this probe. + /// + /// + /// [Obsolete]. Cannot tag this property with the Obsolete attribute due to https://github.com/dotnet/runtime/issues/100453 + /// + [Browsable(false)] + [Externalizable(false)] + public string GainCalibrationFileA + { + get => ProbeConfigurationA.GainCalibrationFileName; + set => ProbeConfigurationA.GainCalibrationFileName = value; + } + + /// + /// Prevent the GainCalibrationFile property from being serialized. + /// + /// False + [Obsolete] + public bool ShouldSerializeGainCalibrationFileA() + { + return false; + } /// [Category(ConfigurationCategory)] [Description("Probe B configuration.")] [Editor("OpenEphys.Onix1.Design.NeuropixelsV2eProbeConfigurationEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] [XmlElement(nameof(ProbeConfigurationB), typeof(NeuropixelsV2QuadShankProbeConfiguration))] + [TypeConverter(typeof(GenericPropertyConverter))] public NeuropixelsV2ProbeConfiguration ProbeConfigurationB { get; set; } = new NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe.ProbeB, NeuropixelsV2QuadShankReference.External); - /// - [Category(ConfigurationCategory)] - [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] - [Description("Path to the gain calibration file for probe B.")] - [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] - public string GainCalibrationFileB { get; set; } + /// + /// Gets or sets the path to the gain calibration file for this probe. + /// + /// + /// [Obsolete]. Cannot tag this property with the Obsolete attribute due to https://github.com/dotnet/runtime/issues/100453 + /// + [Browsable(false)] + [Externalizable(false)] + public string GainCalibrationFileB + { + get => ProbeConfigurationB.GainCalibrationFileName; + set => ProbeConfigurationB.GainCalibrationFileName = value; + } + + /// + /// Prevent the GainCalibrationFile property from being serialized. + /// + /// False + [Obsolete] + public bool ShouldSerializeGainCalibrationFileB() + { + return false; + } /// /// Configures a NeuropixelsV2eBeta device. @@ -108,7 +165,8 @@ public ConfigureNeuropixelsV2eBeta(ConfigureNeuropixelsV2eBeta configureNode) public override IObservable Process(IObservable source) { var enable = Enable; - var invertPolarity = InvertPolarity; + var probeConfigurationA = ProbeConfigurationA; + var probeConfigurationB = ProbeConfigurationB; var deviceName = DeviceName; var deviceAddress = DeviceAddress; return source.ConfigureDevice(context => @@ -155,11 +213,11 @@ public override IObservable Process(IObservable source $" for {NeuropixelsV2Probe.ProbeA}."); } - var gainCorrection = NeuropixelsV2Helper.TryParseGainCalibrationFile(GainCalibrationFileA); + var gainCorrection = NeuropixelsV2Helper.TryParseGainCalibrationFile(probeConfigurationA.GainCalibrationFileName); if (!gainCorrection.HasValue) { - throw new ArgumentException($"{NeuropixelsV2Probe.ProbeA}'s calibration file \"{GainCalibrationFileA}\" is invalid."); + throw new ArgumentException($"{NeuropixelsV2Probe.ProbeA}'s calibration file \"{probeConfigurationA.GainCalibrationFileName}\" is invalid."); } if (gainCorrection.Value.SerialNumber != probeAMetadata.ProbeSerialNumber) @@ -171,7 +229,7 @@ public override IObservable Process(IObservable source gainCorrectionA = gainCorrection.Value.GainCorrectionFactor; SelectProbe(serializer, ref gpo32Config, NeuropixelsV2eBeta.SelectProbeA); - probeControl.WriteConfiguration(ProbeConfigurationA); + probeControl.WriteConfiguration(probeConfigurationA); ConfigureProbeStreaming(probeControl); } @@ -184,11 +242,11 @@ public override IObservable Process(IObservable source $" for {NeuropixelsV2Probe.ProbeB}."); } - var gainCorrection = NeuropixelsV2Helper.TryParseGainCalibrationFile(GainCalibrationFileB); + var gainCorrection = NeuropixelsV2Helper.TryParseGainCalibrationFile(probeConfigurationB.GainCalibrationFileName); if (!gainCorrection.HasValue) { - throw new ArgumentException($"{NeuropixelsV2Probe.ProbeB}'s calibration file \"{GainCalibrationFileB}\" is invalid."); + throw new ArgumentException($"{NeuropixelsV2Probe.ProbeB}'s calibration file \"{probeConfigurationB.GainCalibrationFileName}\" is invalid."); } if (gainCorrection.Value.SerialNumber != probeBMetadata.ProbeSerialNumber) @@ -200,7 +258,7 @@ public override IObservable Process(IObservable source gainCorrectionB = gainCorrection.Value.GainCorrectionFactor; SelectProbe(serializer, ref gpo32Config, NeuropixelsV2eBeta.SelectProbeB); - probeControl.WriteConfiguration(ProbeConfigurationB); + probeControl.WriteConfiguration(probeConfigurationB); ConfigureProbeStreaming(probeControl); } @@ -214,7 +272,7 @@ public override IObservable Process(IObservable source // Still its good to get them roughly (i.e. within 10 PCLKs) started at the same time. SyncProbes(serializer, gpo10Config); - var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, invertPolarity, probeAMetadata, probeBMetadata, ProbeConfigurationA, ProbeConfigurationB); + var deviceInfo = new NeuropixelsV2eDeviceInfo(context, DeviceType, deviceAddress, gainCorrectionA, gainCorrectionB, probeAMetadata, probeBMetadata, probeConfigurationA, probeConfigurationB); var shutdown = Disposable.Create(() => { serializer.WriteByte((uint)DS90UB933SerializerI2CRegister.Gpio10, NeuropixelsV2eBeta.DefaultGPO10Config); diff --git a/OpenEphys.Onix1/IConfigureNeuropixelsV2.cs b/OpenEphys.Onix1/IConfigureNeuropixelsV2.cs index 78e37091..1ef0b0f7 100644 --- a/OpenEphys.Onix1/IConfigureNeuropixelsV2.cs +++ b/OpenEphys.Onix1/IConfigureNeuropixelsV2.cs @@ -15,57 +15,9 @@ public interface IConfigureNeuropixelsV2 /// public NeuropixelsV2ProbeConfiguration ProbeConfigurationA { get; set; } - /// - /// Gets or sets the path to the gain calibration file for Probe A. - /// - /// - /// - /// Each probe is linked to a gain calibration file that contains gain adjustments determined by IMEC during - /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared - /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. - /// - /// - /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the - /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration - /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. - /// - /// - public string GainCalibrationFileA { get; set; } - /// /// Gets or sets the electrode configuration for Probe B. /// public NeuropixelsV2ProbeConfiguration ProbeConfigurationB { get; set; } - - /// - /// Gets or sets the path to the gain calibration file for Probe B. - /// - /// - /// - /// Each probe is linked to a gain calibration file that contains gain adjustments determined by IMEC during - /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared - /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. - /// - /// - /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the - /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration - /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. - /// - /// - public string GainCalibrationFileB { get; set; } - - /// - /// Gets or sets a value determining if the polarity of the electrode voltages acquired by the probe - /// should be inverted. - /// - /// - /// - /// Neuropixels contain inverting amplifiers. This means that neural data that is captured by the probe - /// will be inverted compared to the physical signal that occurs at the electrode: e.g., extracellular - /// action potentials will tend to have positive deflections instead of negative. Setting this - /// property to true will apply a gain of -1 to undo this effect. - /// - /// - public bool InvertPolarity { get; set; } } } diff --git a/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs index aedae6e8..eddbba17 100644 --- a/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs +++ b/OpenEphys.Onix1/NeuropixelsV2ProbeConfiguration.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.ComponentModel; +using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; using Bonsai; @@ -51,12 +52,49 @@ public abstract class NeuropixelsV2ProbeConfiguration [Browsable(false)] public NeuropixelsV2Probe Probe { get; set; } + /// + /// Gets or sets a value determining if the polarity of the electrode voltages acquired by the probe + /// should be inverted. + /// + /// + /// + /// Neuropixels contain inverting amplifiers. This means that neural data that is captured by the probe + /// will be inverted compared to the physical signal that occurs at the electrode: e.g., extracellular + /// action potentials will tend to have positive deflections instead of negative. Setting this + /// property to true will apply a gain of -1 to undo this effect. + /// + /// + [Category(DeviceFactory.ConfigurationCategory)] + [Description("Invert the polarity of the electrode voltages acquired by the probe.")] + public bool InvertPolarity { get; set; } = true; + + /// + /// Gets or sets the path to the gain calibration file. + /// + /// + /// + /// Each probe is linked to a gain calibration file that contains gain adjustments determined by IMEC during + /// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared + /// across probes. Therefore, using the correct gain calibration file is mandatory to create standardized recordings. + /// + /// + /// Calibration files are probe-specific and not interchangeable across probes. Calibration files must contain the + /// serial number of the corresponding probe on their first line of text. If you have lost track of a calibration + /// file for your probe, email IMEC at neuropixels.info@imec.be with the probe serial number to retrieve a new copy. + /// + /// + [Category(DeviceFactory.ConfigurationCategory)] + [FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")] + [Description("Path to the gain calibration file for probe A.")] + [Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] + public string GainCalibrationFileName { get; set; } + /// /// Gets or sets the reference for all electrodes. /// [XmlIgnore] [Description("Defines the reference for the probe.")] - public Enum Reference { get; set; } + public abstract Enum Reference { get; set; } /// /// Gets or sets the serialized reference value. @@ -84,28 +122,108 @@ public abstract class NeuropixelsV2ProbeConfiguration /// List of selected electrodes that are being added to the public abstract void SelectElectrodes(NeuropixelsV2Electrode[] electrodes); + /// + /// Protected task that loads the ProbeInterface file asynchronously. + /// + protected Task probeGroupTask = null; + + /// + /// Protected class. + /// + protected NeuropixelsV2eProbeGroup probeGroup = null; + /// /// Gets the channel configuration for this probe. /// [XmlIgnore] - [Category("Configuration")] + [Category(DeviceFactory.ConfigurationCategory)] [Description("Defines the shape of the probe, and which contacts are currently selected for streaming.")] - public NeuropixelsV2eProbeGroup ProbeGroup { get; set; } + [Browsable(false)] + [Externalizable(false)] + public abstract 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. /// + /// + /// [Obsolete]. Cannot tag this property with the Obsolete attribute due to https://github.com/dotnet/runtime/issues/100453 + /// [Browsable(false)] [Externalizable(false)] [XmlElement(nameof(ProbeGroup))] public abstract string ProbeGroupString { get; set; } + /// + /// Prevent the ProbeGroup property from being serialized. + /// + /// False + [Obsolete] + public bool ShouldSerializeProbeGroupString() + { + return false; + } + + /// + /// Protected ProbeInterface file name. + /// + protected string probeInterfaceFileName; + + /// + /// Gets or sets the file path where the ProbeInterface configuration will be saved. + /// + /// + /// If left empty, the ProbeInterface configuration will not be saved. + /// + [XmlIgnore] + [Category(DeviceFactory.ConfigurationCategory)] + [Description("File path to where the ProbeInterface file will be saved for this probe. If the file exists, it will be overwritten.")] + [FileNameFilter(ProbeInterfaceHelper.ProbeInterfaceFileNameFilter)] + [Editor("Bonsai.Design.SaveFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)] + public string ProbeInterfaceFileName + { + get => probeInterfaceFileName; + set => probeInterfaceFileName = value; + } + + /// + /// Gets or sets the ProbeInterface file name, loading the given file asynchronously when set. + /// + [XmlIgnore] + [Browsable(false)] + [Externalizable(false)] + public abstract string ProbeInterfaceLoadFileName { get; set; } + + /// + /// Gets or sets a string defining the path to an external ProbeInterface JSON file. + /// 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(ProbeInterfaceFileName))] + public string ProbeInterfaceFileNameSerialize + { + get + { + if (string.IsNullOrEmpty(ProbeInterfaceFileName)) + return ""; + + if (probeGroup != null) + ProbeInterfaceHelper.SaveExternalProbeInterfaceFile(ProbeGroup, ProbeInterfaceFileName); + + return ProbeInterfaceFileName; + } + set => ProbeInterfaceLoadFileName = value; + } + internal abstract BitArray[] CreateShankBits(Enum reference); internal abstract int GetReferenceBit(Enum reference); internal abstract bool IsGroundReference(); + + internal abstract NeuropixelsV2ProbeConfiguration Clone(); } } diff --git a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs index 44eec4bf..190f6573 100644 --- a/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs +++ b/OpenEphys.Onix1/NeuropixelsV2QuadShankProbeConfiguration.cs @@ -2,6 +2,7 @@ using System.Collections; using System.ComponentModel; using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; using Bonsai; using Newtonsoft.Json; @@ -58,6 +59,8 @@ public NeuropixelsV2QuadShankProbeConfiguration() /// /// Initializes a new instance of the class. /// + /// The for this probe. + /// The reference value. public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2Probe probe, NeuropixelsV2QuadShankReference reference) { Probe = probe; @@ -74,6 +77,9 @@ public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2QuadShankProbeConfi Reference = probeConfiguration.Reference; ProbeGroup = probeConfiguration.ProbeGroup.Clone(); Probe = probeConfiguration.Probe; + InvertPolarity = probeConfiguration.InvertPolarity; + GainCalibrationFileName = probeConfiguration.GainCalibrationFileName; + probeInterfaceFileName = probeConfiguration.probeInterfaceFileName; } /// @@ -81,14 +87,31 @@ public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2QuadShankProbeConfi /// channel configuration. /// /// The existing instance to use. - /// The reference value. /// The for this probe. + /// The reference value. + /// Boolean defining if the signal polarity should be inverted. + /// String defining the path to the gain calibration file. + /// String defining the path to the ProbeInterface file. [JsonConstructor] - public NeuropixelsV2QuadShankProbeConfiguration(NeuropixelsV2eQuadShankProbeGroup probeGroup, NeuropixelsV2Probe probe, NeuropixelsV2QuadShankReference reference) + public NeuropixelsV2QuadShankProbeConfiguration( + NeuropixelsV2eQuadShankProbeGroup probeGroup, + NeuropixelsV2Probe probe, + NeuropixelsV2QuadShankReference reference, + bool invertPolarity, + string gainCalibrationFileName, + string probeInterfaceFileName) { ProbeGroup = probeGroup.Clone(); Reference = reference; Probe = probe; + InvertPolarity = invertPolarity; + GainCalibrationFileName = gainCalibrationFileName; + ProbeInterfaceFileName = probeInterfaceFileName; + } + + internal override NeuropixelsV2ProbeConfiguration Clone() + { + return new NeuropixelsV2QuadShankProbeConfiguration(this); } /// @@ -103,6 +126,90 @@ public override NeuropixelsV2Electrode[] ChannelMap get => NeuropixelsV2eQuadShankProbeGroup.ToChannelMap((NeuropixelsV2eQuadShankProbeGroup)ProbeGroup); } + NeuropixelsV2QuadShankReference reference; + + /// + /// + /// The available references for a quad-shank probe are . + /// + [XmlIgnore] + [Description("Defines the reference for the probe.")] + [TypeConverter(typeof(NeuropixelsV2QuadShankReferenceConverter))] + public override Enum Reference + { + get => reference; + set => reference = (NeuropixelsV2QuadShankReference)value; + } + + /// + [XmlElement(nameof(Reference))] + [Browsable(false)] + [Externalizable(false)] + public override string ReferenceSerialized + { + get => Reference.ToString(); + set + { + if (string.IsNullOrEmpty(value)) + { + Reference = NeuropixelsV2QuadShankReference.External; + return; + } + + Reference = Enum.TryParse(value, out var result) + ? result + : NeuropixelsV2QuadShankReference.External; + } + } + + /// + [XmlIgnore] + [Browsable(false)] + [Externalizable(false)] + public override string ProbeInterfaceLoadFileName + { + get => probeInterfaceFileName; + set + { + probeInterfaceFileName = value; + probeGroupTask = Task.Run(() => + { + if (string.IsNullOrEmpty(probeInterfaceFileName)) + return new NeuropixelsV2eQuadShankProbeGroup(); + + return ProbeInterfaceHelper.LoadExternalProbeInterfaceFile(probeInterfaceFileName, typeof(NeuropixelsV2eQuadShankProbeGroup)) as NeuropixelsV2eProbeGroup; + }); + } + } + + /// + [XmlIgnore] + [Category(DeviceFactory.ConfigurationCategory)] + [Description("Defines the shape of the probe, and which contacts are currently selected for streaming.")] + [Browsable(false)] + [Externalizable(false)] + public override NeuropixelsV2eProbeGroup ProbeGroup + { + get + { + if (probeGroup == null) + { + try + { + probeGroup = probeGroupTask?.Result ?? new NeuropixelsV2eQuadShankProbeGroup(); + } + catch (AggregateException ae) + { + probeGroup = new NeuropixelsV2eQuadShankProbeGroup(); + throw new InvalidOperationException($"There was an error loading the ProbeInterface file, loading the default configuration instead.\n\nError: {ae.InnerException.Message}", ae.InnerException); + } + } + + return probeGroup; + } + set => probeGroup = value; + } + /// [Browsable(false)] [Externalizable(false)] @@ -219,26 +326,17 @@ internal override int GetReferenceBit(Enum reference) } internal override bool IsGroundReference() => (NeuropixelsV2QuadShankReference)Reference == NeuropixelsV2QuadShankReference.Ground; + } - /// - [XmlElement(nameof(Reference))] - [Browsable(false)] - [Externalizable(false)] - public override string ReferenceSerialized - { - get => Reference.ToString(); - set - { - if (string.IsNullOrEmpty(value)) - { - Reference = NeuropixelsV2QuadShankReference.External; - return; - } - - Reference = Enum.TryParse(value, out var result) - ? result - : NeuropixelsV2QuadShankReference.External; - } - } + /// + /// Provides a type converter for values. + /// + public class NeuropixelsV2QuadShankReferenceConverter : EnumConverter + { + /// + /// Initializes a new instance of the class with + /// the type. + /// + public NeuropixelsV2QuadShankReferenceConverter() : base(typeof(NeuropixelsV2QuadShankReference)) { } } } diff --git a/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs b/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs index d2299291..35b1313e 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eBetaData.cs @@ -84,8 +84,8 @@ public unsafe override IObservable Generate() var probeData = device.Context .GetDeviceFrames(passthrough.Address) .Where(frame => NeuropixelsV2eBetaDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); - var invertPolarity = info.InvertPolarity; var orderByDepth = OrderByDepth; + var invertPolarity = probeConfiguration.InvertPolarity; return Observable.Create(observer => { diff --git a/OpenEphys.Onix1/NeuropixelsV2eData.cs b/OpenEphys.Onix1/NeuropixelsV2eData.cs index b794d96e..179f6e05 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eData.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eData.cs @@ -88,8 +88,8 @@ public unsafe override IObservable Generate() var probeData = device.Context .GetDeviceFrames(passthrough.Address) .Where(frame => NeuropixelsV2eDataFrame.GetProbeIndex(frame) == (int)ProbeIndex); - var invertPolarity = info.InvertPolarity; var orderByDepth = OrderByDepth; + var invertPolarity = probeConfiguration.InvertPolarity; return Observable.Create(observer => { diff --git a/OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs b/OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs index 1c4a3677..52aceb64 100644 --- a/OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs +++ b/OpenEphys.Onix1/NeuropixelsV2eDeviceInfo.cs @@ -4,12 +4,11 @@ namespace OpenEphys.Onix1 { class NeuropixelsV2eDeviceInfo : DeviceInfo { - public NeuropixelsV2eDeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, double? gainCorrectionA, double? gainCorrectionB, bool invertPolarity, INeuropixelsV2eMetadata probeMetadataA, INeuropixelsV2eMetadata probeMetadataB, NeuropixelsV2ProbeConfiguration probeConfigurationA, NeuropixelsV2ProbeConfiguration probeConfigurationB) + public NeuropixelsV2eDeviceInfo(ContextTask context, Type deviceType, uint deviceAddress, double? gainCorrectionA, double? gainCorrectionB, INeuropixelsV2eMetadata probeMetadataA, INeuropixelsV2eMetadata probeMetadataB, NeuropixelsV2ProbeConfiguration probeConfigurationA, NeuropixelsV2ProbeConfiguration probeConfigurationB) : base(context, deviceType, deviceAddress) { GainCorrectionA = gainCorrectionA; GainCorrectionB = gainCorrectionB; - InvertPolarity = invertPolarity; ProbeMetadataA = probeMetadataA; ProbeMetadataB = probeMetadataB; ProbeConfigurationA = probeConfigurationA; @@ -20,8 +19,6 @@ public NeuropixelsV2eDeviceInfo(ContextTask context, Type deviceType, uint devic public double? GainCorrectionB { get; } - public bool InvertPolarity { get; } - public INeuropixelsV2eMetadata ProbeMetadataA { get; } public INeuropixelsV2eMetadata ProbeMetadataB { get; } diff --git a/OpenEphys.Onix1/ProbeInterfaceHelper.cs b/OpenEphys.Onix1/ProbeInterfaceHelper.cs index df968485..c77be6a5 100644 --- a/OpenEphys.Onix1/ProbeInterfaceHelper.cs +++ b/OpenEphys.Onix1/ProbeInterfaceHelper.cs @@ -15,7 +15,7 @@ public static ProbeGroup LoadExternalProbeInterfaceFile(string probeInterfaceFil 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 (!typeof(ProbeGroup).IsAssignableFrom(type)) throw new InvalidDataException($"Invalid type given: {type.Name} is not a valid {nameof(ProbeGroup)} type."); if (!File.Exists(probeInterfaceFileName)) {