From be3923addb26e2263b66659781dc8b19f1a1b30b Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 10 May 2024 16:36:35 +0100 Subject: [PATCH] Added base visualizer for kinematic state which also serves as base for mashup --- .../KinematicStateVisualizer.cs | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 src/Bonsai.ML.Visualizers/KinematicStateVisualizer.cs diff --git a/src/Bonsai.ML.Visualizers/KinematicStateVisualizer.cs b/src/Bonsai.ML.Visualizers/KinematicStateVisualizer.cs new file mode 100644 index 00000000..38138825 --- /dev/null +++ b/src/Bonsai.ML.Visualizers/KinematicStateVisualizer.cs @@ -0,0 +1,203 @@ +using Bonsai.Design; +using Bonsai; +using Bonsai.ML.LinearDynamicalSystems; +using Bonsai.ML.LinearDynamicalSystems.Kinematics; +using Bonsai.ML.Visualizers; +using System.Collections.Generic; +using System; +using System.Windows.Forms; +using System.Xml.Serialization; +using System.Reactive.Linq; +using OxyPlot.Series; +using System.Linq; +using System.Drawing; +using System.Reflection; + +[assembly: TypeVisualizer(typeof(KinematicStateVisualizer), Target = typeof(KinematicState))] + +namespace Bonsai.ML.Visualizers +{ + + /// + /// Provides a type visualizer to display the state components of a Kalman Filter Kinematics model. + /// + public class KinematicStateVisualizer : MashupVisualizer + { + + private int selectedStateIndex = 0; + private int selectedKinematicIndex = 0; + private DateTime? _startTime; + private TimeSpan updateFrequency = TimeSpan.FromSeconds(1/30); + private LineSeries lineSeries; + private AreaSeries areaSeries; + + /// + /// The selected state component property of the Kinematic State object being visualized. + /// + [XmlIgnore()] + public PropertyInfo stateComponentProperty { get; private set; } + + /// + /// The selected kinematic component property of the Kinematic State object being visualized. + /// + [XmlIgnore()] + public PropertyInfo kinematicComponentProperty { get; private set; } + + /// + /// The underlying plot used for visualization. + /// + // [XmlIgnore()] + internal TimeSeriesOxyPlotBase Plot { get; private set; } + + /// + /// The selected index of the state component to be visualized + /// + public int SelectedStateIndex { get => selectedStateIndex; set => selectedStateIndex = value; } + + /// + /// The selected index of the kinematic component to be visualized + /// + public int SelectedKinematicIndex { get => selectedKinematicIndex; set => selectedKinematicIndex = value; } + + /// + /// Size of the window when loaded + /// + public Size Size { get; set; } = new Size(320, 240); + + /// + /// Capacity or length of time shown along the x axis of the plot during automatic updating + /// + public int Capacity { get; set; } = 10; + + /// + /// Buffer the data beyond the capacity. + /// + public bool BufferData { get; set; } = true; + + /// + public override void Load(IServiceProvider provider) + { + Plot = new TimeSeriesOxyPlotBase() + { + Dock = DockStyle.Fill, + StartTime = DateTime.Now, + Capacity = Capacity, + Size = Size, + BufferData = BufferData + }; + + lineSeries = Plot.AddNewLineSeries("Mean"); + areaSeries = Plot.AddNewAreaSeries("Variance"); + + Plot.ResetLineSeries(lineSeries); + Plot.ResetAreaSeries(areaSeries); + Plot.ResetAxes(); + + List stateComponents = KinematicsHelper.GetStateComponents(); + List kinematicComponents = KinematicsHelper.GetKinematicComponents(); + + Plot.AddComboBoxWithLabel("State component:", stateComponents, selectedStateIndex, StateComponentChanged); + Plot.AddComboBoxWithLabel("Kinematic component:", kinematicComponents, selectedKinematicIndex, KinematicComponentChanged); + + stateComponentProperty = typeof(KinematicComponent).GetProperty(stateComponents[selectedStateIndex]); + kinematicComponentProperty = typeof(KinematicState).GetProperty(kinematicComponents[selectedKinematicIndex]); + + var visualizerService = (IDialogTypeVisualizerService)provider.GetService(typeof(IDialogTypeVisualizerService)); + if (visualizerService != null) + { + visualizerService.AddControl(Plot); + } + _startTime = null; + + base.Load(provider); + } + + + /// + public override void Show(object value) + { + var time = DateTime.Now; + if (!_startTime.HasValue) + { + _startTime = time; + Plot.StartTime = _startTime.Value; + Plot.ResetAxes(); + } + + KinematicState kinematicState = (KinematicState)value; + KinematicComponent kinematicComponent = (KinematicComponent)kinematicComponentProperty.GetValue(kinematicState); + StateComponent stateComponent = (StateComponent)stateComponentProperty.GetValue(kinematicComponent); + + double mean = stateComponent.Mean; + double variance = stateComponent.Variance; + + Plot.AddToLineSeries( + lineSeries: lineSeries, + time: time, + value: mean + ); + + Plot.AddToAreaSeries( + areaSeries: areaSeries, + time: time, + value1: mean + variance, + value2: mean - variance + ); + + if (MashupSources.Count == 0) Plot.SetAxes(minTime: time.AddSeconds(-Capacity), maxTime: time); + + } + + /// + public override IObservable Visualize(IObservable> source, IServiceProvider provider) + { + var mashupSourceStreams = MashupSources.Select(mashupSource => + mashupSource.Visualizer.Visualize(mashupSource.Source.Output, provider) + .Do(value => mashupSource.Visualizer.Show(value))); + + var mergedMashupSources = Observable.Merge(mashupSourceStreams); + + var processedSource = base.Visualize(source, provider); + + return Observable.Merge(mergedMashupSources, processedSource) + .Sample(updateFrequency) + .Do(_ => Plot.UpdatePlot()); + } + + /// + public override void Unload() + { + _startTime = null; + if (!Plot.IsDisposed) + { + Plot.Dispose(); + } + } + + private void StateComponentChanged(object sender, EventArgs e) + { + ToolStripComboBox comboBox = sender as ToolStripComboBox; + selectedStateIndex = comboBox.SelectedIndex; + var selectedName = comboBox.SelectedItem.ToString(); + stateComponentProperty = typeof(KinematicComponent).GetProperty(selectedName); + _startTime = null; + + Plot.ResetLineSeries(lineSeries); + Plot.ResetAreaSeries(areaSeries); + Plot.ResetAxes(); + } + + private void KinematicComponentChanged(object sender, EventArgs e) + { + ToolStripComboBox comboBox = sender as ToolStripComboBox; + selectedKinematicIndex = comboBox.SelectedIndex; + var selectedName = comboBox.SelectedItem.ToString(); + kinematicComponentProperty = typeof(KinematicState).GetProperty(selectedName); + _startTime = null; + + Plot.ResetLineSeries(lineSeries); + Plot.ResetAreaSeries(areaSeries); + Plot.ResetAxes(); + } + } +}