From 1b881ac33550834b6850cb32bb5c25ae55d7f27c Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Thu, 14 Nov 2024 15:23:25 +0000 Subject: [PATCH 01/33] Updated package version to preview --- Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 11390a0..68e1811 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,8 +14,8 @@ icon.png true git - 0.3.1 - + 0.4.0 + preview241114 12.0 From a1673e0d9962837a4a41fa8590afe041f8ba0886 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Thu, 14 Nov 2024 15:23:59 +0000 Subject: [PATCH 02/33] Added visualizer for unidimensional array heatmap time series data --- ...UnidimensionalArrayTimeSeriesVisualizer.cs | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs diff --git a/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs b/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs new file mode 100644 index 0000000..2e99ca9 --- /dev/null +++ b/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Windows.Forms; +using Bonsai; +using Bonsai.Design; + +[assembly: TypeVisualizer(typeof(Bonsai.ML.Design.UnidimensionalArrayTimeSeriesVisualizer), + Target = typeof(double[]))] + +namespace Bonsai.ML.Design +{ + /// + /// Provides a type visualizer to display unidimensional array data as a heatmap time series. + /// + public class UnidimensionalArrayTimeSeriesVisualizer : DialogTypeVisualizer + { + /// + /// Gets or sets the selected index of the color palette to use. + /// + public int PaletteSelectedIndex { get; set; } + + /// + /// Gets or sets the selected index of the render method to use. + /// + public int RenderMethodSelectedIndex { get; set; } + + private HeatMapSeriesOxyPlotBase Plot; + + private int _capacity = 100; + + public int Capacity + { + get => _capacity; + set + { + _capacity = value; + // if (_capacity < dataList.Count) + // { + // dataList.RemoveRange(0, dataList.Count - _capacity); + // } + } + } + + private List dataList = new(); + + /// + public override void Load(IServiceProvider provider) + { + Plot = new HeatMapSeriesOxyPlotBase(PaletteSelectedIndex, RenderMethodSelectedIndex) + { + Dock = DockStyle.Fill, + }; + + Plot.PaletteComboBoxValueChanged += PaletteIndexChanged; + Plot.RenderMethodComboBoxValueChanged += RenderMethodIndexChanged; + + var capacityLabel = new ToolStripLabel + { + Text = "Capacity:", + AutoSize = true + }; + var capacityValue = new ToolStripLabel + { + Text = Capacity.ToString(), + AutoSize = true + }; + + Plot.StatusStrip.Items.AddRange([ + capacityLabel, + capacityValue + ]); + + var visualizerService = (IDialogTypeVisualizerService)provider.GetService(typeof(IDialogTypeVisualizerService)); + if (visualizerService != null) + { + visualizerService.AddControl(Plot); + } + } + + /// + public override void Show(object value) + { + var array = (double[])value; + + if (dataList.Count < Capacity) + { + dataList.Add(array); + } + else + { + while (dataList.Count >= Capacity) + { + dataList.RemoveAt(0); + } + dataList.Add(array); + } + + var shape = (dataList.Count, array.Length); + var mdarray = new double[shape.Item1, shape.Item2]; + for (int i = 0; i < shape.Item1; i++) + { + for (int j = 0; j < shape.Item2; j++) + { + mdarray[i, j] = dataList[i][j]; + } + } + + Plot.UpdateHeatMapSeries( + -0.5, + shape.Item1 - 0.5, + -0.5, + shape.Item2 - 0.5, + mdarray + ); + + Plot.UpdatePlot(); + } + + /// + public override void Unload() + { + if (!Plot.IsDisposed) + { + Plot.Dispose(); + } + } + + private void PaletteIndexChanged(object sender, EventArgs e) + { + PaletteSelectedIndex = Plot.PaletteComboBox.SelectedIndex; + } + + private void RenderMethodIndexChanged(object sender, EventArgs e) + { + RenderMethodSelectedIndex = Plot.RenderMethodComboBox.SelectedIndex; + } + } +} From 5182773029f166abf1518f642a1fbde560d14ca1 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Thu, 14 Nov 2024 16:58:39 +0000 Subject: [PATCH 03/33] Added new neural decoding package --- Bonsai.ML.sln | 44 ++++++++++++------- .../Bonsai.ML.NeuralDecoding.Design.csproj | 17 +++++++ .../Properties/launchSettings.json | 10 +++++ .../Bonsai.ML.NeuralDecoding.csproj | 15 +++++++ .../Properties/launchSettings.json | 10 +++++ 5 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 src/Bonsai.ML.NeuralDecoding.Design/Bonsai.ML.NeuralDecoding.Design.csproj create mode 100644 src/Bonsai.ML.NeuralDecoding.Design/Properties/launchSettings.json create mode 100644 src/Bonsai.ML.NeuralDecoding/Bonsai.ML.NeuralDecoding.csproj create mode 100644 src/Bonsai.ML.NeuralDecoding/Properties/launchSettings.json diff --git a/Bonsai.ML.sln b/Bonsai.ML.sln index c5a91b1..14d5549 100644 --- a/Bonsai.ML.sln +++ b/Bonsai.ML.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 @@ -30,6 +30,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.LinearDynamicalSy EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.HiddenMarkovModels.Design", "src\Bonsai.ML.HiddenMarkovModels.Design\Bonsai.ML.HiddenMarkovModels.Design.csproj", "{FC395DDC-62A4-4E14-A198-272AB05B33C7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.NeuralDecoding", "src\Bonsai.ML.NeuralDecoding\Bonsai.ML.NeuralDecoding.csproj", "{CE083548-26CB-4CF6-AE51-D7E32AE7377A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.NeuralDecoding.Design", "src\Bonsai.ML.NeuralDecoding.Design\Bonsai.ML.NeuralDecoding.Design.csproj", "{D2CECE2F-CE7C-41BC-9888-EA53493D64D6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,18 +64,26 @@ Global {39A4414F-52B1-42D7-82FA-E65DAD885264}.Debug|Any CPU.Build.0 = Debug|Any CPU {39A4414F-52B1-42D7-82FA-E65DAD885264}.Release|Any CPU.ActiveCfg = Release|Any CPU {39A4414F-52B1-42D7-82FA-E65DAD885264}.Release|Any CPU.Build.0 = Release|Any CPU - {A135C7DB-EA50-4FC6-A6CB-6A5A5CC5FA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A135C7DB-EA50-4FC6-A6CB-6A5A5CC5FA13}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A135C7DB-EA50-4FC6-A6CB-6A5A5CC5FA13}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A135C7DB-EA50-4FC6-A6CB-6A5A5CC5FA13}.Release|Any CPU.Build.0 = Release|Any CPU - {17DF50BE-F481-4904-A4C8-5DF9725B2CA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17DF50BE-F481-4904-A4C8-5DF9725B2CA1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17DF50BE-F481-4904-A4C8-5DF9725B2CA1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17DF50BE-F481-4904-A4C8-5DF9725B2CA1}.Release|Any CPU.Build.0 = Release|Any CPU - {FC395DDC-62A4-4E14-A198-272AB05B33C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC395DDC-62A4-4E14-A198-272AB05B33C7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC395DDC-62A4-4E14-A198-272AB05B33C7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC395DDC-62A4-4E14-A198-272AB05B33C7}.Release|Any CPU.Build.0 = Release|Any CPU + {A135C7DB-EA50-4FC6-A6CB-6A5A5CC5FA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A135C7DB-EA50-4FC6-A6CB-6A5A5CC5FA13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A135C7DB-EA50-4FC6-A6CB-6A5A5CC5FA13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A135C7DB-EA50-4FC6-A6CB-6A5A5CC5FA13}.Release|Any CPU.Build.0 = Release|Any CPU + {17DF50BE-F481-4904-A4C8-5DF9725B2CA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17DF50BE-F481-4904-A4C8-5DF9725B2CA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17DF50BE-F481-4904-A4C8-5DF9725B2CA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17DF50BE-F481-4904-A4C8-5DF9725B2CA1}.Release|Any CPU.Build.0 = Release|Any CPU + {FC395DDC-62A4-4E14-A198-272AB05B33C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC395DDC-62A4-4E14-A198-272AB05B33C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC395DDC-62A4-4E14-A198-272AB05B33C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC395DDC-62A4-4E14-A198-272AB05B33C7}.Release|Any CPU.Build.0 = Release|Any CPU + {CE083548-26CB-4CF6-AE51-D7E32AE7377A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE083548-26CB-4CF6-AE51-D7E32AE7377A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE083548-26CB-4CF6-AE51-D7E32AE7377A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE083548-26CB-4CF6-AE51-D7E32AE7377A}.Release|Any CPU.Build.0 = Release|Any CPU + {D2CECE2F-CE7C-41BC-9888-EA53493D64D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2CECE2F-CE7C-41BC-9888-EA53493D64D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2CECE2F-CE7C-41BC-9888-EA53493D64D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2CECE2F-CE7C-41BC-9888-EA53493D64D6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -84,8 +96,10 @@ Global {81DB65B3-EA65-4947-8CF1-0E777324C082} = {461FE3E2-21C4-47F9-8405-DF72326AAB2B} {BAD0A733-8EFB-4EAF-9648-9851656AF7FF} = {12312384-8828-4786-AE19-EFCEDF968290} {39A4414F-52B1-42D7-82FA-E65DAD885264} = {12312384-8828-4786-AE19-EFCEDF968290} - {A135C7DB-EA50-4FC6-A6CB-6A5A5CC5FA13} = {12312384-8828-4786-AE19-EFCEDF968290} - {17DF50BE-F481-4904-A4C8-5DF9725B2CA1} = {12312384-8828-4786-AE19-EFCEDF968290} + {A135C7DB-EA50-4FC6-A6CB-6A5A5CC5FA13} = {12312384-8828-4786-AE19-EFCEDF968290} + {17DF50BE-F481-4904-A4C8-5DF9725B2CA1} = {12312384-8828-4786-AE19-EFCEDF968290} + {CE083548-26CB-4CF6-AE51-D7E32AE7377A} = {12312384-8828-4786-AE19-EFCEDF968290} + {D2CECE2F-CE7C-41BC-9888-EA53493D64D6} = {12312384-8828-4786-AE19-EFCEDF968290} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B6468F13-97CD-45E0-9E1E-C122D7F1E09F} diff --git a/src/Bonsai.ML.NeuralDecoding.Design/Bonsai.ML.NeuralDecoding.Design.csproj b/src/Bonsai.ML.NeuralDecoding.Design/Bonsai.ML.NeuralDecoding.Design.csproj new file mode 100644 index 0000000..48c4250 --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoding.Design/Bonsai.ML.NeuralDecoding.Design.csproj @@ -0,0 +1,17 @@ + + + Bonsai.ML.NeuralDecoding.Design + A Bonsai package for visualizing decoding neural activity. + Bonsai Rx ML Neural Decoding Design + net472 + true + + + + + + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.NeuralDecoding.Design/Properties/launchSettings.json b/src/Bonsai.ML.NeuralDecoding.Design/Properties/launchSettings.json new file mode 100644 index 0000000..b48bcfa --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoding.Design/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Bonsai": { + "commandName": "Executable", + "executablePath": "$(registry:HKEY_CURRENT_USER\\Software\\Bonsai Foundation\\Bonsai@InstallDir)Bonsai.exe", + "commandLineArgs": "--lib:\"$(TargetDir).\"", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/src/Bonsai.ML.NeuralDecoding/Bonsai.ML.NeuralDecoding.csproj b/src/Bonsai.ML.NeuralDecoding/Bonsai.ML.NeuralDecoding.csproj new file mode 100644 index 0000000..4c5f5fb --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoding/Bonsai.ML.NeuralDecoding.csproj @@ -0,0 +1,15 @@ + + + Bonsai.ML.NeuralDecoding + A Bonsai package for decoding neural activity. + Bonsai Rx ML Neural Decoding + net472;netstandard2.0 + + + + + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.NeuralDecoding/Properties/launchSettings.json b/src/Bonsai.ML.NeuralDecoding/Properties/launchSettings.json new file mode 100644 index 0000000..b48bcfa --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoding/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Bonsai": { + "commandName": "Executable", + "executablePath": "$(registry:HKEY_CURRENT_USER\\Software\\Bonsai Foundation\\Bonsai@InstallDir)Bonsai.exe", + "commandLineArgs": "--lib:\"$(TargetDir).\"", + "nativeDebugging": true + } + } +} \ No newline at end of file From d81967c318bfa54cb095513873b5d39a2a5ef71a Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Thu, 14 Nov 2024 16:59:11 +0000 Subject: [PATCH 04/33] Make plot model public --- src/Bonsai.ML.Design/HeatMapSeriesOxyPlotBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Bonsai.ML.Design/HeatMapSeriesOxyPlotBase.cs b/src/Bonsai.ML.Design/HeatMapSeriesOxyPlotBase.cs index e9ddf64..85cee3a 100644 --- a/src/Bonsai.ML.Design/HeatMapSeriesOxyPlotBase.cs +++ b/src/Bonsai.ML.Design/HeatMapSeriesOxyPlotBase.cs @@ -63,6 +63,11 @@ public class HeatMapSeriesOxyPlotBase : UserControl /// public StatusStrip StatusStrip => statusStrip; + /// + /// Gets the plot model. + /// + public PlotModel Model => model; + /// /// Constructor of the TimeSeriesOxyPlotBase class. /// Requires a line series name and an area series name. From 60a7d274e53c60e22aaad3da7b90ed49372aa106 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Thu, 14 Nov 2024 17:00:02 +0000 Subject: [PATCH 05/33] Make plot public --- ...UnidimensionalArrayTimeSeriesVisualizer.cs | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs b/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs index 2e99ca9..bc886f5 100644 --- a/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs +++ b/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs @@ -24,20 +24,24 @@ public class UnidimensionalArrayTimeSeriesVisualizer : DialogTypeVisualizer /// public int RenderMethodSelectedIndex { get; set; } - private HeatMapSeriesOxyPlotBase Plot; + private HeatMapSeriesOxyPlotBase plot; + + /// + /// Gets the plot control. + /// + public HeatMapSeriesOxyPlotBase Plot => plot; private int _capacity = 100; + /// + /// Gets or sets the integer value that determines how many data points should be shown along the x axis. + /// public int Capacity { get => _capacity; set { _capacity = value; - // if (_capacity < dataList.Count) - // { - // dataList.RemoveRange(0, dataList.Count - _capacity); - // } } } @@ -46,13 +50,13 @@ public int Capacity /// public override void Load(IServiceProvider provider) { - Plot = new HeatMapSeriesOxyPlotBase(PaletteSelectedIndex, RenderMethodSelectedIndex) + plot = new HeatMapSeriesOxyPlotBase(PaletteSelectedIndex, RenderMethodSelectedIndex) { Dock = DockStyle.Fill, }; - Plot.PaletteComboBoxValueChanged += PaletteIndexChanged; - Plot.RenderMethodComboBoxValueChanged += RenderMethodIndexChanged; + plot.PaletteComboBoxValueChanged += PaletteIndexChanged; + plot.RenderMethodComboBoxValueChanged += RenderMethodIndexChanged; var capacityLabel = new ToolStripLabel { @@ -65,7 +69,7 @@ public override void Load(IServiceProvider provider) AutoSize = true }; - Plot.StatusStrip.Items.AddRange([ + plot.StatusStrip.Items.AddRange([ capacityLabel, capacityValue ]); @@ -73,7 +77,7 @@ public override void Load(IServiceProvider provider) var visualizerService = (IDialogTypeVisualizerService)provider.GetService(typeof(IDialogTypeVisualizerService)); if (visualizerService != null) { - visualizerService.AddControl(Plot); + visualizerService.AddControl(plot); } } @@ -105,7 +109,7 @@ public override void Show(object value) } } - Plot.UpdateHeatMapSeries( + plot.UpdateHeatMapSeries( -0.5, shape.Item1 - 0.5, -0.5, @@ -113,26 +117,26 @@ public override void Show(object value) mdarray ); - Plot.UpdatePlot(); + plot.UpdatePlot(); } /// public override void Unload() { - if (!Plot.IsDisposed) + if (!plot.IsDisposed) { - Plot.Dispose(); + plot.Dispose(); } } private void PaletteIndexChanged(object sender, EventArgs e) { - PaletteSelectedIndex = Plot.PaletteComboBox.SelectedIndex; + PaletteSelectedIndex = plot.PaletteComboBox.SelectedIndex; } private void RenderMethodIndexChanged(object sender, EventArgs e) { - RenderMethodSelectedIndex = Plot.RenderMethodComboBox.SelectedIndex; + RenderMethodSelectedIndex = plot.RenderMethodComboBox.SelectedIndex; } } } From 5af671391a54b45693afdece7fa3de02abb6ce3d Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Thu, 14 Nov 2024 17:00:24 +0000 Subject: [PATCH 06/33] Added classes for getting posterior and position range --- src/Bonsai.ML.NeuralDecoding/PositionRange.cs | 61 ++++++++++++++++++ src/Bonsai.ML.NeuralDecoding/Posterior.cs | 62 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/Bonsai.ML.NeuralDecoding/PositionRange.cs create mode 100644 src/Bonsai.ML.NeuralDecoding/Posterior.cs diff --git a/src/Bonsai.ML.NeuralDecoding/PositionRange.cs b/src/Bonsai.ML.NeuralDecoding/PositionRange.cs new file mode 100644 index 0000000..5d279f1 --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoding/PositionRange.cs @@ -0,0 +1,61 @@ +using System.ComponentModel; +using System; +using System.Reactive.Linq; +using Python.Runtime; +using Bonsai.ML.Python; + +namespace Bonsai.ML.NeuralDecoding; + +/// +/// Transforms the input sequence of Python objects into a sequence of instances. +/// +[Combinator] +[Description("Transforms the input sequence of Python objects into a sequence of PositionRange instances.")] +[WorkflowElementCategory(ElementCategory.Transform)] +public class PositionRange +{ + /// + /// The position bins. + /// + public double[] PositionBins { get; set; } + + /// + /// The bin size. + /// + public double BinSize { get; set; } + + /// + /// The number of bins. + /// + public int NumberOfBins { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public PositionRange() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + public PositionRange(double[] positionBins) + { + PositionBins = positionBins; + BinSize = positionBins[1] - positionBins[0]; + NumberOfBins = positionBins.Length; + } + + /// + /// Transforms the input sequence of Python objects into a sequence of instances. + /// + /// + /// + public IObservable Process(IObservable source) + { + return source.Select(value => { + return new PositionRange((double[])PythonHelper.ConvertPythonObjectToCSharp(value)); + }); + } +} diff --git a/src/Bonsai.ML.NeuralDecoding/Posterior.cs b/src/Bonsai.ML.NeuralDecoding/Posterior.cs new file mode 100644 index 0000000..4fd0821 --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoding/Posterior.cs @@ -0,0 +1,62 @@ +using System.ComponentModel; +using System; +using System.Reactive.Linq; +using System.Linq; +using Python.Runtime; +using Bonsai.ML.Python; + +namespace Bonsai.ML.NeuralDecoding; + +/// +/// Transforms the input sequence of Python objects into a sequence of instances. +/// +[Combinator] +[Description("Transforms the input sequence of Python objects into a sequence of Posterior instances.")] +[WorkflowElementCategory(ElementCategory.Transform)] +public class Posterior +{ + /// + /// The data. + /// + public double[] Data { get; set; } + + /// + /// The argmax. + /// + public int ArgMax { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public Posterior() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public Posterior(double[] data, int argMax) + { + Data = data; + ArgMax = argMax; + } + + /// + /// Transforms the input sequence of Python objects into a sequence of instances. + /// + /// + /// + public IObservable Process(IObservable source) + { + return source.Select(value => { + var data = (double[])PythonHelper.ConvertPythonObjectToCSharp(value); + var argMax = Array.IndexOf(data, data.Max()); + return new Posterior( + data, + argMax + ); + }); + } +} \ No newline at end of file From dfea5b8d40497e16f1eb38a19b591234af425760 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Thu, 14 Nov 2024 17:11:11 +0000 Subject: [PATCH 07/33] Added posterior time series heatmap visualizer --- .../PosteriorTimeSeriesHeatmap.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/Bonsai.ML.NeuralDecoding.Design/PosteriorTimeSeriesHeatmap.cs diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorTimeSeriesHeatmap.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorTimeSeriesHeatmap.cs new file mode 100644 index 0000000..3ac0a41 --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorTimeSeriesHeatmap.cs @@ -0,0 +1,57 @@ +using Bonsai; +using Bonsai.Design; +using System; +using System.Collections.Generic; +using OxyPlot.Series; +using OxyPlot; +using Bonsai.ML.Design; + +[assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoding.Design.PosteriorTimeSeriesHeatmap), + Target = typeof(Bonsai.ML.NeuralDecoding.Posterior))] + +namespace Bonsai.ML.NeuralDecoding.Design +{ + public class PosteriorTimeSeriesHeatmap : DialogTypeVisualizer + { + private UnidimensionalArrayTimeSeriesVisualizer visualizer; + private LineSeries lineSeries; + private List argMaxVals = new(); + + /// + public override void Load(IServiceProvider provider) + { + visualizer = new UnidimensionalArrayTimeSeriesVisualizer(); + visualizer.Load(provider); + + lineSeries = new LineSeries(); + visualizer.Plot.Model.Series.Add(lineSeries); + } + + /// + public override void Show(object value) + { + Posterior posterior = (Posterior)value; + var data = posterior.Data; + var argMax = posterior.ArgMax; + var capacity = visualizer.Capacity; + while (argMaxVals.Count >= capacity) + { + argMaxVals.RemoveAt(0); + } + argMaxVals.Add(argMax); + lineSeries.Points.Clear(); + var count = argMaxVals.Count; + for (int i = 0; i < count; i++) + { + lineSeries.Points.Add(new DataPoint(i, argMaxVals[i])); + } + visualizer.Show(data); + } + + /// + public override void Unload() + { + visualizer.Unload(); + } + } +} From 80e6984e2fe26e7d8fdafcd3fa75609b4aec63ab Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 13:13:54 +0000 Subject: [PATCH 08/33] Remove position range in favor of a single class --- src/Bonsai.ML.NeuralDecoding/PositionRange.cs | 61 ------------------- src/Bonsai.ML.NeuralDecoding/Posterior.cs | 17 +++++- 2 files changed, 14 insertions(+), 64 deletions(-) delete mode 100644 src/Bonsai.ML.NeuralDecoding/PositionRange.cs diff --git a/src/Bonsai.ML.NeuralDecoding/PositionRange.cs b/src/Bonsai.ML.NeuralDecoding/PositionRange.cs deleted file mode 100644 index 5d279f1..0000000 --- a/src/Bonsai.ML.NeuralDecoding/PositionRange.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.ComponentModel; -using System; -using System.Reactive.Linq; -using Python.Runtime; -using Bonsai.ML.Python; - -namespace Bonsai.ML.NeuralDecoding; - -/// -/// Transforms the input sequence of Python objects into a sequence of instances. -/// -[Combinator] -[Description("Transforms the input sequence of Python objects into a sequence of PositionRange instances.")] -[WorkflowElementCategory(ElementCategory.Transform)] -public class PositionRange -{ - /// - /// The position bins. - /// - public double[] PositionBins { get; set; } - - /// - /// The bin size. - /// - public double BinSize { get; set; } - - /// - /// The number of bins. - /// - public int NumberOfBins { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public PositionRange() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - public PositionRange(double[] positionBins) - { - PositionBins = positionBins; - BinSize = positionBins[1] - positionBins[0]; - NumberOfBins = positionBins.Length; - } - - /// - /// Transforms the input sequence of Python objects into a sequence of instances. - /// - /// - /// - public IObservable Process(IObservable source) - { - return source.Select(value => { - return new PositionRange((double[])PythonHelper.ConvertPythonObjectToCSharp(value)); - }); - } -} diff --git a/src/Bonsai.ML.NeuralDecoding/Posterior.cs b/src/Bonsai.ML.NeuralDecoding/Posterior.cs index 4fd0821..f54be3f 100644 --- a/src/Bonsai.ML.NeuralDecoding/Posterior.cs +++ b/src/Bonsai.ML.NeuralDecoding/Posterior.cs @@ -25,6 +25,11 @@ public class Posterior /// public int ArgMax { get; set; } + /// + /// The position range. + /// + public double[] PositionRange { get; set; } + /// /// Initializes a new instance of the class. /// @@ -37,10 +42,12 @@ public Posterior() /// /// /// - public Posterior(double[] data, int argMax) + /// + public Posterior(double[] data, int argMax, double[] positionRange) { Data = data; ArgMax = argMax; + PositionRange = positionRange; } /// @@ -51,11 +58,15 @@ public Posterior(double[] data, int argMax) public IObservable Process(IObservable source) { return source.Select(value => { - var data = (double[])PythonHelper.ConvertPythonObjectToCSharp(value); + var posterior = value[0]; + var placeBinCenters = value[1]; + var data = (double[])PythonHelper.ConvertPythonObjectToCSharp(posterior); var argMax = Array.IndexOf(data, data.Max()); + var positionRange = (double[])PythonHelper.ConvertPythonObjectToCSharp(placeBinCenters); return new Posterior( data, - argMax + argMax, + positionRange ); }); } From 5424fdbeab78717bdf415dd90b96dadec1d226f8 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 13:14:36 +0000 Subject: [PATCH 09/33] Rename posterior time series heatmap to posterior visualizer --- .../PosteriorTimeSeriesHeatmap.cs | 57 ----------- .../PosteriorVisualizer.cs | 96 +++++++++++++++++++ 2 files changed, 96 insertions(+), 57 deletions(-) delete mode 100644 src/Bonsai.ML.NeuralDecoding.Design/PosteriorTimeSeriesHeatmap.cs create mode 100644 src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorTimeSeriesHeatmap.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorTimeSeriesHeatmap.cs deleted file mode 100644 index 3ac0a41..0000000 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorTimeSeriesHeatmap.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Bonsai; -using Bonsai.Design; -using System; -using System.Collections.Generic; -using OxyPlot.Series; -using OxyPlot; -using Bonsai.ML.Design; - -[assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoding.Design.PosteriorTimeSeriesHeatmap), - Target = typeof(Bonsai.ML.NeuralDecoding.Posterior))] - -namespace Bonsai.ML.NeuralDecoding.Design -{ - public class PosteriorTimeSeriesHeatmap : DialogTypeVisualizer - { - private UnidimensionalArrayTimeSeriesVisualizer visualizer; - private LineSeries lineSeries; - private List argMaxVals = new(); - - /// - public override void Load(IServiceProvider provider) - { - visualizer = new UnidimensionalArrayTimeSeriesVisualizer(); - visualizer.Load(provider); - - lineSeries = new LineSeries(); - visualizer.Plot.Model.Series.Add(lineSeries); - } - - /// - public override void Show(object value) - { - Posterior posterior = (Posterior)value; - var data = posterior.Data; - var argMax = posterior.ArgMax; - var capacity = visualizer.Capacity; - while (argMaxVals.Count >= capacity) - { - argMaxVals.RemoveAt(0); - } - argMaxVals.Add(argMax); - lineSeries.Points.Clear(); - var count = argMaxVals.Count; - for (int i = 0; i < count; i++) - { - lineSeries.Points.Add(new DataPoint(i, argMaxVals[i])); - } - visualizer.Show(data); - } - - /// - public override void Unload() - { - visualizer.Unload(); - } - } -} diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs new file mode 100644 index 0000000..fb19852 --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs @@ -0,0 +1,96 @@ +using Bonsai; +using Bonsai.Design; +using System; +using System.Collections.Generic; +using OxyPlot.Series; +using OxyPlot; +using Bonsai.ML.Design; +using System.Windows.Forms; +using System.Reactive.Linq; +using System.Linq; + +[assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoding.Design.PosteriorVisualizer), + Target = typeof(Bonsai.ML.NeuralDecoding.Posterior))] + +namespace Bonsai.ML.NeuralDecoding.Design +{ + /// + /// Provides a mashup visualizer to display the posterior of the neural decoder. + /// + public class PosteriorVisualizer : MashupVisualizer + { + private UnidimensionalArrayTimeSeriesVisualizer visualizer; + private LineSeries lineSeries; + private List argMaxVals = new(); + private double[] positionRange = null; + + /// + /// Gets the underlying heatmap plot. + /// + public HeatMapSeriesOxyPlotBase Plot => visualizer.Plot; + + /// + /// Gets the + /// + public int Capacity => visualizer.Capacity; + + /// + public override void Load(IServiceProvider provider) + { + visualizer = new UnidimensionalArrayTimeSeriesVisualizer(); + visualizer.Load(provider); + + lineSeries = new LineSeries() + { + Color = OxyColors.SkyBlue + }; + visualizer.Plot.Model.Series.Add(lineSeries); + + base.Load(provider); + } + + /// + public override void Show(object value) + { + Posterior posterior = (Posterior)value; + var data = posterior.Data; + var argMax = posterior.ArgMax; + var positionRange = posterior.PositionRange; + var capacity = visualizer.Capacity; + while (argMaxVals.Count >= capacity) + { + argMaxVals.RemoveAt(0); + } + argMaxVals.Add(argMax); + lineSeries.Points.Clear(); + var count = argMaxVals.Count; + for (int i = 0; i < count; i++) + { + lineSeries.Points.Add(new DataPoint(i, argMaxVals[i])); + } + visualizer.Show(data); + } + + /// + public override void Unload() + { + visualizer.Unload(); + } + + /// + public override IObservable Visualize(IObservable> source, IServiceProvider provider) + { + if (provider.GetService(typeof(IDialogTypeVisualizerService)) is not Control visualizerControl) + { + return source; + } + + return Observable.Merge(source.SelectMany(xs => xs.Do( + value => Show(value), + () => visualizerControl.BeginInvoke(SequenceCompleted))), + Observable.Merge(MashupSources.Select( + mashupSource => mashupSource.Visualizer.Visualize(mashupSource.Source.Output, provider) + .Do(value => mashupSource.Visualizer.Show(value))))); + } + } +} From 2298bbdccbfecde1e53a6e43138b30c6364c2234 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 13:14:47 +0000 Subject: [PATCH 10/33] Added mashup visualizer --- .../PosteriorOverlay.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/Bonsai.ML.NeuralDecoding.Design/PosteriorOverlay.cs diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorOverlay.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorOverlay.cs new file mode 100644 index 0000000..3c6808c --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorOverlay.cs @@ -0,0 +1,61 @@ +using Bonsai; +using Bonsai.Design; +using Bonsai.Design.Visualizers; +using System; +using System.Collections.Generic; +using OxyPlot.Series; +using OxyPlot; +using Bonsai.ML.Design; + +[assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoding.Design.PosteriorOverlay), + Target = typeof(MashupSource))] + +namespace Bonsai.ML.NeuralDecoding.Design +{ + /// + /// + /// + public class PosteriorOverlay : DialogTypeVisualizer + { + private PosteriorVisualizer visualizer; + private LineSeries lineSeries; + private List data = new(); + + /// + public override void Load(IServiceProvider provider) + { + var service = provider.GetService(typeof(MashupVisualizer)); + visualizer = (PosteriorVisualizer)service; + var plot = visualizer.Plot; + + lineSeries = new LineSeries() + { + Color = OxyColors.Goldenrod + }; + plot.Model.Series.Add(lineSeries); + } + + /// + public override void Show(object value) + { + var position = (double)value; + var capacity = visualizer.Capacity; + while (data.Count >= capacity) + { + data.RemoveAt(0); + } + data.Add(position); + lineSeries.Points.Clear(); + var count = data.Count; + for (int i = 0; i < count; i++) + { + lineSeries.Points.Add(new DataPoint(i, data[i])); + } + } + + /// + public override void Unload() + { + } + } +} \ No newline at end of file From 2ff62fb12f8ce83100360b5c3f4a44e2ea30acd6 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 13:17:51 +0000 Subject: [PATCH 11/33] Exposed methods to specifically update axes --- .../HeatMapSeriesOxyPlotBase.cs | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/Bonsai.ML.Design/HeatMapSeriesOxyPlotBase.cs b/src/Bonsai.ML.Design/HeatMapSeriesOxyPlotBase.cs index 85cee3a..ff410e1 100644 --- a/src/Bonsai.ML.Design/HeatMapSeriesOxyPlotBase.cs +++ b/src/Bonsai.ML.Design/HeatMapSeriesOxyPlotBase.cs @@ -300,7 +300,42 @@ public void UpdateHeatMapSeries(double[,] data) } /// - /// Method to update the heatmap series with new data. + /// Method to update the heatmap x axis. + /// + /// + /// + public void UpdateHeatMapXAxis(double x0, double x1) + { + heatMapSeries.X0 = x0; + heatMapSeries.X1 = x1; + } + + /// + /// Method to update the heatmap y axis. + /// + /// + /// + public void UpdateHeatMapYAxis(double y0, double y1) + { + heatMapSeries.Y0 = y0; + heatMapSeries.Y1 = y1; + } + + /// + /// Method to update the heatmap axes. + /// + /// + /// + /// + /// + public void UpdateHeatMapAxes(double x0, double x1, double y0, double y1) + { + UpdateHeatMapXAxis(x0, x1); + UpdateHeatMapYAxis(y0, y1); + } + + /// + /// Method to update the heatmap series data and axes. /// /// The minimum x value. /// The maximum x value. @@ -309,10 +344,7 @@ public void UpdateHeatMapSeries(double[,] data) /// The data to be displayed. public void UpdateHeatMapSeries(double x0, double x1, double y0, double y1, double[,] data) { - heatMapSeries.X0 = x0; - heatMapSeries.X1 = x1; - heatMapSeries.Y0 = y0; - heatMapSeries.Y1 = y1; + UpdateHeatMapAxes(x0, x1, y0, y1); heatMapSeries.Data = data; } From 792c71eaf537407e3eaf459576615e25f8c23415 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 15:41:37 +0000 Subject: [PATCH 12/33] Added get properties to relay current count and length of data array --- ...UnidimensionalArrayTimeSeriesVisualizer.cs | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs b/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs index bc886f5..25d1146 100644 --- a/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs +++ b/src/Bonsai.ML.Design/UnidimensionalArrayTimeSeriesVisualizer.cs @@ -31,6 +31,16 @@ public class UnidimensionalArrayTimeSeriesVisualizer : DialogTypeVisualizer /// public HeatMapSeriesOxyPlotBase Plot => plot; + /// + /// Gets or sets the current count of data points. + /// + public int CurrentCount { get; set; } + + /// + /// Gets or sets the current length of the data array. + /// + public int CurrentArrayLength { get; set; } + private int _capacity = 100; /// @@ -89,6 +99,7 @@ public override void Show(object value) if (dataList.Count < Capacity) { dataList.Add(array); + CurrentCount = dataList.Count; } else { @@ -99,24 +110,23 @@ public override void Show(object value) dataList.Add(array); } - var shape = (dataList.Count, array.Length); - var mdarray = new double[shape.Item1, shape.Item2]; - for (int i = 0; i < shape.Item1; i++) + if (array.Length != CurrentArrayLength) { - for (int j = 0; j < shape.Item2; j++) + CurrentArrayLength = array.Length; + plot.UpdateHeatMapYAxis(-0.5, CurrentArrayLength - 0.5); + } + + var mdarray = new double[CurrentCount, CurrentArrayLength]; + for (int i = 0; i < CurrentCount; i++) + { + for (int j = 0; j < CurrentArrayLength; j++) { mdarray[i, j] = dataList[i][j]; } } - plot.UpdateHeatMapSeries( - -0.5, - shape.Item1 - 0.5, - -0.5, - shape.Item2 - 0.5, - mdarray - ); - + plot.UpdateHeatMapSeries(mdarray); + plot.UpdateHeatMapXAxis(-0.5, CurrentCount - 0.5); plot.UpdatePlot(); } From 2c217d295d6db79e8c1a1a75d344856204ecaf5c Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 15:42:09 +0000 Subject: [PATCH 13/33] Change position range to value range --- src/Bonsai.ML.NeuralDecoding/Posterior.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Bonsai.ML.NeuralDecoding/Posterior.cs b/src/Bonsai.ML.NeuralDecoding/Posterior.cs index f54be3f..f87b649 100644 --- a/src/Bonsai.ML.NeuralDecoding/Posterior.cs +++ b/src/Bonsai.ML.NeuralDecoding/Posterior.cs @@ -26,9 +26,9 @@ public class Posterior public int ArgMax { get; set; } /// - /// The position range. + /// The value range. /// - public double[] PositionRange { get; set; } + public double[] ValueRange { get; set; } /// /// Initializes a new instance of the class. @@ -42,12 +42,12 @@ public Posterior() /// /// /// - /// - public Posterior(double[] data, int argMax, double[] positionRange) + /// + public Posterior(double[] data, int argMax, double[] valueRange) { Data = data; ArgMax = argMax; - PositionRange = positionRange; + ValueRange = valueRange; } /// @@ -59,14 +59,14 @@ public IObservable Process(IObservable source) { return source.Select(value => { var posterior = value[0]; - var placeBinCenters = value[1]; + var valueCenters = value[1]; var data = (double[])PythonHelper.ConvertPythonObjectToCSharp(posterior); var argMax = Array.IndexOf(data, data.Max()); - var positionRange = (double[])PythonHelper.ConvertPythonObjectToCSharp(placeBinCenters); + var valueRange = (double[])PythonHelper.ConvertPythonObjectToCSharp(valueCenters); return new Posterior( data, argMax, - positionRange + ValueRange ); }); } From c353efe38f58de7f0975e34c620bbc44aca8d922 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 16:19:47 +0000 Subject: [PATCH 14/33] Added default value range mapping --- src/Bonsai.ML.NeuralDecoding/Posterior.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Bonsai.ML.NeuralDecoding/Posterior.cs b/src/Bonsai.ML.NeuralDecoding/Posterior.cs index f87b649..ef1d619 100644 --- a/src/Bonsai.ML.NeuralDecoding/Posterior.cs +++ b/src/Bonsai.ML.NeuralDecoding/Posterior.cs @@ -26,7 +26,7 @@ public class Posterior public int ArgMax { get; set; } /// - /// The value range. + /// An optional mapping of the data to a value range. /// public double[] ValueRange { get; set; } @@ -43,11 +43,11 @@ public Posterior() /// /// /// - public Posterior(double[] data, int argMax, double[] valueRange) + public Posterior(double[] data, int argMax, double[] valueRange = null) { Data = data; ArgMax = argMax; - ValueRange = valueRange; + ValueRange = valueRange ?? Enumerable.Range(0, data.Length).Select(i => (double)i).ToArray(); } /// @@ -66,7 +66,7 @@ public IObservable Process(IObservable source) return new Posterior( data, argMax, - ValueRange + valueRange ); }); } From c0b8aa597b4ccf19872e13291747a38459fbeafb Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 16:20:06 +0000 Subject: [PATCH 15/33] Changed to true position overlay --- ...eriorOverlay.cs => TruePositionOverlay.cs} | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) rename src/Bonsai.ML.NeuralDecoding.Design/{PosteriorOverlay.cs => TruePositionOverlay.cs} (77%) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorOverlay.cs b/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs similarity index 77% rename from src/Bonsai.ML.NeuralDecoding.Design/PosteriorOverlay.cs rename to src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs index 3c6808c..1173432 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorOverlay.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using OxyPlot.Series; using OxyPlot; -using Bonsai.ML.Design; [assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoding.Design.PosteriorOverlay), Target = typeof(MashupSource))] @@ -13,7 +12,7 @@ namespace Bonsai.ML.NeuralDecoding.Design { /// - /// + /// Class that overlays the true /// public class PosteriorOverlay : DialogTypeVisualizer { @@ -30,26 +29,34 @@ public override void Load(IServiceProvider provider) lineSeries = new LineSeries() { + Title = "True Position", Color = OxyColors.Goldenrod }; + plot.Model.Series.Add(lineSeries); + plot.Model.DefaultYAxis.Title = "Position"; } /// public override void Show(object value) { var position = (double)value; - var capacity = visualizer.Capacity; - while (data.Count >= capacity) + if (position == double.NaN) + { + return; + } + + var currentCount = visualizer.CurrentCount; + while (data.Count > currentCount) { data.RemoveAt(0); } - data.Add(position); lineSeries.Points.Clear(); + var count = data.Count; for (int i = 0; i < count; i++) { - lineSeries.Points.Add(new DataPoint(i, data[i])); + lineSeries.Points.Add(new DataPoint(currentCount - count + i + 1, data[i])); } } From a0825a3fd9f54e916df03cd9b14bdf1f32c3383a Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 16:21:17 +0000 Subject: [PATCH 16/33] Updated visualize method to correctly merge source and mashup streams --- .../PosteriorVisualizer.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs index fb19852..3018477 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs @@ -85,12 +85,15 @@ public override IObservable Visualize(IObservable> s return source; } - return Observable.Merge(source.SelectMany(xs => xs.Do( - value => Show(value), - () => visualizerControl.BeginInvoke(SequenceCompleted))), - Observable.Merge(MashupSources.Select( - mashupSource => mashupSource.Visualizer.Visualize(mashupSource.Source.Output, provider) - .Do(value => mashupSource.Visualizer.Show(value))))); + var mergedSource = source.SelectMany(xs => xs + .ObserveOn(visualizerControl) + .Do(value => Show(value))); + + var mashupSourceStreams = Observable.Merge( + MashupSources.Select(mashupSource => + mashupSource.Visualizer.Visualize(mashupSource.Source.Output, provider))); + + return Observable.Merge(mergedSource, mashupSourceStreams); } } } From 5009c50240c5dfaa288c990709052f88dbd9a2b4 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 16:22:00 +0000 Subject: [PATCH 17/33] Call base unload method --- src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs index 3018477..bec2a3c 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs @@ -75,6 +75,7 @@ public override void Show(object value) public override void Unload() { visualizer.Unload(); + base.Unload(); } /// From df9db80090139d3b5f27055205932bb8ab316b19 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 16:22:50 +0000 Subject: [PATCH 18/33] Added null checks when showing data --- .../PosteriorVisualizer.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs index bec2a3c..5c83b42 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs @@ -22,7 +22,7 @@ public class PosteriorVisualizer : MashupVisualizer private UnidimensionalArrayTimeSeriesVisualizer visualizer; private LineSeries lineSeries; private List argMaxVals = new(); - private double[] positionRange = null; + private double[] valueRange = null; /// /// Gets the underlying heatmap plot. @@ -53,6 +53,16 @@ public override void Load(IServiceProvider provider) public override void Show(object value) { Posterior posterior = (Posterior)value; + if (posterior == null) + { + return; + } + + if (valueRange == null) + { + valueRange = posterior.ValueRange; + } + var data = posterior.Data; var argMax = posterior.ArgMax; var positionRange = posterior.PositionRange; From 1bd13c6ee1eb76f296dcca832c4e4d5d6ed5bc75 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 16:23:05 +0000 Subject: [PATCH 19/33] Fixed missing XML comment --- src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs index 5c83b42..0171aeb 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs @@ -30,7 +30,7 @@ public class PosteriorVisualizer : MashupVisualizer public HeatMapSeriesOxyPlotBase Plot => visualizer.Plot; /// - /// Gets the + /// Gets the capacity of the visualizer. /// public int Capacity => visualizer.Capacity; From 7580f4bec65658e14d7c847a199ee883e383c860 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 16:23:33 +0000 Subject: [PATCH 20/33] Added public getting to retrieve current count information --- src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs index 0171aeb..cfbd715 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs @@ -34,6 +34,11 @@ public class PosteriorVisualizer : MashupVisualizer /// public int Capacity => visualizer.Capacity; + /// + /// Gets the current count of data points. + /// + public int CurrentCount => visualizer.CurrentCount; + /// public override void Load(IServiceProvider provider) { From 1c8d49dab57c35316bc14de4e45c1b73d87c3b3f Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 16:24:08 +0000 Subject: [PATCH 21/33] Initialize heatmap plot with specific properties --- .../PosteriorVisualizer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs index cfbd715..aff1155 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs @@ -42,11 +42,17 @@ public class PosteriorVisualizer : MashupVisualizer /// public override void Load(IServiceProvider provider) { - visualizer = new UnidimensionalArrayTimeSeriesVisualizer(); + visualizer = new UnidimensionalArrayTimeSeriesVisualizer() + { + PaletteSelectedIndex = 1, + RenderMethodSelectedIndex = 1 + }; + visualizer.Load(provider); lineSeries = new LineSeries() { + Title = "Maximum Posterior", Color = OxyColors.SkyBlue }; visualizer.Plot.Model.Series.Add(lineSeries); From 9e37e236f064ae18eff0c796fbef2fd32b4927f6 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 16:24:38 +0000 Subject: [PATCH 22/33] Update heatmap to display data correctly in show method --- .../PosteriorVisualizer.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs index aff1155..379100d 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs @@ -76,20 +76,23 @@ public override void Show(object value) var data = posterior.Data; var argMax = posterior.ArgMax; - var positionRange = posterior.PositionRange; - var capacity = visualizer.Capacity; - while (argMaxVals.Count >= capacity) + + while (argMaxVals.Count >= Capacity) { argMaxVals.RemoveAt(0); } - argMaxVals.Add(argMax); + + argMaxVals.Add(valueRange[argMax]); lineSeries.Points.Clear(); var count = argMaxVals.Count; + for (int i = 0; i < count; i++) { lineSeries.Points.Add(new DataPoint(i, argMaxVals[i])); } + visualizer.Show(data); + Plot.UpdateHeatMapYAxis(valueRange[0], valueRange[valueRange.Length - 1]); } /// From 83e76490a568741596d5fe478124249b3b7f73a9 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 17:18:17 +0000 Subject: [PATCH 23/33] Updated visualize function to make the correct call to the observable visualize --- .../PosteriorVisualizer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs index 379100d..efc4e72 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs @@ -111,14 +111,16 @@ public override IObservable Visualize(IObservable> s } var mergedSource = source.SelectMany(xs => xs - .ObserveOn(visualizerControl) .Do(value => Show(value))); var mashupSourceStreams = Observable.Merge( MashupSources.Select(mashupSource => - mashupSource.Visualizer.Visualize(mashupSource.Source.Output, provider))); + mashupSource.Source.Output.SelectMany(xs => xs + .Do(value => mashupSource.Visualizer.Show(value))))); + + return Observable.Merge(mergedSource, mashupSourceStreams) + .ObserveOn(visualizerControl); - return Observable.Merge(mergedSource, mashupSourceStreams); } } } From 0ce5f2236f23742f7a225ec4e773010b3c54578e Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 17:18:58 +0000 Subject: [PATCH 24/33] Correctly adding values to the series --- src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs b/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs index 1173432..8403ab4 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs @@ -46,6 +46,8 @@ public override void Show(object value) return; } + data.Add(position); + var currentCount = visualizer.CurrentCount; while (data.Count > currentCount) { @@ -56,7 +58,7 @@ public override void Show(object value) var count = data.Count; for (int i = 0; i < count; i++) { - lineSeries.Points.Add(new DataPoint(currentCount - count + i + 1, data[i])); + lineSeries.Points.Add(new DataPoint(currentCount - count + i, data[i])); } } From a1e00a3c993292422179634d4f3e65f468816d35 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 17:20:46 +0000 Subject: [PATCH 25/33] Updated name to true position overlay --- src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs b/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs index 8403ab4..8428544 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs @@ -6,7 +6,7 @@ using OxyPlot.Series; using OxyPlot; -[assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoding.Design.PosteriorOverlay), +[assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoding.Design.TruePositionOverlay), Target = typeof(MashupSource))] namespace Bonsai.ML.NeuralDecoding.Design @@ -14,7 +14,7 @@ namespace Bonsai.ML.NeuralDecoding.Design /// /// Class that overlays the true /// - public class PosteriorOverlay : DialogTypeVisualizer + public class TruePositionOverlay : DialogTypeVisualizer { private PosteriorVisualizer visualizer; private LineSeries lineSeries; From 8212d27e1a7929bedb6e83a9ee3560446a32665c Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 17:21:13 +0000 Subject: [PATCH 26/33] Added method to update y axis title on load --- .../TruePositionOverlay.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs b/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs index 8428544..aceb7b5 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs @@ -32,9 +32,14 @@ public override void Load(IServiceProvider provider) Title = "True Position", Color = OxyColors.Goldenrod }; - + plot.Model.Series.Add(lineSeries); - plot.Model.DefaultYAxis.Title = "Position"; + + plot.Model.Updated += (sender, e) => + { + plot.Model.DefaultYAxis.Title = "Position"; + }; + } /// From 269a8b573e0f0d7d7f81ee57fac76501966886ac Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 15 Nov 2024 17:23:49 +0000 Subject: [PATCH 27/33] Added method to update y axis title --- .../TruePositionOverlay.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs b/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs index aceb7b5..98f5831 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs +++ b/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using OxyPlot.Series; using OxyPlot; +using Bonsai.ML.Design; [assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoding.Design.TruePositionOverlay), Target = typeof(MashupSource))] @@ -19,13 +20,15 @@ public class TruePositionOverlay : DialogTypeVisualizer private PosteriorVisualizer visualizer; private LineSeries lineSeries; private List data = new(); + private string defaultYAxisTitle; + private HeatMapSeriesOxyPlotBase plot; /// public override void Load(IServiceProvider provider) { var service = provider.GetService(typeof(MashupVisualizer)); visualizer = (PosteriorVisualizer)service; - var plot = visualizer.Plot; + plot = visualizer.Plot; lineSeries = new LineSeries() { @@ -37,6 +40,7 @@ public override void Load(IServiceProvider provider) plot.Model.Updated += (sender, e) => { + defaultYAxisTitle = plot.Model.DefaultYAxis.Title; plot.Model.DefaultYAxis.Title = "Position"; }; @@ -69,7 +73,8 @@ public override void Show(object value) /// public override void Unload() - { + { + plot.Model.DefaultYAxis.Title = defaultYAxisTitle; } } } \ No newline at end of file From a2729f3fa1b004b896a4355d00c08a69c5e825df Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Tue, 19 Nov 2024 11:53:05 +0000 Subject: [PATCH 28/33] Changed name to decoder --- Bonsai.ML.sln | 4 +-- .../Bonsai.ML.NeuralDecoder.Design.csproj} | 9 ++++--- .../PosteriorVisualizer.cs | 27 ++++++++++++++++--- .../Properties/launchSettings.json | 0 .../TruePositionOverlay.cs | 16 ++++++++--- .../Bonsai.ML.NeuralDecoder.csproj} | 7 +++-- .../Posterior.cs | 9 ++++++- .../Properties/launchSettings.json | 0 8 files changed, 55 insertions(+), 17 deletions(-) rename src/{Bonsai.ML.NeuralDecoding.Design/Bonsai.ML.NeuralDecoding.Design.csproj => Bonsai.ML.NeuralDecoder.Design/Bonsai.ML.NeuralDecoder.Design.csproj} (53%) rename src/{Bonsai.ML.NeuralDecoding.Design => Bonsai.ML.NeuralDecoder.Design}/PosteriorVisualizer.cs (81%) rename src/{Bonsai.ML.NeuralDecoding.Design => Bonsai.ML.NeuralDecoder.Design}/Properties/launchSettings.json (100%) rename src/{Bonsai.ML.NeuralDecoding.Design => Bonsai.ML.NeuralDecoder.Design}/TruePositionOverlay.cs (77%) rename src/{Bonsai.ML.NeuralDecoding/Bonsai.ML.NeuralDecoding.csproj => Bonsai.ML.NeuralDecoder/Bonsai.ML.NeuralDecoder.csproj} (73%) rename src/{Bonsai.ML.NeuralDecoding => Bonsai.ML.NeuralDecoder}/Posterior.cs (87%) rename src/{Bonsai.ML.NeuralDecoding => Bonsai.ML.NeuralDecoder}/Properties/launchSettings.json (100%) diff --git a/Bonsai.ML.sln b/Bonsai.ML.sln index 14d5549..bf116cc 100644 --- a/Bonsai.ML.sln +++ b/Bonsai.ML.sln @@ -30,9 +30,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.LinearDynamicalSy EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.HiddenMarkovModels.Design", "src\Bonsai.ML.HiddenMarkovModels.Design\Bonsai.ML.HiddenMarkovModels.Design.csproj", "{FC395DDC-62A4-4E14-A198-272AB05B33C7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.NeuralDecoding", "src\Bonsai.ML.NeuralDecoding\Bonsai.ML.NeuralDecoding.csproj", "{CE083548-26CB-4CF6-AE51-D7E32AE7377A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.NeuralDecoder", "src\Bonsai.ML.NeuralDecoder\Bonsai.ML.NeuralDecoder.csproj", "{CE083548-26CB-4CF6-AE51-D7E32AE7377A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.NeuralDecoding.Design", "src\Bonsai.ML.NeuralDecoding.Design\Bonsai.ML.NeuralDecoding.Design.csproj", "{D2CECE2F-CE7C-41BC-9888-EA53493D64D6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.NeuralDecoder.Design", "src\Bonsai.ML.NeuralDecoder.Design\Bonsai.ML.NeuralDecoder.Design.csproj", "{D2CECE2F-CE7C-41BC-9888-EA53493D64D6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Bonsai.ML.NeuralDecoding.Design/Bonsai.ML.NeuralDecoding.Design.csproj b/src/Bonsai.ML.NeuralDecoder.Design/Bonsai.ML.NeuralDecoder.Design.csproj similarity index 53% rename from src/Bonsai.ML.NeuralDecoding.Design/Bonsai.ML.NeuralDecoding.Design.csproj rename to src/Bonsai.ML.NeuralDecoder.Design/Bonsai.ML.NeuralDecoder.Design.csproj index 48c4250..edc72c0 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/Bonsai.ML.NeuralDecoding.Design.csproj +++ b/src/Bonsai.ML.NeuralDecoder.Design/Bonsai.ML.NeuralDecoder.Design.csproj @@ -1,17 +1,18 @@ - Bonsai.ML.NeuralDecoding.Design - A Bonsai package for visualizing decoding neural activity. - Bonsai Rx ML Neural Decoding Design + Bonsai.ML.NeuralDecoder.Design + A Bonsai package for visualizing decoded neural activity. + Bonsai Rx ML Neural Decoder Design net472 true + - + \ No newline at end of file diff --git a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs b/src/Bonsai.ML.NeuralDecoder.Design/PosteriorVisualizer.cs similarity index 81% rename from src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs rename to src/Bonsai.ML.NeuralDecoder.Design/PosteriorVisualizer.cs index efc4e72..175b60a 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/PosteriorVisualizer.cs +++ b/src/Bonsai.ML.NeuralDecoder.Design/PosteriorVisualizer.cs @@ -8,11 +8,12 @@ using System.Windows.Forms; using System.Reactive.Linq; using System.Linq; +using System.Xml.Serialization; -[assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoding.Design.PosteriorVisualizer), - Target = typeof(Bonsai.ML.NeuralDecoding.Posterior))] +[assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoder.Design.PosteriorVisualizer), + Target = typeof(Bonsai.ML.NeuralDecoder.Posterior))] -namespace Bonsai.ML.NeuralDecoding.Design +namespace Bonsai.ML.NeuralDecoder.Design { /// /// Provides a mashup visualizer to display the posterior of the neural decoder. @@ -22,6 +23,7 @@ public class PosteriorVisualizer : MashupVisualizer private UnidimensionalArrayTimeSeriesVisualizer visualizer; private LineSeries lineSeries; private List argMaxVals = new(); + private double[] valueCenters = null; private double[] valueRange = null; /// @@ -39,6 +41,18 @@ public class PosteriorVisualizer : MashupVisualizer /// public int CurrentCount => visualizer.CurrentCount; + /// + /// Gets the values of the Y axis. + /// + [XmlIgnore] + public double[] ValueCenters => valueCenters; + + /// + /// Gets the range of values mapped to the values of the Y axis. + /// + [XmlIgnore] + public double[] ValueRange => valueRange; + /// public override void Load(IServiceProvider provider) { @@ -69,6 +83,11 @@ public override void Show(object value) return; } + if (valueCenters == null) + { + valueCenters = posterior.ValueCenters; + } + if (valueRange == null) { valueRange = posterior.ValueRange; @@ -82,7 +101,7 @@ public override void Show(object value) argMaxVals.RemoveAt(0); } - argMaxVals.Add(valueRange[argMax]); + argMaxVals.Add(valueCenters[argMax]); lineSeries.Points.Clear(); var count = argMaxVals.Count; diff --git a/src/Bonsai.ML.NeuralDecoding.Design/Properties/launchSettings.json b/src/Bonsai.ML.NeuralDecoder.Design/Properties/launchSettings.json similarity index 100% rename from src/Bonsai.ML.NeuralDecoding.Design/Properties/launchSettings.json rename to src/Bonsai.ML.NeuralDecoder.Design/Properties/launchSettings.json diff --git a/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs b/src/Bonsai.ML.NeuralDecoder.Design/TruePositionOverlay.cs similarity index 77% rename from src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs rename to src/Bonsai.ML.NeuralDecoder.Design/TruePositionOverlay.cs index 98f5831..103c726 100644 --- a/src/Bonsai.ML.NeuralDecoding.Design/TruePositionOverlay.cs +++ b/src/Bonsai.ML.NeuralDecoder.Design/TruePositionOverlay.cs @@ -7,10 +7,10 @@ using OxyPlot; using Bonsai.ML.Design; -[assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoding.Design.TruePositionOverlay), - Target = typeof(MashupSource))] +[assembly: TypeVisualizer(typeof(Bonsai.ML.NeuralDecoder.Design.TruePositionOverlay), + Target = typeof(MashupSource))] -namespace Bonsai.ML.NeuralDecoding.Design +namespace Bonsai.ML.NeuralDecoder.Design { /// /// Class that overlays the true @@ -58,6 +58,9 @@ public override void Show(object value) data.Add(position); var currentCount = visualizer.CurrentCount; + var valueRange = visualizer.ValueRange; + var valueCenters = visualizer.ValueCenters; + while (data.Count > currentCount) { data.RemoveAt(0); @@ -67,7 +70,12 @@ public override void Show(object value) var count = data.Count; for (int i = 0; i < count; i++) { - lineSeries.Points.Add(new DataPoint(currentCount - count + i, data[i])); + var closestIndex = Array.BinarySearch(valueRange, data[i]); + if (closestIndex < 0) + { + closestIndex = ~closestIndex; + } + lineSeries.Points.Add(new DataPoint(currentCount - count + i, valueCenters[closestIndex])); } } diff --git a/src/Bonsai.ML.NeuralDecoding/Bonsai.ML.NeuralDecoding.csproj b/src/Bonsai.ML.NeuralDecoder/Bonsai.ML.NeuralDecoder.csproj similarity index 73% rename from src/Bonsai.ML.NeuralDecoding/Bonsai.ML.NeuralDecoding.csproj rename to src/Bonsai.ML.NeuralDecoder/Bonsai.ML.NeuralDecoder.csproj index 4c5f5fb..01805ef 100644 --- a/src/Bonsai.ML.NeuralDecoding/Bonsai.ML.NeuralDecoding.csproj +++ b/src/Bonsai.ML.NeuralDecoder/Bonsai.ML.NeuralDecoder.csproj @@ -1,8 +1,8 @@ - Bonsai.ML.NeuralDecoding + Bonsai.ML.NeuralDecoder A Bonsai package for decoding neural activity. - Bonsai Rx ML Neural Decoding + Bonsai Rx ML Neural Decoder net472;netstandard2.0 @@ -12,4 +12,7 @@ + + + \ No newline at end of file diff --git a/src/Bonsai.ML.NeuralDecoding/Posterior.cs b/src/Bonsai.ML.NeuralDecoder/Posterior.cs similarity index 87% rename from src/Bonsai.ML.NeuralDecoding/Posterior.cs rename to src/Bonsai.ML.NeuralDecoder/Posterior.cs index ef1d619..bbc8051 100644 --- a/src/Bonsai.ML.NeuralDecoding/Posterior.cs +++ b/src/Bonsai.ML.NeuralDecoder/Posterior.cs @@ -5,7 +5,7 @@ using Python.Runtime; using Bonsai.ML.Python; -namespace Bonsai.ML.NeuralDecoding; +namespace Bonsai.ML.NeuralDecoder; /// /// Transforms the input sequence of Python objects into a sequence of instances. @@ -30,6 +30,11 @@ public class Posterior /// public double[] ValueRange { get; set; } + /// + /// The value centers. + /// + public double[] ValueCenters { get; set; } + /// /// Initializes a new instance of the class. /// @@ -48,6 +53,8 @@ public Posterior(double[] data, int argMax, double[] valueRange = null) Data = data; ArgMax = argMax; ValueRange = valueRange ?? Enumerable.Range(0, data.Length).Select(i => (double)i).ToArray(); + var step = (valueRange[valueRange.Length-1] - valueRange[0]) / data.Length; + ValueCenters = Enumerable.Range(0, data.Length).Select(i => i * step).ToArray(); } /// diff --git a/src/Bonsai.ML.NeuralDecoding/Properties/launchSettings.json b/src/Bonsai.ML.NeuralDecoder/Properties/launchSettings.json similarity index 100% rename from src/Bonsai.ML.NeuralDecoding/Properties/launchSettings.json rename to src/Bonsai.ML.NeuralDecoder/Properties/launchSettings.json From 1a820c3e8875d2718b68750a90b5ff65fcfb4c2a Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Tue, 19 Nov 2024 11:53:19 +0000 Subject: [PATCH 29/33] Added include workflows --- .../ImportDecoderModule.bonsai | 28 ++++ .../LoadClusterlessSpikeDecoder.bonsai | 105 ++++++++++++++ .../LoadDecodedResultsFromFile.bonsai | 133 ++++++++++++++++++ .../LoadSortedSpikeDecoder.bonsai | 105 ++++++++++++++ src/Bonsai.ML.NeuralDecoder/RunDecoder.bonsai | 89 ++++++++++++ 5 files changed, 460 insertions(+) create mode 100644 src/Bonsai.ML.NeuralDecoder/ImportDecoderModule.bonsai create mode 100644 src/Bonsai.ML.NeuralDecoder/LoadClusterlessSpikeDecoder.bonsai create mode 100644 src/Bonsai.ML.NeuralDecoder/LoadDecodedResultsFromFile.bonsai create mode 100644 src/Bonsai.ML.NeuralDecoder/LoadSortedSpikeDecoder.bonsai create mode 100644 src/Bonsai.ML.NeuralDecoder/RunDecoder.bonsai diff --git a/src/Bonsai.ML.NeuralDecoder/ImportDecoderModule.bonsai b/src/Bonsai.ML.NeuralDecoder/ImportDecoderModule.bonsai new file mode 100644 index 0000000..d266b2f --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoder/ImportDecoderModule.bonsai @@ -0,0 +1,28 @@ + + + + + + Source1 + + + + from bayesian_neural_decoder import * + + + + DecoderModule + + + + + + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.NeuralDecoder/LoadClusterlessSpikeDecoder.bonsai b/src/Bonsai.ML.NeuralDecoder/LoadClusterlessSpikeDecoder.bonsai new file mode 100644 index 0000000..af51655 --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoder/LoadClusterlessSpikeDecoder.bonsai @@ -0,0 +1,105 @@ + + + + + + + + + + decoder + + + + decoder + + + Name + + + + + + FileName + ../../../datasets/decoder_data/clusterless_spike_decoder.pkl + + + + + + + + + {0}=ClusterlessSpikeDecoder.load("{1}") + Item1,Item2 + + + + + + + + DecoderModule + + + + + + + + + + + + + + + + + + + DecoderModule + + + + + + + + + hmm + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.NeuralDecoder/LoadDecodedResultsFromFile.bonsai b/src/Bonsai.ML.NeuralDecoder/LoadDecodedResultsFromFile.bonsai new file mode 100644 index 0000000..878a800 --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoder/LoadDecodedResultsFromFile.bonsai @@ -0,0 +1,133 @@ + + + + + + + + + FileName + ../../../datasets/decoder_data/clusterless_spike_decoding_results.pkl + + + + + + data=DataLoader.load(results_path="{0}") + + + + + + + + + DecoderModule + + + + + + + + + + + + + + Iterate + + + + + + + + PT0S + PT0.1S + + + + + + + DecoderModule + + + + + + + + + iterator = DataIterator(data) + + + + + + + + + + DecoderModule + + + + + + + + + iterator.next() + + + + it[0] + + + new(it[0] as Position, it[1] as Spikes, it[2] as Predictions, it[3] as PositionBins) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.NeuralDecoder/LoadSortedSpikeDecoder.bonsai b/src/Bonsai.ML.NeuralDecoder/LoadSortedSpikeDecoder.bonsai new file mode 100644 index 0000000..908be97 --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoder/LoadSortedSpikeDecoder.bonsai @@ -0,0 +1,105 @@ + + + + + + + + + + decoder + + + + decoder + + + Name + + + + + + FileName + ../../../datasets/decoder_data/sorted_spike_decoder.pkl + + + + + + + + + {0}=SortedSpikeDecoder.load("{1}") + Item1,Item2 + + + + + + + + DecoderModule + + + + + + + + + + + + + + + + + + + DecoderModule + + + + + + + + + hmm + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.NeuralDecoder/RunDecoder.bonsai b/src/Bonsai.ML.NeuralDecoder/RunDecoder.bonsai new file mode 100644 index 0000000..4416d29 --- /dev/null +++ b/src/Bonsai.ML.NeuralDecoder/RunDecoder.bonsai @@ -0,0 +1,89 @@ + + + + + + Source1 + + + + + + DecoderModule + + + + + + + + + spikes + + + + + + + decoder + + + Name + + + + + + {0}.decode(spikes) + Item2 + + + + + + + + DecoderModule + + + + + + + + + decoder.decode(spikes) + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f9a988308d6b6571b9126792e4265f0d8eecdc46 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Tue, 19 Nov 2024 12:13:17 +0000 Subject: [PATCH 30/33] Removed preview from package version --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 68e1811..e9805de 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,7 +15,7 @@ true git 0.4.0 - preview241114 + 12.0 From 5b53766ddd71699e215bcfe9a0387c8c4a308b04 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Tue, 19 Nov 2024 15:13:28 +0000 Subject: [PATCH 31/33] Updated README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cd87550..f3998f8 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,12 @@ Facilitates inference using Hidden Markov Models (HMMs). It interfaces with the ### Bonsai.ML.HiddenMarkovModels.Design Visualizers and editor features for the HiddenMarkovModels package. +### Bonsai.ML.NeuralDecoder +Enables online neural decoding of spike sorted or clusterless neural activity. It interfaces with the [bayesian-neural-decoder](https://github.com/ncguilbeault/bayesian-neural-decoder) package using the [Bonsai - Python Scripting](https://github.com/bonsai-rx/python-scripting) library. The neural decoder consists of a bayesian state-space point process model to decode sorted spikes or clusterless neural activity. The technical details describing the models implementation are described in: Denovellis, E.L., Gillespie, A.K., Coulter, M.E., et al. Hippocampal replay of experience at real-world speeds. eLife 10, e64505 (2021). https://doi.org/10.7554/eLife.64505. + +### Bonsai.ML.NeuralDecoder.Design +Visualizers for the Neural Decoder package. + > [!NOTE] > Bonsai.ML packages can be installed through Bonsai's integrated package manager and are generally ready for immediate use. However, some packages may require additional installation steps. Refer to the specific package section for detailed installation guides and documentation. From 7d3d9bfb76299215ed1bd009fe53799bf03188ff Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Tue, 19 Nov 2024 15:13:41 +0000 Subject: [PATCH 32/33] Added getting started guide for neural decoder package --- .../NeuralDecoder/nd-getting-started.md | 26 +++++++++++++++++++ docs/articles/toc.yml | 5 +++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 docs/articles/NeuralDecoder/nd-getting-started.md diff --git a/docs/articles/NeuralDecoder/nd-getting-started.md b/docs/articles/NeuralDecoder/nd-getting-started.md new file mode 100644 index 0000000..1e098c1 --- /dev/null +++ b/docs/articles/NeuralDecoder/nd-getting-started.md @@ -0,0 +1,26 @@ +# Getting Started + +To get started using the Bonsai.ML.NeuralDecoder package, please read below or get started on the demo in the [Neural Decoding example guide](~/examples/README.md). + +## Algorithm + +The neural decoder consists of a bayesian state space point process model. With this model, latent variables such as an animals position can be decoded from neural activity. To read more about the theory behind the model and how the algorithm works, we refer the reader to: Denovellis, E.L., Gillespie, A.K., Coulter, M.E., et al. Hippocampal replay of experience at real-world speeds. eLife 10, e64505 (2021). https://doi.org/10.7554/eLife.64505. + +## Installation + +### Python + +To install the python package needed to use the package, run the following: + +``` +cd \path\to\examples\NeuralDecoding\PositionDecodingFromHippocampus +python -m venv .venv +.\.venv\Scripts\activate +pip install git+https://github.com/ncguilbeault/bayesian-neural-decoder.git +``` + +You can test whether the installation was successful by launching python and running + +```python +import bayesian_neural_decoder +``` diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml index e22b0b8..f2bdfb6 100644 --- a/docs/articles/toc.yml +++ b/docs/articles/toc.yml @@ -13,4 +13,7 @@ - name: Overview href: HiddenMarkovModels/hmm-overview.md - name: Getting Started - href: HiddenMarkovModels/hmm-getting-started.md \ No newline at end of file + href: HiddenMarkovModels/hmm-getting-started.md +- name: NeuralDecoder +- name: Getting Started + href: NeuralDecoder/nd-getting-started.md \ No newline at end of file From 1d07ed1f585379c5f781965c4810549bc2c662e6 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Wed, 20 Nov 2024 14:08:09 +0000 Subject: [PATCH 33/33] Add documentation to include workflows --- src/Bonsai.ML.NeuralDecoder/ImportDecoderModule.bonsai | 1 + .../LoadClusterlessSpikeDecoder.bonsai | 5 +++-- .../LoadDecodedResultsFromFile.bonsai | 5 +++-- src/Bonsai.ML.NeuralDecoder/LoadSortedSpikeDecoder.bonsai | 5 +++-- .../{RunDecoder.bonsai => PerformInference.bonsai} | 3 ++- 5 files changed, 12 insertions(+), 7 deletions(-) rename src/Bonsai.ML.NeuralDecoder/{RunDecoder.bonsai => PerformInference.bonsai} (93%) diff --git a/src/Bonsai.ML.NeuralDecoder/ImportDecoderModule.bonsai b/src/Bonsai.ML.NeuralDecoder/ImportDecoderModule.bonsai index d266b2f..5b9c1ba 100644 --- a/src/Bonsai.ML.NeuralDecoder/ImportDecoderModule.bonsai +++ b/src/Bonsai.ML.NeuralDecoder/ImportDecoderModule.bonsai @@ -4,6 +4,7 @@ xmlns:py="clr-namespace:Bonsai.Scripting.Python;assembly=Bonsai.Scripting.Python" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns="https://bonsai-rx.org/2018/workflow"> + Import the Bayesian neural decoder module. diff --git a/src/Bonsai.ML.NeuralDecoder/LoadClusterlessSpikeDecoder.bonsai b/src/Bonsai.ML.NeuralDecoder/LoadClusterlessSpikeDecoder.bonsai index af51655..6d7dec8 100644 --- a/src/Bonsai.ML.NeuralDecoder/LoadClusterlessSpikeDecoder.bonsai +++ b/src/Bonsai.ML.NeuralDecoder/LoadClusterlessSpikeDecoder.bonsai @@ -7,10 +7,11 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:py="clr-namespace:Bonsai.Scripting.Python;assembly=Bonsai.Scripting.Python" xmlns="https://bonsai-rx.org/2018/workflow"> + Load a pickle object into an instance of the clusterless spike decoder class and set it to the referenced python variable. - + @@ -24,7 +25,7 @@ Name - + FileName diff --git a/src/Bonsai.ML.NeuralDecoder/LoadDecodedResultsFromFile.bonsai b/src/Bonsai.ML.NeuralDecoder/LoadDecodedResultsFromFile.bonsai index 878a800..878e34a 100644 --- a/src/Bonsai.ML.NeuralDecoder/LoadDecodedResultsFromFile.bonsai +++ b/src/Bonsai.ML.NeuralDecoder/LoadDecodedResultsFromFile.bonsai @@ -7,6 +7,7 @@ xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" xmlns="https://bonsai-rx.org/2018/workflow"> + Loads the decoded results from a pickle file and emits the animals position, spikes, predictions, and position bins at a specified sampling frequency. @@ -40,14 +41,14 @@ - + Iterate - + diff --git a/src/Bonsai.ML.NeuralDecoder/LoadSortedSpikeDecoder.bonsai b/src/Bonsai.ML.NeuralDecoder/LoadSortedSpikeDecoder.bonsai index 908be97..9add7c7 100644 --- a/src/Bonsai.ML.NeuralDecoder/LoadSortedSpikeDecoder.bonsai +++ b/src/Bonsai.ML.NeuralDecoder/LoadSortedSpikeDecoder.bonsai @@ -7,10 +7,11 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:py="clr-namespace:Bonsai.Scripting.Python;assembly=Bonsai.Scripting.Python" xmlns="https://bonsai-rx.org/2018/workflow"> + Load a pickle object into an instance of the sorted spike decoder class and set it to the referenced python variable. - + @@ -24,7 +25,7 @@ Name - + FileName diff --git a/src/Bonsai.ML.NeuralDecoder/RunDecoder.bonsai b/src/Bonsai.ML.NeuralDecoder/PerformInference.bonsai similarity index 93% rename from src/Bonsai.ML.NeuralDecoder/RunDecoder.bonsai rename to src/Bonsai.ML.NeuralDecoder/PerformInference.bonsai index 4416d29..cf07149 100644 --- a/src/Bonsai.ML.NeuralDecoder/RunDecoder.bonsai +++ b/src/Bonsai.ML.NeuralDecoder/PerformInference.bonsai @@ -6,6 +6,7 @@ xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:p2="clr-namespace:Bonsai.ML.NeuralDecoder;assembly=Bonsai.ML.NeuralDecoder" xmlns="https://bonsai-rx.org/2018/workflow"> + Performs inference using the Bayesian neural decoder. @@ -28,7 +29,7 @@ - + decoder