From 7cfa1b41c4a93ed228f2e23907ffacf4efa853c8 Mon Sep 17 00:00:00 2001 From: bparks13 Date: Thu, 13 Nov 2025 10:26:56 -0500 Subject: [PATCH] Refactor Rhs2116 to save ProbeInterface files externally --- OpenEphys.Onix1/ConfigureRhs2116Trigger.cs | 108 ++++++++++++++++++++- OpenEphys.Onix1/Rhs2116ProbeGroup.cs | 7 +- 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureRhs2116Trigger.cs b/OpenEphys.Onix1/ConfigureRhs2116Trigger.cs index 081c316a..432437f5 100644 --- a/OpenEphys.Onix1/ConfigureRhs2116Trigger.cs +++ b/OpenEphys.Onix1/ConfigureRhs2116Trigger.cs @@ -3,6 +3,7 @@ using System.Reactive.Disposables; using System.Reactive.Subjects; using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; using Bonsai; using Newtonsoft.Json; @@ -44,7 +45,7 @@ public ConfigureRhs2116Trigger(ConfigureRhs2116Trigger rhs2116Trigger) DeviceAddress = rhs2116Trigger.DeviceAddress; DeviceName = rhs2116Trigger.DeviceName; TriggerSource = rhs2116Trigger.TriggerSource; - ProbeGroup = rhs2116Trigger.ProbeGroup; + ProbeGroup = rhs2116Trigger.ProbeGroup.Clone(); Armed = rhs2116Trigger.Armed; StimulusSequence = new(rhs2116Trigger.StimulusSequence); } @@ -76,13 +77,116 @@ public ConfigureRhs2116Trigger(ConfigureRhs2116Trigger rhs2116Trigger) [Description("Specifies whether the trigger source is local or external.")] public Rhs2116TriggerSource TriggerSource { get; set; } = Rhs2116TriggerSource.Local; + Task probeGroupTask = null; + + Rhs2116ProbeGroup probeGroup = null; + /// /// Gets or sets the channel configuration. /// [XmlIgnore] [Category(ConfigurationCategory)] [Description("Defines the channel configuration")] - public Rhs2116ProbeGroup ProbeGroup { get; set; } = new(); + [Browsable(false)] + [Externalizable(false)] + public Rhs2116ProbeGroup ProbeGroup + { + get + { + if (probeGroup == null) + { + try + { + probeGroup = probeGroupTask?.Result ?? new Rhs2116ProbeGroup(); + } + catch (AggregateException ae) + { + probeGroup = new(); + 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; + } + + 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(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 string ProbeInterfaceLoadFileName + { + get => probeInterfaceFileName; + set + { + probeInterfaceFileName = value; + probeGroupTask = Task.Run(() => + { + if (string.IsNullOrEmpty(probeInterfaceFileName)) + return new Rhs2116ProbeGroup(); + + return ProbeInterfaceHelper.LoadExternalProbeInterfaceFile(probeInterfaceFileName); + }); + } + } + + /// + /// 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. + /// + /// + /// [Obsolete]. Cannot tag this property with the Obsolete attribute due to https://github.com/dotnet/runtime/issues/100453 + /// + [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; + } + + /// + /// Prevent the ProbeGroup property from being serialized. + /// + /// False + [Obsolete] + public bool ShouldSerializeProbeGroupString() + { + return false; + } /// /// Gets or sets if trigger is armed. diff --git a/OpenEphys.Onix1/Rhs2116ProbeGroup.cs b/OpenEphys.Onix1/Rhs2116ProbeGroup.cs index 814e6900..bfb9678b 100644 --- a/OpenEphys.Onix1/Rhs2116ProbeGroup.cs +++ b/OpenEphys.Onix1/Rhs2116ProbeGroup.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using OpenEphys.ProbeInterface.NET; @@ -82,6 +82,11 @@ public Rhs2116ProbeGroup(Rhs2116ProbeGroup probeGroup) { } + internal Rhs2116ProbeGroup Clone() + { + return new Rhs2116ProbeGroup(Specification, Version, Probes.Select(probe => new Probe(probe)).ToArray()); + } + internal static ContactAnnotations DefaultContactAnnotations(int numberOfChannels, int probeIndex) { string[] contactAnnotations = new string[numberOfChannels];