From 1f583a288411164eed3e723bb261f1a4eb086262 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sat, 10 Aug 2024 08:36:55 +0100 Subject: [PATCH 1/5] Add low-level interface to the commutator --- OpenEphys.Commutator/TurnMotor.cs | 72 +++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 OpenEphys.Commutator/TurnMotor.cs diff --git a/OpenEphys.Commutator/TurnMotor.cs b/OpenEphys.Commutator/TurnMotor.cs new file mode 100644 index 0000000..eca8c87 --- /dev/null +++ b/OpenEphys.Commutator/TurnMotor.cs @@ -0,0 +1,72 @@ +using System; +using System.ComponentModel; +using System.Reactive; +using System.Reactive.Linq; +using Bonsai; +using Bonsai.IO.Ports; + +namespace OpenEphys.Commutator +{ + /// + /// Turns an Open Ephys commutator using a sequence of angle steps, in units of turns. + /// + [Description("Turns an Open Ephys commutator using a sequence of angle steps, in units of turns.")] + public class TurnMotor : Sink + { + readonly SerialWriteLine serialWriteLine = new(); + + /// + /// Gets or sets the name of the serial port that the commutator is plugged into. + /// + [TypeConverter("Bonsai.IO.Ports.PortNameConverter, Bonsai.System")] + [Description("The name of the serial port that the commutator is plugged into.")] + public string PortName + { + get => serialWriteLine.PortName; + set => serialWriteLine.PortName = value; + } + + /// + /// Turns an Open Ephys commutator using a sequence of angle steps, in units of turns. + /// + /// + /// The sequence of angle steps, in units of turns, by which to rotate the commutator. + /// + /// + /// A sequence which is identical to the sequence, but + /// where the commutator is instructed to turn by each step value in the sequence. + /// + public override IObservable Process(IObservable source) + { + return Observable.Create(observer => + { + // inner observable will format commands to turn the motor + var commands = Observable.Create(commandObserver => + { + var valueObserver = Observer.Create( + turns => + { + // only format turns for valid values + if (!double.IsNaN(turns) && !double.IsInfinity(turns)) + { + var command = $"{{turn: {turns}}}"; + commandObserver.OnNext(command); + } + + // send all values to the output observer regardless + observer.OnNext(turns); + }, + commandObserver.OnError, + commandObserver.OnCompleted); + return source.SubscribeSafe(valueObserver); + }); + + // route termination notifications to the output observer + return serialWriteLine.Process(commands).SubscribeSafe(Observer.Create( + _ => { }, + observer.OnError, + observer.OnCompleted)); + }); + } + } +} From 70406c3c0b6b63295790278c26c0b17069cd8b6b Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sat, 10 Aug 2024 14:44:52 +0100 Subject: [PATCH 2/5] Add swing-twist decomposition feedback controller --- .../QuaternionTwistController.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 OpenEphys.Commutator/QuaternionTwistController.cs diff --git a/OpenEphys.Commutator/QuaternionTwistController.cs b/OpenEphys.Commutator/QuaternionTwistController.cs new file mode 100644 index 0000000..b0cada4 --- /dev/null +++ b/OpenEphys.Commutator/QuaternionTwistController.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Numerics; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Commutator +{ + /// + /// Calculates a feedback control signal to compensate for twisting about the specified axis, + /// in units of turns. + /// + [Description("Calculates a feedback control signal to compensate for twisting about the specified axis, in units of turns.")] + public class QuaternionTwistController : Combinator + { + /// + /// Gets or sets the direction vector specifying the axis around which to calculate the twist feedback. + /// + [TypeConverter(typeof(NumericRecordConverter))] + [Description("The direction vector specifying the axis around which to calculate the twist feedback.")] + public Vector3 RotationAxis { get; set; } = Vector3.UnitZ; + + /// + /// Calculates a feedback control signal from an observable sequence of rotation measurements + /// to compensate for twisting about the specified axis. + /// + /// The sequence of rotation measurements. + /// The sequence of controller feedback values, in units of turns. + public override IObservable Process(IObservable source) + { + return Observable.Defer(() => + { + double? previousTwist = default; + return source.Select(rotation => + { + // project rotation axis onto the direction axis + var direction = RotationAxis; + var rotationAxis = new Vector3(rotation.X, rotation.Y, rotation.Z); + var dotProduct = Vector3.Dot(rotationAxis, direction); + var projection = dotProduct / Vector3.Dot(direction, direction) * direction; + var twist = new Quaternion(projection, rotation.W); + twist = Quaternion.Normalize(twist); + if (dotProduct < 0) // account for angle-axis flipping + { + twist = -twist; + } + + // normalize twist feedback in units of turns + var twistAngle = 2 * Math.Acos(twist.W); + var feedback = previousTwist.HasValue + ? (twistAngle - previousTwist.GetValueOrDefault() + 3 * Math.PI) % (2 * Math.PI) - Math.PI + : 0; + previousTwist = twistAngle; + return -feedback / (2 * Math.PI); + }); + }); + } + } +} From e7403b930f0317c73de00ef7499c8b1e873a968e Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sat, 10 Aug 2024 14:44:57 +0100 Subject: [PATCH 3/5] Add high-level commutator controller --- OpenEphys.Commutator/AutoCommutator.bonsai | 47 +++++++++++++++++++ .../OpenEphys.Commutator.csproj | 4 ++ 2 files changed, 51 insertions(+) create mode 100644 OpenEphys.Commutator/AutoCommutator.bonsai diff --git a/OpenEphys.Commutator/AutoCommutator.bonsai b/OpenEphys.Commutator/AutoCommutator.bonsai new file mode 100644 index 0000000..005d69d --- /dev/null +++ b/OpenEphys.Commutator/AutoCommutator.bonsai @@ -0,0 +1,47 @@ + + + Control an Open Ephys commutator by using rotation angle measurements. + + + + Source1 + + + + PT0.1S + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenEphys.Commutator/OpenEphys.Commutator.csproj b/OpenEphys.Commutator/OpenEphys.Commutator.csproj index 26d1d82..5e7a039 100644 --- a/OpenEphys.Commutator/OpenEphys.Commutator.csproj +++ b/OpenEphys.Commutator/OpenEphys.Commutator.csproj @@ -7,6 +7,10 @@ net472 + + + + From af903f51558ef0e667f93c454650b6ee9e815fc0 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Mon, 12 Aug 2024 00:25:23 +0100 Subject: [PATCH 4/5] Defer turn motor operator to future revision --- OpenEphys.Commutator/AutoCommutator.bonsai | 17 +++-- OpenEphys.Commutator/TurnMotor.cs | 72 ---------------------- 2 files changed, 13 insertions(+), 76 deletions(-) delete mode 100644 OpenEphys.Commutator/TurnMotor.cs diff --git a/OpenEphys.Commutator/AutoCommutator.bonsai b/OpenEphys.Commutator/AutoCommutator.bonsai index 005d69d..c8d21f5 100644 --- a/OpenEphys.Commutator/AutoCommutator.bonsai +++ b/OpenEphys.Commutator/AutoCommutator.bonsai @@ -3,6 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:commutator="clr-namespace:OpenEphys.Commutator;assembly=OpenEphys.Commutator" + xmlns:port="clr-namespace:Bonsai.IO.Ports;assembly=Bonsai.System" xmlns="https://bonsai-rx.org/2018/workflow"> Control an Open Ephys commutator by using rotation angle measurements. @@ -27,11 +28,18 @@ + + {{turn : {0}}} + it + - + + + \r\n + @@ -39,9 +47,10 @@ - - - + + + + \ No newline at end of file diff --git a/OpenEphys.Commutator/TurnMotor.cs b/OpenEphys.Commutator/TurnMotor.cs deleted file mode 100644 index eca8c87..0000000 --- a/OpenEphys.Commutator/TurnMotor.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.ComponentModel; -using System.Reactive; -using System.Reactive.Linq; -using Bonsai; -using Bonsai.IO.Ports; - -namespace OpenEphys.Commutator -{ - /// - /// Turns an Open Ephys commutator using a sequence of angle steps, in units of turns. - /// - [Description("Turns an Open Ephys commutator using a sequence of angle steps, in units of turns.")] - public class TurnMotor : Sink - { - readonly SerialWriteLine serialWriteLine = new(); - - /// - /// Gets or sets the name of the serial port that the commutator is plugged into. - /// - [TypeConverter("Bonsai.IO.Ports.PortNameConverter, Bonsai.System")] - [Description("The name of the serial port that the commutator is plugged into.")] - public string PortName - { - get => serialWriteLine.PortName; - set => serialWriteLine.PortName = value; - } - - /// - /// Turns an Open Ephys commutator using a sequence of angle steps, in units of turns. - /// - /// - /// The sequence of angle steps, in units of turns, by which to rotate the commutator. - /// - /// - /// A sequence which is identical to the sequence, but - /// where the commutator is instructed to turn by each step value in the sequence. - /// - public override IObservable Process(IObservable source) - { - return Observable.Create(observer => - { - // inner observable will format commands to turn the motor - var commands = Observable.Create(commandObserver => - { - var valueObserver = Observer.Create( - turns => - { - // only format turns for valid values - if (!double.IsNaN(turns) && !double.IsInfinity(turns)) - { - var command = $"{{turn: {turns}}}"; - commandObserver.OnNext(command); - } - - // send all values to the output observer regardless - observer.OnNext(turns); - }, - commandObserver.OnError, - commandObserver.OnCompleted); - return source.SubscribeSafe(valueObserver); - }); - - // route termination notifications to the output observer - return serialWriteLine.Process(commands).SubscribeSafe(Observer.Create( - _ => { }, - observer.OnError, - observer.OnCompleted)); - }); - } - } -} From aeadf1bfeb15d551a777bf63ad610b9b5827a387 Mon Sep 17 00:00:00 2001 From: jonnew Date: Mon, 12 Aug 2024 09:53:25 -0400 Subject: [PATCH 5/5] Add LED on/off to Autocommutator - Minor changes to description text - Rename QuaternionTwistController -> QuaternionToTwist because it does not control anything --- .bonsai/Bonsai.config | 51 +++++++++++++++ OpenEphys.Commutator/AutoCommutator.bonsai | 63 ++++++++++++++++++- ...wistController.cs => QuaternionToTwist.cs} | 21 ++++--- 3 files changed, 124 insertions(+), 11 deletions(-) rename OpenEphys.Commutator/{QuaternionTwistController.cs => QuaternionToTwist.cs} (71%) diff --git a/.bonsai/Bonsai.config b/.bonsai/Bonsai.config index ab4ecd0..7e2921c 100644 --- a/.bonsai/Bonsai.config +++ b/.bonsai/Bonsai.config @@ -2,45 +2,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -48,9 +93,12 @@ + + + @@ -60,6 +108,7 @@ + @@ -70,6 +119,8 @@ + + diff --git a/OpenEphys.Commutator/AutoCommutator.bonsai b/OpenEphys.Commutator/AutoCommutator.bonsai index c8d21f5..ffe3127 100644 --- a/OpenEphys.Commutator/AutoCommutator.bonsai +++ b/OpenEphys.Commutator/AutoCommutator.bonsai @@ -20,7 +20,7 @@ - + 0 0 @@ -32,6 +32,55 @@ {{turn : {0}}} it + + + + + + false + + + + + + + Source1 + + + + + + + + + + + {led: true} + + + + + + + Source1 + + + + + + + + + + + + + {led: false} + + + + + @@ -48,9 +97,17 @@ - - + + + + + + + + + + \ No newline at end of file diff --git a/OpenEphys.Commutator/QuaternionTwistController.cs b/OpenEphys.Commutator/QuaternionToTwist.cs similarity index 71% rename from OpenEphys.Commutator/QuaternionTwistController.cs rename to OpenEphys.Commutator/QuaternionToTwist.cs index b0cada4..f16d504 100644 --- a/OpenEphys.Commutator/QuaternionTwistController.cs +++ b/OpenEphys.Commutator/QuaternionToTwist.cs @@ -8,25 +8,30 @@ namespace OpenEphys.Commutator { /// - /// Calculates a feedback control signal to compensate for twisting about the specified axis, - /// in units of turns. + /// Calculates a the rotation about a specified axis (the "twist") that has occurred between successive 3D + /// rotation measurements. /// [Description("Calculates a feedback control signal to compensate for twisting about the specified axis, in units of turns.")] - public class QuaternionTwistController : Combinator + public class QuaternionToTwist : Combinator { /// - /// Gets or sets the direction vector specifying the axis around which to calculate the twist feedback. + /// Gets or sets the direction vector specifying the axis around which to calculate the twist. /// + /// + /// This vector should point, using the reference frame of the device producing rotation measurements, + /// in the direction that the tether exits the headstage. Note that negating this vector will result in + /// negating the direction of twisting. + /// [TypeConverter(typeof(NumericRecordConverter))] - [Description("The direction vector specifying the axis around which to calculate the twist feedback.")] + [Description("The direction vector specifying the axis around which to calculate the twist.")] public Vector3 RotationAxis { get; set; } = Vector3.UnitZ; /// - /// Calculates a feedback control signal from an observable sequence of rotation measurements - /// to compensate for twisting about the specified axis. + /// Calculates a twist about that has occurred between successive rotation + /// measurements provided by the input sequence. /// /// The sequence of rotation measurements. - /// The sequence of controller feedback values, in units of turns. + /// The sequence of twist values, in units of turns. public override IObservable Process(IObservable source) { return Observable.Defer(() =>