From 936b229904864edbb5d60658f7edbad0da6600ec Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 8 Mar 2024 15:28:14 +0000 Subject: [PATCH 01/14] Updated main.py and added LR sub package --- .../LinearRegression/CreateKFModel.bonsai | 121 ++++++++++++++ .../LinearRegression/KFModelParameters.cs | 150 ++++++++++++++++++ .../LinearRegression/PerformInference.bonsai | 147 +++++++++++++++++ .../SerializeToJson.cs | 5 + src/Bonsai.ML.LinearDynamicalSystems/main.py | 71 +++++---- 5 files changed, 463 insertions(+), 31 deletions(-) create mode 100644 src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai create mode 100644 src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs create mode 100644 src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/PerformInference.bonsai diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai new file mode 100644 index 00000000..ce004666 --- /dev/null +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai @@ -0,0 +1,121 @@ + + + + + + + + + + model + + + + model + + + Name + + + Source1 + + + + + + + + + 25 + 2 + + + + + + + + + + {0} = KalmanFilterLinearRegression({1}) + it.Item1, it.Item2 + + + + + + + + LDSModule + + + + + + + + + model = KalmanFilterLinearRegression() + + + + + + + + + + + + LDSModule + + + + + + + + + model + + + + + 25 + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs new file mode 100644 index 00000000..ba34f269 --- /dev/null +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs @@ -0,0 +1,150 @@ +using System.ComponentModel; +using Newtonsoft.Json; +using System; +using System.Reactive.Linq; +using Python.Runtime; +using System.Collections.Generic; +using System.Linq; + +namespace Bonsai.ML.LinearDynamicalSystems.LinearRegression +{ + + /// + /// Model parameters for a Kalman Filter Kinematics python class + /// + [Description("Model parameters for a Kalman Filter Linear Regression (KFLR) model")] + [Combinator()] + [WorkflowElementCategory(ElementCategory.Source)] + public class KFModelParameters + { + + private double _likelihood_precision_coef; + + private double _prior_precision_coef; + + private double[,] _mn = null; + + private string _likelihood_precision_coefString; + private string _prior_precision_coefString; + private string _mnString; + + /// + /// likelihood precision coefficient + /// + [JsonProperty("likelihood_precision_coef")] + [Description("likelihood precision coefficient")] + public double LikelihoodPrecisionCoef + { + get + { + return _likelihood_precision_coef; + } + set + { + _likelihood_precision_coef = value; + _likelihood_precision_coefString = double.IsNaN(_likelihood_precision_coef) ? "None" : _likelihood_precision_coef.ToString(); + } + } + + /// + /// prior precision coefficient + /// + [JsonProperty("prior_precision_coef")] + [Description("prior precision coefficient")] + public double PriorPrecisionCoef + { + get + { + return _prior_precision_coef; + } + set + { + _prior_precision_coef = value; + _prior_precision_coefString = double.IsNaN(_prior_precision_coef) ? "None" : _prior_precision_coef.ToString(); + } + } + + /// + /// mean of the prior + /// + [JsonProperty("mn")] + [Description("mean of the prior")] + public double[,] Mn + { + get + { + return _mn; + } + set + { + _mn = value; + if (_mn == null || _mn.Length == 0) _mnString = "None"; + else + { + _mnString = NumpyHelper.NumpyParser.ParseArray(_mn); + } + } + } + + public KFModelParameters () + { + LikelihoodPrecisionCoef = 25; + PriorPrecisionCoef = 2; + } + + /// + /// Generates parameters for a Kalman Filter Linear Regression Model + /// + public IObservable Process() + { + return Observable.Defer(() => Observable.Return( + new KFModelParameters { + LikelihoodPrecisionCoef = _likelihood_precision_coef, + PriorPrecisionCoef = _prior_precision_coef, + Mn = _mn + })); + } + + /// + /// Gets the model parameters from a PyObject of a Kalman Filter Linear Regression Model + /// + public IObservable Process(IObservable source) + { + return Observable.Select(source, pyObject => + { + // var likelihood_precision_coefPyObj = GetPythonAttribute(pyObject, "likelihood_precision_coef"); + var likelihood_precision_coefPyObj = pyObject.GetAttr("likelihood_precision_coef"); + // var prior_precision_coefPyObj = GetPythonAttribute(pyObject, "prior_precision_coef"); + var prior_precision_coefPyObj = pyObject.GetAttr("prior_precision_coef"); + // var mnPyObj = (double[])GetPythonAttribute(pyObject, "mn"); + var mnPyObj = (double[,])pyObject.GetArrayAttribute("mn"); + + return new KFModelParameters { + LikelihoodPrecisionCoef = likelihood_precision_coefPyObj, + PriorPrecisionCoef = _prior_precision_coef, + Mn = mnPyObj + }; + }); + } + + /// + /// Generates parameters for a Kalman Filter Linear Regression Model on each input + /// + public IObservable Process(IObservable source) + { + return Observable.Select(source, x => + new KFModelParameters { + LikelihoodPrecisionCoef = _likelihood_precision_coef, + PriorPrecisionCoef = _prior_precision_coef, + Mn = _mn + }); + } + + public override string ToString() + { + + return $"likelihood_precision_coef={_likelihood_precision_coefString},prior_precision_coef={_prior_precision_coefString},mn={_mnString}"; + } + } + +} \ No newline at end of file diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/PerformInference.bonsai b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/PerformInference.bonsai new file mode 100644 index 00000000..aeda5b80 --- /dev/null +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/PerformInference.bonsai @@ -0,0 +1,147 @@ + + + + + + Source1 + + + + + + + + + + model + + + + + + + Predict + + + + Source1 + + + {0}.predict() + it.Item2 + + + + + + + + LDSModule + + + + + + + + + model.predict() + + + + + + + + + + + + + + + + Update + + + + Source1 + + + {0}.update({1}) + it.Item2, it.Item1 + + + + + + + + LDSModule + + + + + + + + + model.update(x=0,y=0) + + + + + + + + + + + + + + + + + + + + + LDSModule + + + + + + + + + model + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.LinearDynamicalSystems/SerializeToJson.cs b/src/Bonsai.ML.LinearDynamicalSystems/SerializeToJson.cs index 13cf4aec..7bef804e 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/SerializeToJson.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/SerializeToJson.cs @@ -47,5 +47,10 @@ public IObservable Process(IObservable so { return Process(source); } + + public IObservable Process(IObservable source) + { + return Process(source); + } } } diff --git a/src/Bonsai.ML.LinearDynamicalSystems/main.py b/src/Bonsai.ML.LinearDynamicalSystems/main.py index b56c401b..d55f5b1f 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/main.py +++ b/src/Bonsai.ML.LinearDynamicalSystems/main.py @@ -1,19 +1,6 @@ -from lds.inference import OnlineKalmanFilter +from lds.inference import OnlineKalmanFilter, TimeVaryingOnlineKalmanFilter import numpy as np - -DEFAULT_PARAMS = { - "pos_x0" : 0, - "pos_y0" : 0, - "vel_x0" : 0, - "vel_y0" : 0, - "acc_x0" : 0, - "acc_y0" : 0, - "sigma_a" : 10000, - "sigma_x" : 100, - "sigma_y" : 100, - "sqrt_diag_V0_value" : 0.001, - "fps" : 60 -} +from scipy.stats import multivariate_normal class KalmanFilterKinematics(OnlineKalmanFilter): @@ -31,8 +18,6 @@ def __init__(self, fps: int ) -> None: - super(OnlineKalmanFilter, self).__init__() - self.pos_x0=pos_x0 self.pos_y0=pos_y0 self.vel_x0=vel_x0 @@ -51,12 +36,8 @@ def __init__(self, if np.isnan(self.pos_y0): self.pos_y0 = 0 - # build KF matrices for tracking dt = 1.0 / self.fps - # Taken from the book - # barShalomEtAl01-estimationWithApplicationToTrackingAndNavigation.pdf - # section 6.3.3 - # Eq. 6.3.3-2 + B = np.array([ [1, dt, .5*dt**2, 0, 0, 0], [0, 1, dt, 0, 0, 0], [0, 0, 1, 0, 0, 0], @@ -64,8 +45,10 @@ def __init__(self, [0, 0, 0, 0, 1, dt], [0, 0, 0, 0, 0, 1]], dtype=np.double) + Z = np.array([ [1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0]], + dtype=np.double) Qt = np.array([ [dt**4/4, dt**3/2, dt**2/2, 0, 0, 0], [dt**3/2, dt**2, dt, 0, 0, 0], @@ -91,12 +74,38 @@ def update(self, x, y): return super().update(y=np.array([x, y])) -if __name__ == "__main__": - - model = KalmanFilterKinematics(**DEFAULT_PARAMS) - x, y = 100, 100 - model.predict() - model.update(x, y) - print(model.x) - print(model.x.shape) - print(model.P.shape) \ No newline at end of file +class KalmanFilterLinearRegression(TimeVaryingOnlineKalmanFilter): + + def __init__(self, + likelihood_precision_coef: float, + prior_precision_coef: float, + mn: list[float], + ) -> None: + + if mn is None: + self.mn = np.array([0.0, 0.0]) + else: + self.mn = np.array(mn) + + self.likelihood_precision_coef = likelihood_precision_coef + self.prior_precision_coef = prior_precision_coef + self.x = self.mn + self.P = np.eye(len(self.x)) * 1.0 / self.prior_precision_coef + + self.B = np.eye(N=len(self.x)) + self.Q = np.zeros(shape=((len(self.x), len(self.x)))) + self.R = np.array([[1.0/self.likelihood_precision_coef]]) + + super().__init__() + + def predict(self): + self.x, self.P = super().predict(x = self.x, P = self.P, B = self.B, Q = self.Q) + + def update(self, x, y): + self.x, self.P = super().update(y = y, x = self.x, P = self.P, Z = np.array([1, x]), R = self.R) + + def pdf(self, x1 = 0, x2 = 1, xsteps = 100, y1 = 0, y2 = 1, ysteps = 100): + rv = multivariate_normal(self.x, self.P) + x, y = np.mgrid[x1:x2:np.abs(x2-x1)/xsteps, y1:y2:np.abs(y2-y1)/ysteps] + pos = np.dstack((x, y)) + self.pdf = rv.pdf(pos) \ No newline at end of file From 7310c1c31d4e20de1b472105197eaab15ce3dd3d Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Tue, 12 Mar 2024 09:45:03 +0000 Subject: [PATCH 02/14] Added heatmap visualizers and linear regression model --- .../CreateMultivariatePDF.bonsai | 127 +++++++++++ .../LinearRegression/GridParameters.cs | 214 ++++++++++++++++++ .../LinearRegression/KFModelParameters.cs | 7 +- .../LinearRegression/MultivariatePDF.cs | 40 ++++ .../LinearRegression/PerformInference.bonsai | 26 ++- src/Bonsai.ML.LinearDynamicalSystems/main.py | 24 +- .../HeatMapSeriesOxyPlotBase.cs | 181 +++++++++++++++ .../MultivariatePDFVisualizer.cs | 91 ++++++++ .../StateComponentVisualizer.cs | 112 +++++++++ 9 files changed, 798 insertions(+), 24 deletions(-) create mode 100644 src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai create mode 100644 src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs create mode 100644 src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs create mode 100644 src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs create mode 100644 src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs create mode 100644 src/Bonsai.ML.Visualizers/StateComponentVisualizer.cs diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai new file mode 100644 index 00000000..953a4c3d --- /dev/null +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai @@ -0,0 +1,127 @@ + + + + + + Source1 + + + + + + + + + model + + + Name + + + + + + Item2 + + + + -1 + 1 + 100 + -1 + 1 + 100 + + + + + + + PDF + + + + Source1 + + + {0}.pdf({1}) + it.Item1, it.Item2 + + + + + + + + LDSModule + + + + + + + + + model.pdf(x0=-1,x1=1,xsteps=100,y0=-1,y1=1,ysteps=100) + + + + + + + + + + + + + + + + + + + + + LDSModule + + + + + + + + + model + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs new file mode 100644 index 00000000..3d60a675 --- /dev/null +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs @@ -0,0 +1,214 @@ +using System.ComponentModel; +using System; +using System.Reactive.Linq; +using Newtonsoft.Json; +using Python.Runtime; + +namespace Bonsai.ML.LinearDynamicalSystems.LinearRegression +{ + + /// + /// 2D grid parameters used for calculating the PDF of a multivariate distribution + /// + [Description("2D grid parameters used for calculating the PDF of a multivariate distribution")] + [Combinator()] + [WorkflowElementCategory(ElementCategory.Source)] + public class GridParameters + { + + private double _x0 = 0; + private double _x1 = 1; + private int _xsteps = 100; + + private double _y0 = 0; + private double _y1 = 1; + private int _ysteps = 100; + + private string _x0String; + private string _x1String; + private string _xstepsString; + + private string _y0String; + private string _y1String; + private string _ystepsString; + + /// + /// Lower bound of the x axis + /// + [JsonProperty("x0")] + [Description("Lower bound of the x axis")] + public double X0 + { + get + { + return _x0; + } + set + { + _x0 = value; + _x0String = double.IsNaN(_x0) ? "None" : _x0.ToString(); + + } + } + + /// + /// Upper bound of the x axis + /// + [JsonProperty("x1")] + [Description("Upper bound of the x axis")] + public double X1 + { + get + { + return _x1; + } + set + { + _x1 = value; + _x1String = double.IsNaN(_x1) ? "None" : _x1.ToString(); + } + } + + /// + /// Number of steps along the x axis + /// + [JsonProperty("xsteps")] + [Description("Number of steps along the x axis")] + public int Xsteps + { + get + { + return _xsteps; + } + set + { + _xsteps = value >= 0 ? value : _xsteps; + _xstepsString = _xsteps.ToString(); + } + } + + /// + /// Lower bound of the y axis + /// + [JsonProperty("y0")] + [Description("Lower bound of the y axis")] + public double Y0 + { + get + { + return _y0; + } + set + { + _y0 = value; + _y0String = double.IsNaN(_y0) ? "None" : _y0.ToString(); + } + } + + /// + /// Upper bound of the y axis + /// + [JsonProperty("y1")] + [Description("Upper bound of the y axis")] + public double Y1 + { + get + { + return _y1; + } + set + { + _y1 = value; + _y1String = double.IsNaN(_y1) ? "None" : _y1.ToString(); + } + } + + /// + /// Number of steps along the y axis + /// + [JsonProperty("ysteps")] + [Description("Number of steps along the y axis")] + public int Ysteps + { + get + { + return _ysteps; + } + set + { + _ysteps = value; + _ystepsString = _ysteps.ToString(); + } + } + + /// + /// Generates grid parameters + /// + public IObservable Process() + { + return Observable.Defer(() => Observable.Return( + new GridParameters { + X0 = _x0, + X1 = _x1, + Xsteps = _xsteps, + Y0 = _y0, + Y1 = _y1, + Ysteps = _ysteps, + })); + } + + /// + /// Gets the grid parameters from a PyObject of a Kalman Filter Linear Regression Model + /// + public IObservable Process(IObservable source) + { + return Observable.Select(source, pyObject => + { + return ConvertPyObject(pyObject); + }); + } + + public static GridParameters ConvertPyObject(PyObject pyObject) + { + var x0PyObj = pyObject.GetAttr("x0"); + var x1PyObj = pyObject.GetAttr("x1"); + var xstepsPyObj = pyObject.GetAttr("xsteps"); + + var y0PyObj = pyObject.GetAttr("y0"); + var y1PyObj = pyObject.GetAttr("y1"); + var ystepsPyObj = pyObject.GetAttr("ysteps"); + + return new GridParameters { + X0 = x0PyObj, + X1 = x1PyObj, + Xsteps = xstepsPyObj, + Y0 = y0PyObj, + Y1 = y1PyObj, + Ysteps = ystepsPyObj, + }; + } + + /// + /// Generates grid parameters on each input + /// + public IObservable Process(IObservable source) + { + return Observable.Select(source, x => + new GridParameters { + X0 = _x0, + X1 = _x1, + Xsteps = _xsteps, + Y0 = _y0, + Y1 = _y1, + Ysteps = _ysteps, + }); + } + + /// + public override string ToString() + { + + return $"x0={_x0},x1={_x1},xsteps={_xsteps},y0={_y0},y1={_y1},ysteps={_ysteps}"; + } + } +} diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs index ba34f269..80ba6ada 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs @@ -3,8 +3,7 @@ using System; using System.Reactive.Linq; using Python.Runtime; -using System.Collections.Generic; -using System.Linq; +using System.Xml.Serialization; namespace Bonsai.ML.LinearDynamicalSystems.LinearRegression { @@ -67,6 +66,7 @@ public double PriorPrecisionCoef /// /// mean of the prior /// + [XmlIgnore] [JsonProperty("mn")] [Description("mean of the prior")] public double[,] Mn @@ -112,11 +112,8 @@ public IObservable Process(IObservable source) { return Observable.Select(source, pyObject => { - // var likelihood_precision_coefPyObj = GetPythonAttribute(pyObject, "likelihood_precision_coef"); var likelihood_precision_coefPyObj = pyObject.GetAttr("likelihood_precision_coef"); - // var prior_precision_coefPyObj = GetPythonAttribute(pyObject, "prior_precision_coef"); var prior_precision_coefPyObj = pyObject.GetAttr("prior_precision_coef"); - // var mnPyObj = (double[])GetPythonAttribute(pyObject, "mn"); var mnPyObj = (double[,])pyObject.GetArrayAttribute("mn"); return new KFModelParameters { diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs new file mode 100644 index 00000000..ee7c1f00 --- /dev/null +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs @@ -0,0 +1,40 @@ +using System.ComponentModel; +using System; +using System.Reactive.Linq; +using Python.Runtime; +using System.Xml.Serialization; + +namespace Bonsai.ML.LinearDynamicalSystems.LinearRegression +{ + /// + /// A class that converts a python object, representing a multivariate PDF, into a multidimensional array + /// /// + [Description("A multivariate PDF")] + [Combinator()] + [WorkflowElementCategory(ElementCategory.Transform)] + public class MultivariatePDF + { + + [XmlIgnore] + public GridParameters GridParameters; + + [XmlIgnore] + public double[,] Values; + + /// + /// Converts a python object, representing a multivariate PDF, into a multidimensional array + /// + public IObservable Process(IObservable source) + { + return Observable.Select(source, pyObject => + { + var gridParameters = GridParameters.ConvertPyObject(pyObject); + var values = (double[,])pyObject.GetArrayAttribute("pdf_values"); + return new MultivariatePDF { + GridParameters = gridParameters, + Values = values + }; + }); + } + } +} \ No newline at end of file diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/PerformInference.bonsai b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/PerformInference.bonsai index aeda5b80..75aa6fea 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/PerformInference.bonsai +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/PerformInference.bonsai @@ -14,12 +14,13 @@ - + - - - model - + + model + + + Name @@ -131,17 +132,18 @@ - + - - + + - - - - + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.LinearDynamicalSystems/main.py b/src/Bonsai.ML.LinearDynamicalSystems/main.py index d55f5b1f..4c99cecb 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/main.py +++ b/src/Bonsai.ML.LinearDynamicalSystems/main.py @@ -83,7 +83,7 @@ def __init__(self, ) -> None: if mn is None: - self.mn = np.array([0.0, 0.0]) + self.mn = np.array([[0.0], [0.0]]) else: self.mn = np.array(mn) @@ -102,10 +102,20 @@ def predict(self): self.x, self.P = super().predict(x = self.x, P = self.P, B = self.B, Q = self.Q) def update(self, x, y): - self.x, self.P = super().update(y = y, x = self.x, P = self.P, Z = np.array([1, x]), R = self.R) + self.x, self.P = super().update(y = np.array([y]), x = self.x, P = self.P, Z = np.array([x, 1]).reshape((1, -1)), R = self.R) - def pdf(self, x1 = 0, x2 = 1, xsteps = 100, y1 = 0, y2 = 1, ysteps = 100): - rv = multivariate_normal(self.x, self.P) - x, y = np.mgrid[x1:x2:np.abs(x2-x1)/xsteps, y1:y2:np.abs(y2-y1)/ysteps] - pos = np.dstack((x, y)) - self.pdf = rv.pdf(pos) \ No newline at end of file + def pdf(self, x0 = 0, x1 = 1, xsteps = 100, y0 = 0, y1 = 1, ysteps = 100): + + self.x0 = x0 + self.x1 = x1 + self.xsteps = xsteps + self.y0 = y0 + self.y1 = y1 + self.ysteps = ysteps + + rv = multivariate_normal(self.x.flatten(), self.P) + self.xpos = np.linspace(x0, x1, xsteps) + self.ypos = np.linspace(y0, y1, ysteps) + xx, yy = np.meshgrid(self.xpos, self.ypos) + pos = np.dstack((xx, yy)) + self.pdf_values = rv.pdf(pos) \ No newline at end of file diff --git a/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs b/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs new file mode 100644 index 00000000..ea57ab22 --- /dev/null +++ b/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs @@ -0,0 +1,181 @@ +using System.Windows.Forms; +using OxyPlot; +using OxyPlot.Series; +using OxyPlot.WindowsForms; +using System.Drawing; +using System; +using OxyPlot.Axes; +using System.Collections.Generic; +using System.Reflection; + +namespace Bonsai.ML.Visualizers +{ + internal class HeatMapSeriesOxyPlotBase : UserControl + { + private PlotView view; + private PlotModel model; + private HeatMapSeries heatMapSeries; + private LinearColorAxis colorAxis; + + private ComboBox comboBox; + private Label label; + + private int _numColors = 100; + + private int _selectedIndex; + + private OxyPalette palette; + + /// + /// Event handler which can be used to hook into events generated when the combobox values have changed. + /// + public event EventHandler ComboBoxValueChanged; + + public ComboBox ComboBox + { + get => comboBox; + } + + /// + /// Constructor of the TimeSeriesOxyPlotBase class. + /// Requires a line series name and an area series name. + /// Data source is optional, since pasing it to the constructor will populate the combobox and leave it empty otherwise. + /// The selected index is only needed when the data source is provided. + /// + public HeatMapSeriesOxyPlotBase(int selectedIndex, int numColors = 100) + { + _selectedIndex = selectedIndex; + _numColors = numColors; + // palette = OxyPalettes.Rainbow(_numColors); + Initialize(); + } + + private void Initialize() + { + view = new PlotView + { + Size = Size, + Dock = DockStyle.Fill, + }; + + model = new PlotModel(); + + heatMapSeries = new HeatMapSeries { + X0 = 0, + X1 = 100, + Y0 = 0, + Y1 = 100, + Interpolate = true, + RenderMethod = HeatMapRenderMethod.Bitmap, + CoordinateDefinition = HeatMapCoordinateDefinition.Center + }; + + colorAxis = new LinearColorAxis { + Position = AxisPosition.Right, + }; + + model.Axes.Add(colorAxis); + model.Series.Add(heatMapSeries); + + view.Model = model; + Controls.Add(view); + + label = new Label + { + Text = "Color palette:", + AutoSize = true, + Location = new Point(-200, 8), + Anchor = AnchorStyles.Top | AnchorStyles.Right + }; + + comboBox = new ComboBox + { + Location = new Point(0, 5), + DataSource = Enum.GetValues(typeof(ColorPalettes)), + Anchor = AnchorStyles.Top | AnchorStyles.Right, + BindingContext = BindingContext + }; + + Controls.Add(comboBox); + Controls.Add(label); + + comboBox.SelectedIndexChanged += ComboBoxSelectedIndexChanged; + comboBox.SelectedIndex = _selectedIndex; + UpdateColorPalette(); + + comboBox.BringToFront(); + label.BringToFront(); + + AutoScaleDimensions = new SizeF(6F, 13F); + } + + private void ComboBoxSelectedIndexChanged(object sender, EventArgs e) + { + if (comboBox.SelectedIndex != _selectedIndex) + { + _selectedIndex = comboBox.SelectedIndex; + UpdateColorPalette(); + ComboBoxValueChanged?.Invoke(this, e); + UpdatePlot(); + } + } + + private void UpdateColorPalette() + { + var selectedPalette = (ColorPalettes)comboBox.Items[_selectedIndex]; + paletteLookup.TryGetValue(selectedPalette, out Func paletteMethod); + palette = paletteMethod(_numColors); + colorAxis.Palette = palette; + } + + public void UpdateHeatMapSeries(double x0, double x1, int xsteps, double y0, double y1, int ysteps, double[,] data) + { + heatMapSeries.X0 = x0; + heatMapSeries.X1 = x1; + heatMapSeries.Y0 = y0; + heatMapSeries.Y1 = y1; + heatMapSeries.Data = data; + } + + public void UpdatePlot() + { + model.InvalidatePlot(true); + } + + private static readonly Dictionary> paletteLookup = new Dictionary> + { + { ColorPalettes.Cividis, (numColors) => OxyPalettes.Cividis(numColors) }, + { ColorPalettes.Inferno, (numColors) => OxyPalettes.Inferno(numColors) }, + { ColorPalettes.Viridis, (numColors) => OxyPalettes.Viridis(numColors) }, + { ColorPalettes.Magma, (numColors) => OxyPalettes.Magma(numColors) }, + { ColorPalettes.Plasma, (numColors) => OxyPalettes.Plasma(numColors) }, + { ColorPalettes.BlackWhiteRed, (numColors) => OxyPalettes.BlackWhiteRed(numColors) }, + { ColorPalettes.BlueWhiteRed, (numColors) => OxyPalettes.BlueWhiteRed(numColors) }, + { ColorPalettes.Cool, (numColors) => OxyPalettes.Cool(numColors) }, + { ColorPalettes.Gray, (numColors) => OxyPalettes.Gray(numColors) }, + { ColorPalettes.Hot, (numColors) => OxyPalettes.Hot(numColors) }, + { ColorPalettes.Hue, (numColors) => OxyPalettes.Hue(numColors) }, + { ColorPalettes.HueDistinct, (numColors) => OxyPalettes.HueDistinct(numColors) }, + { ColorPalettes.Jet, (numColors) => OxyPalettes.Jet(numColors) }, + { ColorPalettes.Rainbow, (numColors) => OxyPalettes.Rainbow(numColors) }, + }; + } + + internal enum ColorPalettes + { + Cividis, + Inferno, + Viridis, + Magma, + Plasma, + BlackWhiteRed, + BlueWhiteRed, + Cool, + Gray, + Hot, + Hue, + HueDistinct, + Jet, + Rainbow + } +} diff --git a/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs b/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs new file mode 100644 index 00000000..b194ce75 --- /dev/null +++ b/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs @@ -0,0 +1,91 @@ +using System; +using System.Windows.Forms; +using System.Collections.Generic; +using System.Reflection; +using Bonsai; +using Bonsai.Design; +using Bonsai.ML.Visualizers; +using Bonsai.ML.LinearDynamicalSystems; +using Bonsai.ML.LinearDynamicalSystems.LinearRegression; +using System.Drawing; +using System.Reactive; +using Bonsai.Reactive; + +[assembly: TypeVisualizer(typeof(MultivariatePDFVisualizer), Target = typeof(MultivariatePDF))] + +namespace Bonsai.ML.Visualizers +{ + + /// + /// Provides a type visualizer to display the state components of a Kalman Filter kinematics model. + /// + public class MultivariatePDFVisualizer : DialogTypeVisualizer + { + + private int selectedIndex = 0; + + /// + /// Size of the window when loaded + /// + public Size Size { get; set; } = new Size(320, 240); + + /// + /// The selected index of the color palette to use + /// + public int SelectedIndex { get => selectedIndex; set => selectedIndex = value; } + + private HeatMapSeriesOxyPlotBase Plot; + + /// + public override void Load(IServiceProvider provider) + { + Plot = new HeatMapSeriesOxyPlotBase(selectedIndex) + { + Size = Size, + Dock = DockStyle.Fill, + }; + + Plot.ComboBoxValueChanged += IndexChanged; + + var visualizerService = (IDialogTypeVisualizerService)provider.GetService(typeof(IDialogTypeVisualizerService)); + if (visualizerService != null) + { + visualizerService.AddControl(Plot); + } + } + + /// + public override void Show(object value) + { + var pdf = (MultivariatePDF)value; + Plot.UpdateHeatMapSeries( + pdf.GridParameters.X0, + pdf.GridParameters.X1, + pdf.GridParameters.Xsteps, + pdf.GridParameters.Y0, + pdf.GridParameters.Y1, + pdf.GridParameters.Ysteps, + pdf.Values + ); + Plot.UpdatePlot(); + } + + /// + public override void Unload() + { + if (!Plot.IsDisposed) + { + Plot.Dispose(); + } + } + + /// + /// Callback function to update the selected index when the selected combobox index has changed + /// + private void IndexChanged(object sender, EventArgs e) + { + var comboBox = Plot.ComboBox; + selectedIndex = comboBox.SelectedIndex; + } + } +} diff --git a/src/Bonsai.ML.Visualizers/StateComponentVisualizer.cs b/src/Bonsai.ML.Visualizers/StateComponentVisualizer.cs new file mode 100644 index 00000000..fbdaf333 --- /dev/null +++ b/src/Bonsai.ML.Visualizers/StateComponentVisualizer.cs @@ -0,0 +1,112 @@ +using System; +using System.Windows.Forms; +using System.Collections.Generic; +using Bonsai; +using Bonsai.Design; +using Bonsai.ML.Visualizers; +using Bonsai.ML.LinearDynamicalSystems; +using System.Drawing; +using System.Reactive; + +[assembly: TypeVisualizer(typeof(StateComponentVisualizer), Target = typeof(StateComponent))] + +namespace Bonsai.ML.Visualizers +{ + /// + /// Provides a type visualizer to display the state components of a Kalman Filter kinematics model. + /// + public class StateComponentVisualizer : BufferedVisualizer + { + + private DateTime? _startTime; + + private TimeSeriesOxyPlotBase Plot; + + /// + /// 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; + + /// + public override void Load(IServiceProvider provider) + { + Plot = new TimeSeriesOxyPlotBase( + lineSeriesName: "Mean", + areaSeriesName: "Variance" + ) + { + Size = Size, + Capacity = Capacity, + Dock = DockStyle.Fill, + StartTime = DateTime.Now + }; + + Plot.ResetSeries(); + + var visualizerService = (IDialogTypeVisualizerService)provider.GetService(typeof(IDialogTypeVisualizerService)); + if (visualizerService != null) + { + visualizerService.AddControl(Plot); + } + } + + /// + public override void Show(object value) + { + } + + /// + protected override void Show(DateTime time, object value) + { + if (!_startTime.HasValue) + { + _startTime = time; + Plot.StartTime = _startTime.Value; + Plot.ResetSeries(); + } + + StateComponent stateComponent = (StateComponent)value; + double mean = stateComponent.Mean; + double variance = stateComponent.Variance; + + Plot.AddToLineSeries( + time: time, + mean: mean + ); + + Plot.AddToAreaSeries( + time: time, + mean: mean, + variance: variance + ); + + Plot.SetAxes(minTime: time.AddSeconds(-Capacity), maxTime: time); + + } + + /// + protected override void ShowBuffer(IList> values) + { + base.ShowBuffer(values); + if (values.Count > 0) + { + Plot.UpdatePlot(); + } + } + + /// + public override void Unload() + { + _startTime = null; + if (!Plot.IsDisposed) + { + Plot.Dispose(); + } + } + } +} From 8b102927a66f98e3628a074b168d66a8e31c94e1 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 22 Mar 2024 17:21:12 +0000 Subject: [PATCH 03/14] Added linear regression nodes and workflows --- .../Bonsai.ML.LinearDynamicalSystems.csproj | 2 +- .../LinearRegression/CreateKFModel.bonsai | 165 ++++++++--------- .../CreateMultivariatePDF.bonsai | 23 ++- .../LinearRegression/KFModelParameters.cs | 85 +++++++-- .../Reshape.cs | 50 +++++ src/Bonsai.ML.LinearDynamicalSystems/Slice.cs | 72 ++++++++ .../StateComponent.cs | 36 +++- src/Bonsai.ML.LinearDynamicalSystems/main.py | 39 ++-- .../Bonsai.ML.Visualizers.csproj | 2 +- src/Bonsai.ML.Visualizers/ColorPalettes.cs | 20 ++ .../HeatMapSeriesOxyPlotBase.cs | 173 ++++++++++++------ .../MultidimensionalArrayVisualizer.cs | 102 +++++++++++ .../MultivariatePDFVisualizer.cs | 32 ++-- .../TimeSeriesOxyPlotBase.cs | 91 +++++---- 14 files changed, 665 insertions(+), 227 deletions(-) create mode 100644 src/Bonsai.ML.LinearDynamicalSystems/Reshape.cs create mode 100644 src/Bonsai.ML.LinearDynamicalSystems/Slice.cs create mode 100644 src/Bonsai.ML.Visualizers/ColorPalettes.cs create mode 100644 src/Bonsai.ML.Visualizers/MultidimensionalArrayVisualizer.cs diff --git a/src/Bonsai.ML.LinearDynamicalSystems/Bonsai.ML.LinearDynamicalSystems.csproj b/src/Bonsai.ML.LinearDynamicalSystems/Bonsai.ML.LinearDynamicalSystems.csproj index 4f672b63..26aaa789 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/Bonsai.ML.LinearDynamicalSystems.csproj +++ b/src/Bonsai.ML.LinearDynamicalSystems/Bonsai.ML.LinearDynamicalSystems.csproj @@ -4,7 +4,7 @@ A Bonsai package for implementing the Kalman Filter using python. Bonsai Rx ML KalmanFilter LinearDynamicalSystems net472;netstandard2.0 - 0.1.0 + 0.1.1 diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai index ce004666..1638a7e0 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai @@ -1,121 +1,112 @@  - - - - - - model - - - - model - - - Name - Source1 - - - + + + + + + + - - 25 - 2 + + 25 + 2 + 2 - - + + - - - - {0} = KalmanFilterLinearRegression({1}) - it.Item1, it.Item2 - - - - - - - - LDSModule + + model + - - - - + + model - - - model = KalmanFilterLinearRegression() - + + Name - - - - - - - LDSModule - - - - - + + InitModel + + + + Source1 + + + + + + {0} = KalmanFilterLinearRegression({1}) + it.Item2, it.Item1 + + + + + + + + LDSModule + + + + + + + + + model = KalmanFilterLinearRegression(likelihood_precision_coef=25,prior_precision_coef=2,n_features=2,x=None,P=None) + + + + + + + + + + + + + + - - - model - - - - - 25 - 2 - + + Item1 - - - - - - - - - - + + + + + + + + + - - - - - - - - - - + \ No newline at end of file diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai index 953a4c3d..0ee02e63 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai @@ -29,6 +29,14 @@ Item2 + + + + + + + + -1 @@ -113,15 +121,16 @@ - - - + + + - - - - + + + + + \ No newline at end of file diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs index 80ba6ada..8375f92f 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs @@ -21,17 +21,23 @@ public class KFModelParameters private double _prior_precision_coef; - private double[,] _mn = null; + private int _n_features; + + private double[,] _x = null; + private double[,] _p = null; private string _likelihood_precision_coefString; private string _prior_precision_coefString; - private string _mnString; + private string _n_featuresString; + private string _xString; + private string _pString; /// /// likelihood precision coefficient /// [JsonProperty("likelihood_precision_coef")] [Description("likelihood precision coefficient")] + [Category("Parameters")] public double LikelihoodPrecisionCoef { get @@ -50,6 +56,7 @@ public double LikelihoodPrecisionCoef /// [JsonProperty("prior_precision_coef")] [Description("prior precision coefficient")] + [Category("Parameters")] public double PriorPrecisionCoef { get @@ -64,25 +71,61 @@ public double PriorPrecisionCoef } /// - /// mean of the prior + /// number of features + /// + [JsonProperty("n_features")] + [Description("number of features")] + [Category("Parameters")] + public int NumFeatures + { + get + { + return _n_features; + } + set + { + _n_features = value; + _n_featuresString = _n_features.ToString(); + } + } + + /// + /// matrix representing the mean of the state + /// + [XmlIgnore] + [JsonProperty("x")] + [Description("matrix representing the mean of the state")] + [Category("ModelState")] + public double[,] X + { + get + { + return _x; + } + set + { + _x = value; + _xString = _x == null ? "None" : NumpyHelper.NumpyParser.ParseArray(_x); + } + } + + /// + /// matrix representing the covariance of the state /// [XmlIgnore] - [JsonProperty("mn")] - [Description("mean of the prior")] - public double[,] Mn + [JsonProperty("P")] + [Description("matrix representing the covariance of the state")] + [Category("ModelState")] + public double[,] P { get { - return _mn; + return _p; } set { - _mn = value; - if (_mn == null || _mn.Length == 0) _mnString = "None"; - else - { - _mnString = NumpyHelper.NumpyParser.ParseArray(_mn); - } + _p = value; + _pString = _p == null ? "None" : NumpyHelper.NumpyParser.ParseArray(_p); } } @@ -101,7 +144,9 @@ public IObservable Process() new KFModelParameters { LikelihoodPrecisionCoef = _likelihood_precision_coef, PriorPrecisionCoef = _prior_precision_coef, - Mn = _mn + NumFeatures = _n_features, + X = _x, + P = _p })); } @@ -114,12 +159,14 @@ public IObservable Process(IObservable source) { var likelihood_precision_coefPyObj = pyObject.GetAttr("likelihood_precision_coef"); var prior_precision_coefPyObj = pyObject.GetAttr("prior_precision_coef"); - var mnPyObj = (double[,])pyObject.GetArrayAttribute("mn"); + var n_featuresPyObj = pyObject.GetAttr("n_features"); return new KFModelParameters { LikelihoodPrecisionCoef = likelihood_precision_coefPyObj, PriorPrecisionCoef = _prior_precision_coef, - Mn = mnPyObj + NumFeatures = n_featuresPyObj, + X = _x, + P = _p }; }); } @@ -133,14 +180,16 @@ public IObservable Process(IObservable sour new KFModelParameters { LikelihoodPrecisionCoef = _likelihood_precision_coef, PriorPrecisionCoef = _prior_precision_coef, - Mn = _mn + NumFeatures = _n_features, + X = _x, + P = _p }); } public override string ToString() { - return $"likelihood_precision_coef={_likelihood_precision_coefString},prior_precision_coef={_prior_precision_coefString},mn={_mnString}"; + return $"likelihood_precision_coef={_likelihood_precision_coefString},prior_precision_coef={_prior_precision_coefString},n_features={_n_featuresString},x={_xString},P={_pString}"; } } diff --git a/src/Bonsai.ML.LinearDynamicalSystems/Reshape.cs b/src/Bonsai.ML.LinearDynamicalSystems/Reshape.cs new file mode 100644 index 00000000..53d734b6 --- /dev/null +++ b/src/Bonsai.ML.LinearDynamicalSystems/Reshape.cs @@ -0,0 +1,50 @@ +using System; +using System.Reactive.Linq; + +namespace Bonsai.ML.LinearDynamicalSystems +{ + /// + /// A class that converts a python object, representing a multivariate PDF, into a multidimensional array + /// /// + [Combinator()] + [WorkflowElementCategory(ElementCategory.Transform)] + public class Reshape + { + public int Rows {get;set;} + public int Cols {get;set;} + + public IObservable Process(IObservable source) + { + + var rows = Rows; + var cols = Cols; + + return Observable.Select(source, value => + { + var inputRows = value.GetLength(0); + var inputCols = value.GetLength(1); + int totalElements = inputRows * inputCols; + + if (totalElements != rows * cols) + { + throw new InvalidOperationException($"Multidimensional array of shape {rows}x{cols} cannot be made from input array with a total of {totalElements} elements."); + } + + double[,] reshapedArray = new double[rows, cols]; + + for (int i = 0; i < totalElements; i++) + { + int originalRow = i / inputCols; + int originalCol = i % inputCols; + + int newRow = i / cols; + int newCol = i % cols; + + reshapedArray[newRow, newCol] = value[originalRow, originalCol]; + } + + return reshapedArray; + }); + } + } +} \ No newline at end of file diff --git a/src/Bonsai.ML.LinearDynamicalSystems/Slice.cs b/src/Bonsai.ML.LinearDynamicalSystems/Slice.cs new file mode 100644 index 00000000..7584a611 --- /dev/null +++ b/src/Bonsai.ML.LinearDynamicalSystems/Slice.cs @@ -0,0 +1,72 @@ +using System; +using System.Reactive.Linq; + +namespace Bonsai.ML.LinearDynamicalSystems +{ + /// + /// A class that converts a python object, representing a multivariate PDF, into a multidimensional array + /// /// + [Combinator()] + [WorkflowElementCategory(ElementCategory.Transform)] + public class Slice + { + public int? RowStart {get;set;} = null; + public int? RowEnd {get;set;} = null; + public int? ColStart {get;set;} = null; + public int? ColEnd {get;set;} = null; + + public IObservable Process(IObservable source) + { + + int rowStart = RowStart.HasValue ? RowStart.Value : 0; + int rowEnd = RowEnd.HasValue ? RowEnd.Value : int.MaxValue; + + int colStart = ColStart.HasValue ? ColStart.Value : 0; + int colEnd = ColEnd.HasValue ? ColEnd.Value : int.MaxValue; + + if (rowEnd < rowStart) + { + throw new InvalidOperationException("Starting row must be less than or equal to ending row."); + } + + if (colEnd < colStart) + { + throw new InvalidOperationException("Starting column must be less than or equal to ending column."); + } + + return Observable.Select(source, value => + { + var inputRows = value.GetLength(0); + var inputCols = value.GetLength(1); + + if (rowEnd == int.MaxValue) + { + rowEnd = inputRows; + } + + if (colEnd == int.MaxValue) + { + colEnd = inputCols; + } + + // Calculate the size of the new sliced array + int rowCount = rowEnd - rowStart; + int colCount = colEnd - colStart; + + // Initialize the new array with the calculated size + double[,] slicedArray = new double[rowCount, colCount]; + + // Copy the data from the original array to the new array + for (int i = 0; i < rowCount; i++) + { + for (int j = 0; j < colCount; j++) + { + slicedArray[i, j] = value[rowStart + i, colStart + j]; + } + } + + return slicedArray; + }); + } + } +} \ No newline at end of file diff --git a/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs b/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs index 77f70da8..29356360 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using System; using System.Xml.Serialization; +using System.Reactive.Linq; using Newtonsoft.Json; namespace Bonsai.ML.LinearDynamicalSystems @@ -53,7 +54,14 @@ public double Variance } /// - /// Extracts a single state compenent from the full state + /// Base constructor of a state component + /// + public StateComponent() + { + } + + /// + /// Constructs a state compenent from the full state and covariance matrices given an index /// public StateComponent(double[,] X, double[,] P, int i) { @@ -65,5 +73,31 @@ private double Sigma(double variance) { return 2 * Math.Sqrt(variance); } + + /// + /// Generates a state component + /// + public IObservable Process() + { + return Observable.Defer(() => Observable.Return( + new StateComponent { + Mean = _mean, + Variance = _variance + })); + } + + /// + /// Generates a state component + /// + public IObservable Process(IObservable source) + { + return Observable.Select(source, pyObject => + { + return new StateComponent { + Mean = _mean, + Variance = _variance + }; + }); + } } } diff --git a/src/Bonsai.ML.LinearDynamicalSystems/main.py b/src/Bonsai.ML.LinearDynamicalSystems/main.py index 4c99cecb..5ff25c64 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/main.py +++ b/src/Bonsai.ML.LinearDynamicalSystems/main.py @@ -79,18 +79,27 @@ class KalmanFilterLinearRegression(TimeVaryingOnlineKalmanFilter): def __init__(self, likelihood_precision_coef: float, prior_precision_coef: float, - mn: list[float], + n_features: int, + x: list[list[float]] = None, + P: list[list[float]] = None, ) -> None: - if mn is None: - self.mn = np.array([[0.0], [0.0]]) - else: - self.mn = np.array(mn) - self.likelihood_precision_coef = likelihood_precision_coef self.prior_precision_coef = prior_precision_coef - self.x = self.mn - self.P = np.eye(len(self.x)) * 1.0 / self.prior_precision_coef + self.n_features = n_features + + if x is None: + x = np.zeros((self.n_features,1), dtype=np.double) + else: + x = np.array(x) + + if P is None: + P = 1.0 / self.prior_precision_coef * np.eye(len(x)) + else: + P = np.array(P) + + self.x = x + self.P = P self.B = np.eye(N=len(self.x)) self.Q = np.zeros(shape=((len(self.x), len(self.x)))) @@ -102,7 +111,11 @@ def predict(self): self.x, self.P = super().predict(x = self.x, P = self.P, B = self.B, Q = self.Q) def update(self, x, y): - self.x, self.P = super().update(y = np.array([y]), x = self.x, P = self.P, Z = np.array([x, 1]).reshape((1, -1)), R = self.R) + if not isinstance(x, list): + x = [x] + self.x, self.P = super().update(y = np.array(y, dtype=np.float64), x = self.x, P = self.P, Z = np.array(x).reshape((1, -1)), R = self.R) + if self.x.ndim == 1: + self.x = self.x[:, np.newaxis] def pdf(self, x0 = 0, x1 = 1, xsteps = 100, y0 = 0, y1 = 1, ysteps = 100): @@ -112,10 +125,10 @@ def pdf(self, x0 = 0, x1 = 1, xsteps = 100, y0 = 0, y1 = 1, ysteps = 100): self.y0 = y0 self.y1 = y1 self.ysteps = ysteps - + rv = multivariate_normal(self.x.flatten(), self.P) - self.xpos = np.linspace(x0, x1, xsteps) - self.ypos = np.linspace(y0, y1, ysteps) - xx, yy = np.meshgrid(self.xpos, self.ypos) + xpos = np.linspace(x0, x1, xsteps) + ypos = np.linspace(y0, y1, ysteps) + xx, yy = np.meshgrid(xpos, ypos) pos = np.dstack((xx, yy)) self.pdf_values = rv.pdf(pos) \ No newline at end of file diff --git a/src/Bonsai.ML.Visualizers/Bonsai.ML.Visualizers.csproj b/src/Bonsai.ML.Visualizers/Bonsai.ML.Visualizers.csproj index 58bc3adf..1312648f 100644 --- a/src/Bonsai.ML.Visualizers/Bonsai.ML.Visualizers.csproj +++ b/src/Bonsai.ML.Visualizers/Bonsai.ML.Visualizers.csproj @@ -5,7 +5,7 @@ Bonsai Rx ML Machine Learning Visualizers net472 true - 0.1.0 + 0.1.1 diff --git a/src/Bonsai.ML.Visualizers/ColorPalettes.cs b/src/Bonsai.ML.Visualizers/ColorPalettes.cs new file mode 100644 index 00000000..e53dc712 --- /dev/null +++ b/src/Bonsai.ML.Visualizers/ColorPalettes.cs @@ -0,0 +1,20 @@ +namespace Bonsai.ML.Visualizers +{ + internal enum ColorPalettes + { + Cividis, + Inferno, + Viridis, + Magma, + Plasma, + BlackWhiteRed, + BlueWhiteRed, + Cool, + Gray, + Hot, + Hue, + HueDistinct, + Jet, + Rainbow + } +} diff --git a/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs b/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs index ea57ab22..91867197 100644 --- a/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs +++ b/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs @@ -6,7 +6,6 @@ using System; using OxyPlot.Axes; using System.Collections.Generic; -using System.Reflection; namespace Bonsai.ML.Visualizers { @@ -17,23 +16,40 @@ internal class HeatMapSeriesOxyPlotBase : UserControl private HeatMapSeries heatMapSeries; private LinearColorAxis colorAxis; - private ComboBox comboBox; - private Label label; + private ToolStripComboBox paletteComboBox; + private ToolStripLabel paletteLabel; + private int _paletteSelectedIndex; + private OxyPalette palette; - private int _numColors = 100; + private ToolStripComboBox renderMethodComboBox; + private ToolStripLabel renderMethodLabel; + private int _renderMethodSelectedIndex; + private HeatMapRenderMethod renderMethod = HeatMapRenderMethod.Bitmap; - private int _selectedIndex; + private StatusStrip statusStrip; - private OxyPalette palette; + private int _numColors = 100; /// /// Event handler which can be used to hook into events generated when the combobox values have changed. /// - public event EventHandler ComboBoxValueChanged; + public event EventHandler PaletteComboBoxValueChanged; + + public ToolStripComboBox PaletteComboBox + { + get => paletteComboBox; + } + + public event EventHandler RenderMethodComboBoxValueChanged; - public ComboBox ComboBox + public ToolStripComboBox RenderMethodComboBox { - get => comboBox; + get => renderMethodComboBox; + } + + public StatusStrip StatusStrip + { + get => statusStrip; } /// @@ -42,9 +58,10 @@ public ComboBox ComboBox /// Data source is optional, since pasing it to the constructor will populate the combobox and leave it empty otherwise. /// The selected index is only needed when the data source is provided. /// - public HeatMapSeriesOxyPlotBase(int selectedIndex, int numColors = 100) + public HeatMapSeriesOxyPlotBase(int paletteSelectedIndex, int renderMethodSelectedIndex, int numColors = 100) { - _selectedIndex = selectedIndex; + _paletteSelectedIndex = paletteSelectedIndex; + _renderMethodSelectedIndex = renderMethodSelectedIndex; _numColors = numColors; // palette = OxyPalettes.Rainbow(_numColors); Initialize(); @@ -66,8 +83,8 @@ private void Initialize() Y0 = 0, Y1 = 100, Interpolate = true, - RenderMethod = HeatMapRenderMethod.Bitmap, - CoordinateDefinition = HeatMapCoordinateDefinition.Center + RenderMethod = renderMethod, + CoordinateDefinition = HeatMapCoordinateDefinition.Edge }; colorAxis = new LinearColorAxis { @@ -80,55 +97,119 @@ private void Initialize() view.Model = model; Controls.Add(view); - label = new Label + InitilizeColorPalette(); + InitilizeRenderMethod(); + + statusStrip = new StatusStrip + { + Visible = false + }; + + statusStrip.Items.AddRange(new ToolStripItem[] { + paletteLabel, + paletteComboBox, + renderMethodLabel, + renderMethodComboBox + }); + + Controls.Add(statusStrip); + view.MouseClick += new MouseEventHandler(onMouseClick); + AutoScaleDimensions = new SizeF(6F, 13F); + } + + private void InitilizeColorPalette() + { + paletteLabel = new ToolStripLabel { Text = "Color palette:", - AutoSize = true, - Location = new Point(-200, 8), - Anchor = AnchorStyles.Top | AnchorStyles.Right + AutoSize = true }; - comboBox = new ComboBox + paletteComboBox = new ToolStripComboBox() { - Location = new Point(0, 5), - DataSource = Enum.GetValues(typeof(ColorPalettes)), - Anchor = AnchorStyles.Top | AnchorStyles.Right, - BindingContext = BindingContext + Name = "palette", + AutoSize = true, }; - Controls.Add(comboBox); - Controls.Add(label); + foreach (var value in Enum.GetValues(typeof(ColorPalettes))) + { + paletteComboBox.Items.Add(value); + } - comboBox.SelectedIndexChanged += ComboBoxSelectedIndexChanged; - comboBox.SelectedIndex = _selectedIndex; + paletteComboBox.SelectedIndexChanged += PaletteComboBoxSelectedIndexChanged; + paletteComboBox.SelectedIndex = _paletteSelectedIndex; UpdateColorPalette(); - - comboBox.BringToFront(); - label.BringToFront(); - - AutoScaleDimensions = new SizeF(6F, 13F); } - private void ComboBoxSelectedIndexChanged(object sender, EventArgs e) + private void PaletteComboBoxSelectedIndexChanged(object sender, EventArgs e) { - if (comboBox.SelectedIndex != _selectedIndex) + if (paletteComboBox.SelectedIndex != _paletteSelectedIndex) { - _selectedIndex = comboBox.SelectedIndex; + _paletteSelectedIndex = paletteComboBox.SelectedIndex; UpdateColorPalette(); - ComboBoxValueChanged?.Invoke(this, e); + PaletteComboBoxValueChanged?.Invoke(this, e); UpdatePlot(); } } private void UpdateColorPalette() { - var selectedPalette = (ColorPalettes)comboBox.Items[_selectedIndex]; + var selectedPalette = (ColorPalettes)paletteComboBox.Items[_paletteSelectedIndex]; paletteLookup.TryGetValue(selectedPalette, out Func paletteMethod); palette = paletteMethod(_numColors); colorAxis.Palette = palette; } - public void UpdateHeatMapSeries(double x0, double x1, int xsteps, double y0, double y1, int ysteps, double[,] data) + private void InitilizeRenderMethod() + { + renderMethodLabel = new ToolStripLabel + { + Text = "Render method:", + AutoSize = true + }; + + renderMethodComboBox = new ToolStripComboBox() + { + Name = "renderMethod", + AutoSize = true, + }; + + foreach (var value in Enum.GetValues(typeof(HeatMapRenderMethod))) + { + renderMethodComboBox.Items.Add(value); + } + + renderMethodComboBox.SelectedIndexChanged += renderMethodComboBoxSelectedIndexChanged; + renderMethodComboBox.SelectedIndex = _renderMethodSelectedIndex; + UpdateRenderMethod(); + } + + private void renderMethodComboBoxSelectedIndexChanged(object sender, EventArgs e) + { + if (renderMethodComboBox.SelectedIndex != _renderMethodSelectedIndex) + { + _renderMethodSelectedIndex = renderMethodComboBox.SelectedIndex; + UpdateRenderMethod(); + RenderMethodComboBoxValueChanged?.Invoke(this, e); + UpdatePlot(); + } + } + + private void UpdateRenderMethod() + { + renderMethod = (HeatMapRenderMethod)renderMethodComboBox.Items[_renderMethodSelectedIndex]; + heatMapSeries.RenderMethod = renderMethod; + } + + private void onMouseClick(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Right) + { + statusStrip.Visible = !statusStrip.Visible; + } + } + + public void UpdateHeatMapSeries(double x0, double x1, double y0, double y1, double[,] data) { heatMapSeries.X0 = x0; heatMapSeries.X1 = x1; @@ -160,22 +241,4 @@ public void UpdatePlot() { ColorPalettes.Rainbow, (numColors) => OxyPalettes.Rainbow(numColors) }, }; } - - internal enum ColorPalettes - { - Cividis, - Inferno, - Viridis, - Magma, - Plasma, - BlackWhiteRed, - BlueWhiteRed, - Cool, - Gray, - Hot, - Hue, - HueDistinct, - Jet, - Rainbow - } } diff --git a/src/Bonsai.ML.Visualizers/MultidimensionalArrayVisualizer.cs b/src/Bonsai.ML.Visualizers/MultidimensionalArrayVisualizer.cs new file mode 100644 index 00000000..456452aa --- /dev/null +++ b/src/Bonsai.ML.Visualizers/MultidimensionalArrayVisualizer.cs @@ -0,0 +1,102 @@ +using System; +using System.Windows.Forms; +using System.Collections.Generic; +using System.Reflection; +using Bonsai; +using Bonsai.Design; +using Bonsai.ML.Visualizers; +using Bonsai.ML.LinearDynamicalSystems; +using Bonsai.ML.LinearDynamicalSystems.LinearRegression; +using System.Drawing; +using System.Reactive; +using Bonsai.Reactive; +using Bonsai.Expressions; +using OxyPlot; + +[assembly: TypeVisualizer(typeof(MultidimensionalArrayVisualizer), Target = typeof(double[,]))] + +namespace Bonsai.ML.Visualizers +{ + + /// + /// Provides a type visualizer to display the state components of a Kalman Filter kinematics model. + /// + public class MultidimensionalArrayVisualizer : DialogTypeVisualizer + { + + private int paletteSelectedIndex = 0; + private int renderMethodSelectedIndex = 0; + + /// + /// Size of the window when loaded + /// + public Size Size { get; set; } = new Size(320, 240); + + /// + /// The selected index of the color palette to use + /// + public int PaletteSelectedIndex { get => paletteSelectedIndex; set => paletteSelectedIndex = value; } + public int RenderMethodSelectedIndex { get => renderMethodSelectedIndex; set => renderMethodSelectedIndex = value; } + + private HeatMapSeriesOxyPlotBase Plot; + + /// + public override void Load(IServiceProvider provider) + { + Plot = new HeatMapSeriesOxyPlotBase(paletteSelectedIndex, renderMethodSelectedIndex) + { + Size = Size, + Dock = DockStyle.Fill, + }; + + Plot.PaletteComboBoxValueChanged += PaletteIndexChanged; + Plot.RenderMethodComboBoxValueChanged += RenderMethodIndexChanged; + + var visualizerService = (IDialogTypeVisualizerService)provider.GetService(typeof(IDialogTypeVisualizerService)); + if (visualizerService != null) + { + visualizerService.AddControl(Plot); + } + } + + /// + public override void Show(object value) + { + var mdarray = (double[,])value; + var shape = new int[] {mdarray.GetLength(0), mdarray.GetLength(1)}; + + Plot.UpdateHeatMapSeries( + -0.5, + shape[0] - 0.5, + -0.5, + shape[1] - 0.5, + mdarray + ); + + Plot.UpdatePlot(); + } + + /// + public override void Unload() + { + if (!Plot.IsDisposed) + { + Plot.Dispose(); + } + } + + /// + /// Callback function to update the selected index when the selected combobox index has changed + /// + private void PaletteIndexChanged(object sender, EventArgs e) + { + var comboBox = Plot.PaletteComboBox; + paletteSelectedIndex = comboBox.SelectedIndex; + } + private void RenderMethodIndexChanged(object sender, EventArgs e) + { + var comboBox = Plot.RenderMethodComboBox; + renderMethodSelectedIndex = comboBox.SelectedIndex; + } + } +} diff --git a/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs b/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs index b194ce75..8e55be0e 100644 --- a/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs +++ b/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs @@ -22,7 +22,8 @@ namespace Bonsai.ML.Visualizers public class MultivariatePDFVisualizer : DialogTypeVisualizer { - private int selectedIndex = 0; + private int paletteSelectedIndex = 0; + private int renderMethodSelectedIndex = 0; /// /// Size of the window when loaded @@ -32,20 +33,22 @@ public class MultivariatePDFVisualizer : DialogTypeVisualizer /// /// The selected index of the color palette to use /// - public int SelectedIndex { get => selectedIndex; set => selectedIndex = value; } + public int PaletteSelectedIndex { get => paletteSelectedIndex; set => paletteSelectedIndex = value; } + public int RenderMethodSelectedIndex { get => renderMethodSelectedIndex; set => renderMethodSelectedIndex = value; } private HeatMapSeriesOxyPlotBase Plot; /// public override void Load(IServiceProvider provider) { - Plot = new HeatMapSeriesOxyPlotBase(selectedIndex) + Plot = new HeatMapSeriesOxyPlotBase(paletteSelectedIndex, renderMethodSelectedIndex) { Size = Size, Dock = DockStyle.Fill, }; - Plot.ComboBoxValueChanged += IndexChanged; + Plot.PaletteComboBoxValueChanged += PaletteIndexChanged; + Plot.RenderMethodComboBoxValueChanged += RenderMethodIndexChanged; var visualizerService = (IDialogTypeVisualizerService)provider.GetService(typeof(IDialogTypeVisualizerService)); if (visualizerService != null) @@ -59,12 +62,10 @@ public override void Show(object value) { var pdf = (MultivariatePDF)value; Plot.UpdateHeatMapSeries( - pdf.GridParameters.X0, - pdf.GridParameters.X1, - pdf.GridParameters.Xsteps, - pdf.GridParameters.Y0, - pdf.GridParameters.Y1, - pdf.GridParameters.Ysteps, + pdf.GridParameters.X0 - (1 / 2 * pdf.GridParameters.Xsteps), + pdf.GridParameters.X1 - (1 / 2 * pdf.GridParameters.Xsteps), + pdf.GridParameters.Y0 - (1 / 2 * pdf.GridParameters.Ysteps), + pdf.GridParameters.Y1 - (1 / 2 * pdf.GridParameters.Ysteps), pdf.Values ); Plot.UpdatePlot(); @@ -82,10 +83,15 @@ public override void Unload() /// /// Callback function to update the selected index when the selected combobox index has changed /// - private void IndexChanged(object sender, EventArgs e) + private void PaletteIndexChanged(object sender, EventArgs e) { - var comboBox = Plot.ComboBox; - selectedIndex = comboBox.SelectedIndex; + var comboBox = Plot.PaletteComboBox; + paletteSelectedIndex = comboBox.SelectedIndex; + } + private void RenderMethodIndexChanged(object sender, EventArgs e) + { + var comboBox = Plot.RenderMethodComboBox; + renderMethodSelectedIndex = comboBox.SelectedIndex; } } } diff --git a/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs b/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs index 4ca07104..1ca3cb82 100644 --- a/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs +++ b/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs @@ -13,8 +13,8 @@ internal class TimeSeriesOxyPlotBase : UserControl { private PlotView view; private PlotModel model; - private ComboBox comboBox; - private Label label; + private ToolStripComboBox comboBox; + private ToolStripLabel label; private string _lineSeriesName; private string _areaSeriesName; @@ -26,6 +26,8 @@ internal class TimeSeriesOxyPlotBase : UserControl private Axis xAxis; private Axis yAxis; + private StatusStrip statusStrip; + /// /// Event handler which can be used to hook into events generated when the combobox values have changed. /// @@ -41,6 +43,16 @@ internal class TimeSeriesOxyPlotBase : UserControl /// public int Capacity { get; set; } + public ToolStripComboBox ComboBox + { + get => comboBox; + } + + public StatusStrip StatusStrip + { + get => statusStrip; + } + /// /// Constructor of the TimeSeriesOxyPlotBase class. /// Requires a line series name and an area series name. @@ -56,11 +68,6 @@ public TimeSeriesOxyPlotBase(string lineSeriesName, string areaSeriesName, IEnum Initialize(); } - public ComboBox ComboBox - { - get => comboBox; - } - private void Initialize() { view = new PlotView @@ -107,37 +114,59 @@ private void Initialize() view.Model = model; Controls.Add(view); + statusStrip = new StatusStrip + { + Visible = false + }; + if (_dataSource != null) { - label = new Label - { - Text = "State component:", - AutoSize = true, - Location = new Point(-200, 8), - Anchor = AnchorStyles.Top | AnchorStyles.Right - }; - - comboBox = new ComboBox - { - Location = new Point(0, 5), - DataSource = _dataSource, - Anchor = AnchorStyles.Top | AnchorStyles.Right, - BindingContext = BindingContext - }; - - Controls.Add(comboBox); - Controls.Add(label); - - comboBox.SelectedIndexChanged += ComboBoxSelectedIndexChanged; - comboBox.SelectedIndex = _selectedIndex; - - comboBox.BringToFront(); - label.BringToFront(); + InitializeComboBox(_dataSource); + + statusStrip.Items.AddRange(new ToolStripItem[] { + label, + comboBox, + }); + + view.MouseClick += new MouseEventHandler(onMouseClick); } + Controls.Add(statusStrip); + AutoScaleDimensions = new SizeF(6F, 13F); } + private void onMouseClick(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Right) + { + statusStrip.Visible = !statusStrip.Visible; + } + } + + private void InitializeComboBox(IEnumerable dataSource) + { + label = new ToolStripLabel + { + Text = "State component:", + AutoSize = true, + }; + + comboBox = new ToolStripComboBox() + { + Name = "stateComponent", + AutoSize = true, + }; + + foreach (var value in dataSource) + { + comboBox.Items.Add(value); + } + + comboBox.SelectedIndexChanged += ComboBoxSelectedIndexChanged; + comboBox.SelectedIndex = _selectedIndex; + } + private void ComboBoxSelectedIndexChanged(object sender, EventArgs e) { if (comboBox.SelectedIndex != _selectedIndex) From 5933895f40bd30198c5e521c43ea15e838b12aad Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Mon, 25 Mar 2024 12:28:44 +0000 Subject: [PATCH 04/14] Added lds linear regression to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dd326541..282052ba 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ The Bonsai.ML project is a collection of packages with reactive infrastructure f * Bonsai.ML - provides core functionality across all Bonsai.ML packages. * Bonsai.ML.LinearDynamicalSystems - package for performing inference of linear dynamical systems. Interfaces with the [lds_python](https://github.com/joacorapela/lds_python) package. - *Bonsai.ML.LinearDynamicalSystems.Kinematics* - subpackage included in the LinearDynamicalSystems package which supports using the Kalman Filter to infer kinematic data. + - *Bonsai.ML.LinearDynamicalSystems.LinearRegression* - subpackage included in the LinearDynamicalSystems package which supports using the Kalman Filter to perform Bayesian linear regression. * Bonsai.ML.Visualizers - provides a set of visualizers for dynamic graphing/plotting. > [!NOTE] From 0e72f5286b1b1de75821db31feb1814eef81aad8 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Fri, 5 Apr 2024 15:16:39 +0100 Subject: [PATCH 05/14] Updated name of external property in workflow --- .../LinearRegression/CreateKFModel.bonsai | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai index 1638a7e0..88ed603b 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai @@ -14,7 +14,7 @@ - + @@ -24,7 +24,7 @@ 25 2 - 2 + 2 From 6dd0885aff40eeb8d6f2118bdf67d229e88ee8e2 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Sat, 18 May 2024 09:33:26 +0100 Subject: [PATCH 06/14] Refactor build solution jobs into CI pipeline --- .github/workflows/build.yml | 27 +++++++++++++++++++++++++++ .github/workflows/docs.yml | 17 +++-------------- 2 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..9efa0974 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: Build + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + workflow_call: + +jobs: + build: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + with: + submodules: true + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Restore NuGet Packages + run: msbuild -t:restore src/Bonsai.ML.sln + + - name: Build Solution + run: msbuild src/Bonsai.ML.sln /p:Configuration=Release \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f3f3a637..66e49aed 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,27 +5,16 @@ on: workflow_dispatch: jobs: - build: + build_docs: runs-on: windows-latest steps: - - name: Checkout - uses: actions/checkout@v4.1.1 - with: - submodules: true + - name: Build Solution + uses: ./.github/workflows/build.yml - name: Setup .NET Core SDK uses: actions/setup-dotnet@v4.0.0 with: dotnet-version: 7.x - - - name: Setup MSBuild - uses: microsoft/setup-msbuild@v2 - - - name: Restore NuGet Packages - run: msbuild -t:restore src/Bonsai.ML.sln - - - name: Build Solution - run: msbuild src/Bonsai.ML.sln /p:Configuration=Release - name: Setup DocFX run: dotnet tool restore From 3b005d9bebe87d089d3368675d9fbf95f038ac82 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Mon, 20 May 2024 11:54:30 +0100 Subject: [PATCH 07/14] Updated package version number to new minor release --- .../Bonsai.ML.LinearDynamicalSystems.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bonsai.ML.LinearDynamicalSystems/Bonsai.ML.LinearDynamicalSystems.csproj b/src/Bonsai.ML.LinearDynamicalSystems/Bonsai.ML.LinearDynamicalSystems.csproj index 26aaa789..f45791fc 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/Bonsai.ML.LinearDynamicalSystems.csproj +++ b/src/Bonsai.ML.LinearDynamicalSystems/Bonsai.ML.LinearDynamicalSystems.csproj @@ -4,7 +4,7 @@ A Bonsai package for implementing the Kalman Filter using python. Bonsai Rx ML KalmanFilter LinearDynamicalSystems net472;netstandard2.0 - 0.1.1 + 0.2.0 From 7b4666478149bdbcaf0de5312c0e422bbc6d135a Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Mon, 20 May 2024 11:56:31 +0100 Subject: [PATCH 08/14] Added new documentation and comments --- .../LinearRegression/GridParameters.cs | 59 ++++++++++--------- .../LinearRegression/KFModelParameters.cs | 53 +++++++++-------- .../LinearRegression/MultivariatePDF.cs | 16 +++-- .../Reshape.cs | 26 ++++++-- src/Bonsai.ML.LinearDynamicalSystems/Slice.cs | 42 +++++++++---- .../StateComponent.cs | 5 +- 6 files changed, 125 insertions(+), 76 deletions(-) diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs index 3d60a675..70cfce6d 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs @@ -8,10 +8,10 @@ namespace Bonsai.ML.LinearDynamicalSystems.LinearRegression { /// - /// 2D grid parameters used for calculating the PDF of a multivariate distribution + /// Represents an operator that creates the 2D grid parameters used for calculating the PDF of a multivariate distribution. /// - [Description("2D grid parameters used for calculating the PDF of a multivariate distribution")] - [Combinator()] + [Combinator] + [Description("Creates the 2D grid parameters used for calculating the PDF of a multivariate distribution.")] [WorkflowElementCategory(ElementCategory.Source)] public class GridParameters { @@ -33,10 +33,10 @@ public class GridParameters private string _ystepsString; /// - /// Lower bound of the x axis + /// Gets or sets the lower bound of the X axis. /// [JsonProperty("x0")] - [Description("Lower bound of the x axis")] + [Description("The lower bound of the X axis")] public double X0 { get @@ -52,10 +52,10 @@ public double X0 } /// - /// Upper bound of the x axis + /// Gets or sets the upper bound of the X axis. /// [JsonProperty("x1")] - [Description("Upper bound of the x axis")] + [Description("The upper bound of the X axis")] public double X1 { get @@ -70,11 +70,11 @@ public double X1 } /// - /// Number of steps along the x axis + /// Gets or sets the number of steps along the X axis. /// [JsonProperty("xsteps")] - [Description("Number of steps along the x axis")] - public int Xsteps + [Description("The number of steps along the X axis")] + public int XSteps { get { @@ -88,10 +88,10 @@ public int Xsteps } /// - /// Lower bound of the y axis + /// Gets or sets the lower bound of the Y axis. /// [JsonProperty("y0")] - [Description("Lower bound of the y axis")] + [Description("The lower bound of the Y axis")] public double Y0 { get @@ -106,10 +106,10 @@ public double Y0 } /// - /// Upper bound of the y axis + /// Gets or sets the upper bound of the Y axis. /// [JsonProperty("y1")] - [Description("Upper bound of the y axis")] + [Description("The upper bound of the Y axis")] public double Y1 { get @@ -124,11 +124,11 @@ public double Y1 } /// - /// Number of steps along the y axis + /// Gets or sets the number of steps along the Y axis. /// [JsonProperty("ysteps")] - [Description("Number of steps along the y axis")] - public int Ysteps + [Description("The number of steps along the Y axis")] + public int YSteps { get { @@ -148,12 +148,12 @@ public IObservable Process() { return Observable.Defer(() => Observable.Return( new GridParameters { - X0 = _x0, + X0 = _x0, X1 = _x1, - Xsteps = _xsteps, - Y0 = _y0, + XSteps = _xsteps, + Y0 = _y0, Y1 = _y1, - Ysteps = _ysteps, + YSteps = _ysteps, })); } @@ -168,23 +168,26 @@ public IObservable Process(IObservable source) }); } + /// + /// Converts a PyObject, represeting a Kalman Filter Linear Regression Model, into a GridParameters object + /// public static GridParameters ConvertPyObject(PyObject pyObject) { var x0PyObj = pyObject.GetAttr("x0"); var x1PyObj = pyObject.GetAttr("x1"); - var xstepsPyObj = pyObject.GetAttr("xsteps"); + var xStepsPyObj = pyObject.GetAttr("xsteps"); var y0PyObj = pyObject.GetAttr("y0"); var y1PyObj = pyObject.GetAttr("y1"); - var ystepsPyObj = pyObject.GetAttr("ysteps"); + var yStepsPyObj = pyObject.GetAttr("ysteps"); return new GridParameters { X0 = x0PyObj, X1 = x1PyObj, - Xsteps = xstepsPyObj, + XSteps = xStepsPyObj, Y0 = y0PyObj, Y1 = y1PyObj, - Ysteps = ystepsPyObj, + YSteps = yStepsPyObj, }; } @@ -197,10 +200,10 @@ public IObservable Process(IObservable source) new GridParameters { X0 = _x0, X1 = _x1, - Xsteps = _xsteps, + XSteps = _xsteps, Y0 = _y0, Y1 = _y1, - Ysteps = _ysteps, + YSteps = _ysteps, }); } @@ -208,7 +211,7 @@ public IObservable Process(IObservable source) public override string ToString() { - return $"x0={_x0},x1={_x1},xsteps={_xsteps},y0={_y0},y1={_y1},ysteps={_ysteps}"; + return $"x0={_x0}, x1={_x1}, xsteps={_xsteps}, y0={_y0}, y1={_y1}, ysteps={_ysteps}"; } } } diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs index 8375f92f..affbf288 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs @@ -9,10 +9,10 @@ namespace Bonsai.ML.LinearDynamicalSystems.LinearRegression { /// - /// Model parameters for a Kalman Filter Kinematics python class + /// Represents an operator that creates the model parameters for a Kalman Filter Linear Regression python class /// - [Description("Model parameters for a Kalman Filter Linear Regression (KFLR) model")] - [Combinator()] + [Combinator] + [Description("Creates the model parameters used for initializing a Kalman Filter Linear Regression (KFLR) python class")] [WorkflowElementCategory(ElementCategory.Source)] public class KFModelParameters { @@ -33,12 +33,12 @@ public class KFModelParameters private string _pString; /// - /// likelihood precision coefficient + /// Gets or sets the likelihood precision coefficient. /// [JsonProperty("likelihood_precision_coef")] - [Description("likelihood precision coefficient")] + [Description("The likelihood precision coefficient")] [Category("Parameters")] - public double LikelihoodPrecisionCoef + public double LikelihoodPrecisionCoefficient { get { @@ -52,12 +52,12 @@ public double LikelihoodPrecisionCoef } /// - /// prior precision coefficient + /// Gets or sets the prior precision coefficient. /// [JsonProperty("prior_precision_coef")] - [Description("prior precision coefficient")] + [Description("The prior precision coefficient")] [Category("Parameters")] - public double PriorPrecisionCoef + public double PriorPrecisionCoefficient { get { @@ -71,10 +71,10 @@ public double PriorPrecisionCoef } /// - /// number of features + /// Gets or sets the number of features present in the model. /// [JsonProperty("n_features")] - [Description("number of features")] + [Description("The number of features")] [Category("Parameters")] public int NumFeatures { @@ -90,11 +90,11 @@ public int NumFeatures } /// - /// matrix representing the mean of the state + /// Gets or sets the matrix representing the mean of the state. /// [XmlIgnore] [JsonProperty("x")] - [Description("matrix representing the mean of the state")] + [Description("The matrix representing the mean of the state")] [Category("ModelState")] public double[,] X { @@ -110,11 +110,11 @@ public int NumFeatures } /// - /// matrix representing the covariance of the state + /// Gets or sets the matrix representing the covariance between state components. /// [XmlIgnore] [JsonProperty("P")] - [Description("matrix representing the covariance of the state")] + [Description("The matrix representing the covariance between state components.")] [Category("ModelState")] public double[,] P { @@ -128,11 +128,14 @@ public int NumFeatures _pString = _p == null ? "None" : NumpyHelper.NumpyParser.ParseArray(_p); } } - + + /// + /// Constructs a KF Model Parameters class. + /// public KFModelParameters () { - LikelihoodPrecisionCoef = 25; - PriorPrecisionCoef = 2; + LikelihoodPrecisionCoefficient = 25; + PriorPrecisionCoefficient = 2; } /// @@ -142,8 +145,8 @@ public IObservable Process() { return Observable.Defer(() => Observable.Return( new KFModelParameters { - LikelihoodPrecisionCoef = _likelihood_precision_coef, - PriorPrecisionCoef = _prior_precision_coef, + LikelihoodPrecisionCoefficient = _likelihood_precision_coef, + PriorPrecisionCoefficient = _prior_precision_coef, NumFeatures = _n_features, X = _x, P = _p @@ -162,8 +165,8 @@ public IObservable Process(IObservable source) var n_featuresPyObj = pyObject.GetAttr("n_features"); return new KFModelParameters { - LikelihoodPrecisionCoef = likelihood_precision_coefPyObj, - PriorPrecisionCoef = _prior_precision_coef, + LikelihoodPrecisionCoefficient = likelihood_precision_coefPyObj, + PriorPrecisionCoefficient = _prior_precision_coef, NumFeatures = n_featuresPyObj, X = _x, P = _p @@ -178,8 +181,8 @@ public IObservable Process(IObservable sour { return Observable.Select(source, x => new KFModelParameters { - LikelihoodPrecisionCoef = _likelihood_precision_coef, - PriorPrecisionCoef = _prior_precision_coef, + LikelihoodPrecisionCoefficient = _likelihood_precision_coef, + PriorPrecisionCoefficient = _prior_precision_coef, NumFeatures = _n_features, X = _x, P = _p @@ -189,7 +192,7 @@ public IObservable Process(IObservable sour public override string ToString() { - return $"likelihood_precision_coef={_likelihood_precision_coefString},prior_precision_coef={_prior_precision_coefString},n_features={_n_featuresString},x={_xString},P={_pString}"; + return $"likelihood_precision_coef={_likelihood_precision_coefString}, prior_precision_coef={_prior_precision_coefString}, n_features={_n_featuresString}, x={_xString}, P={_pString}"; } } diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs index ee7c1f00..6fc92c6c 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs @@ -7,22 +7,28 @@ namespace Bonsai.ML.LinearDynamicalSystems.LinearRegression { /// - /// A class that converts a python object, representing a multivariate PDF, into a multidimensional array - /// /// - [Description("A multivariate PDF")] - [Combinator()] + /// Represents an operator that converts a python object, representing a multivariate PDF, into a multivariate PDF class. + /// + [Combinator] + [Description("Converts a python object, representing a multivariate PDF, into a multivariate PDF.")] [WorkflowElementCategory(ElementCategory.Transform)] public class MultivariatePDF { + /// + /// Gets or sets the grid parameters used for generating the multivariate PDF. + /// [XmlIgnore] public GridParameters GridParameters; + /// + /// Gets or sets the probability density value at each 2D position of the grid. + /// [XmlIgnore] public double[,] Values; /// - /// Converts a python object, representing a multivariate PDF, into a multidimensional array + /// Converts a PyObject into a multivariate PDF. /// public IObservable Process(IObservable source) { diff --git a/src/Bonsai.ML.LinearDynamicalSystems/Reshape.cs b/src/Bonsai.ML.LinearDynamicalSystems/Reshape.cs index 53d734b6..8279e6bc 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/Reshape.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/Reshape.cs @@ -1,18 +1,32 @@ using System; using System.Reactive.Linq; +using System.ComponentModel; namespace Bonsai.ML.LinearDynamicalSystems { /// - /// A class that converts a python object, representing a multivariate PDF, into a multidimensional array - /// /// - [Combinator()] + /// Represents an operator that reshapes the dimensions of a 2D multi-dimensional array. + /// + [Combinator] + [Description("Reshapes the dimensions of a 2D multi-dimensional array.")] [WorkflowElementCategory(ElementCategory.Transform)] public class Reshape { - public int Rows {get;set;} - public int Cols {get;set;} + /// + /// Gets or sets the number of rows in the reshaped array. + /// + [Description("The number of rows in the reshaped array.")] + public int Rows { get; set; } + /// + /// Gets or sets the number of columns in the reshaped array. + /// + [Description("The number of columns in the reshaped array.")] + public int Cols { get; set; } + + /// + /// Reshapes a 2D multi-dimensional array into a new multi-dimensional array with the provided number of rows and columns. + /// public IObservable Process(IObservable source) { @@ -27,7 +41,7 @@ public IObservable Process(IObservable source) if (totalElements != rows * cols) { - throw new InvalidOperationException($"Multidimensional array of shape {rows}x{cols} cannot be made from input array with a total of {totalElements} elements."); + throw new InvalidOperationException($"Multi-dimensional array of shape {rows}x{cols} cannot be made from the input array with a total of {totalElements} elements."); } double[,] reshapedArray = new double[rows, cols]; diff --git a/src/Bonsai.ML.LinearDynamicalSystems/Slice.cs b/src/Bonsai.ML.LinearDynamicalSystems/Slice.cs index 7584a611..9ea20cae 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/Slice.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/Slice.cs @@ -1,20 +1,45 @@ using System; using System.Reactive.Linq; +using System.ComponentModel; namespace Bonsai.ML.LinearDynamicalSystems { /// - /// A class that converts a python object, representing a multivariate PDF, into a multidimensional array - /// /// - [Combinator()] + /// Represents an operator that slices a 2D multi-dimensional array. + /// + [Combinator] + [Description("Slices a 2D multi-dimensional array.")] [WorkflowElementCategory(ElementCategory.Transform)] public class Slice { - public int? RowStart {get;set;} = null; - public int? RowEnd {get;set;} = null; - public int? ColStart {get;set;} = null; - public int? ColEnd {get;set;} = null; + /// + /// Gets or sets the index to begin slicing the rows of the array. A value of null indicates the first row of the array. + /// + [Description("The index to begin slicing the rows of the array. A value of null indicates the first row of the array.")] + public int? RowStart { get; set; } + + /// + /// Gets or sets the index to stop slicing the rows of the array. A value of null indicates the last row of the array. + /// + [Description("The index to stop slicing the rows of the array. A value of null indicates the last row of the array.")] + public int? RowEnd { get; set; } + + /// + /// Gets or sets the index to begin slicing the columns of the array. A value of null indicates the first column of the array. + /// + [Description("The index to begin slicing the columns of the array. A value of null indicates the first column of the array.")] + public int? ColStart { get; set; } + + /// + /// Gets or sets the index to stop slicing the columns of the array. A value of null indicates the last column of the array. + /// + [Description("The index to stop slicing the columns of the array. A value of null indicates the last column of the array.")] + public int? ColEnd { get; set; } + + /// + /// Slices a 2D multi-dimensional array into a new multi-dimensional array by extracting elements between the provided start and end indices of the rows and columns. + /// public IObservable Process(IObservable source) { @@ -49,14 +74,11 @@ public IObservable Process(IObservable source) colEnd = inputCols; } - // Calculate the size of the new sliced array int rowCount = rowEnd - rowStart; int colCount = colEnd - colStart; - // Initialize the new array with the calculated size double[,] slicedArray = new double[rowCount, colCount]; - // Copy the data from the original array to the new array for (int i = 0; i < rowCount; i++) { for (int j = 0; j < colCount; j++) diff --git a/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs b/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs index 29356360..65d1688a 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs @@ -54,14 +54,15 @@ public double Variance } /// - /// Base constructor of a state component + /// Initializes a new instance of the class. /// public StateComponent() { } /// - /// Constructs a state compenent from the full state and covariance matrices given an index + /// Initializes a new instance of the class from + /// the full state and covariance matrices given an index /// public StateComponent(double[,] X, double[,] P, int i) { From eb31906a0a34eecd5c86f9ed635218258d164f72 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Mon, 20 May 2024 12:24:08 +0100 Subject: [PATCH 09/14] Updated package version --- src/Bonsai.ML.Visualizers/Bonsai.ML.Visualizers.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bonsai.ML.Visualizers/Bonsai.ML.Visualizers.csproj b/src/Bonsai.ML.Visualizers/Bonsai.ML.Visualizers.csproj index 1312648f..b834503e 100644 --- a/src/Bonsai.ML.Visualizers/Bonsai.ML.Visualizers.csproj +++ b/src/Bonsai.ML.Visualizers/Bonsai.ML.Visualizers.csproj @@ -5,7 +5,7 @@ Bonsai Rx ML Machine Learning Visualizers net472 true - 0.1.1 + 0.2.0 From f68949682b07e29b8ad7f8778e309f744f1f0ef5 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Mon, 20 May 2024 12:24:35 +0100 Subject: [PATCH 10/14] Changed ColorPalettes to ColorPalette --- src/Bonsai.ML.Visualizers/{ColorPalettes.cs => ColorPalette.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Bonsai.ML.Visualizers/{ColorPalettes.cs => ColorPalette.cs} (89%) diff --git a/src/Bonsai.ML.Visualizers/ColorPalettes.cs b/src/Bonsai.ML.Visualizers/ColorPalette.cs similarity index 89% rename from src/Bonsai.ML.Visualizers/ColorPalettes.cs rename to src/Bonsai.ML.Visualizers/ColorPalette.cs index e53dc712..5f887fe7 100644 --- a/src/Bonsai.ML.Visualizers/ColorPalettes.cs +++ b/src/Bonsai.ML.Visualizers/ColorPalette.cs @@ -1,6 +1,6 @@ namespace Bonsai.ML.Visualizers { - internal enum ColorPalettes + internal enum ColorPalette { Cividis, Inferno, From 24a98291fb5463245dc0b879f2d8285713ffe0f8 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Mon, 20 May 2024 12:25:05 +0100 Subject: [PATCH 11/14] Updated documentation and minor code refactoring --- .../HeatMapSeriesOxyPlotBase.cs | 54 ++++++++----------- .../MultidimensionalArrayVisualizer.cs | 26 +++------ .../MultivariatePDFVisualizer.cs | 34 +++++------- 3 files changed, 43 insertions(+), 71 deletions(-) diff --git a/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs b/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs index 91867197..2d1d3705 100644 --- a/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs +++ b/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs @@ -35,17 +35,11 @@ internal class HeatMapSeriesOxyPlotBase : UserControl /// public event EventHandler PaletteComboBoxValueChanged; - public ToolStripComboBox PaletteComboBox - { - get => paletteComboBox; - } + public ToolStripComboBox PaletteComboBox => paletteComboBox; public event EventHandler RenderMethodComboBoxValueChanged; - public ToolStripComboBox RenderMethodComboBox - { - get => renderMethodComboBox; - } + public ToolStripComboBox RenderMethodComboBox => renderMethodComboBox; public StatusStrip StatusStrip { @@ -63,7 +57,6 @@ public HeatMapSeriesOxyPlotBase(int paletteSelectedIndex, int renderMethodSelect _paletteSelectedIndex = paletteSelectedIndex; _renderMethodSelectedIndex = renderMethodSelectedIndex; _numColors = numColors; - // palette = OxyPalettes.Rainbow(_numColors); Initialize(); } @@ -71,7 +64,6 @@ private void Initialize() { view = new PlotView { - Size = Size, Dock = DockStyle.Fill, }; @@ -97,8 +89,8 @@ private void Initialize() view.Model = model; Controls.Add(view); - InitilizeColorPalette(); - InitilizeRenderMethod(); + InitializeColorPalette(); + InitializeRenderMethod(); statusStrip = new StatusStrip { @@ -117,7 +109,7 @@ private void Initialize() AutoScaleDimensions = new SizeF(6F, 13F); } - private void InitilizeColorPalette() + private void InitializeColorPalette() { paletteLabel = new ToolStripLabel { @@ -131,7 +123,7 @@ private void InitilizeColorPalette() AutoSize = true, }; - foreach (var value in Enum.GetValues(typeof(ColorPalettes))) + foreach (var value in Enum.GetValues(typeof(ColorPalette))) { paletteComboBox.Items.Add(value); } @@ -154,13 +146,13 @@ private void PaletteComboBoxSelectedIndexChanged(object sender, EventArgs e) private void UpdateColorPalette() { - var selectedPalette = (ColorPalettes)paletteComboBox.Items[_paletteSelectedIndex]; + var selectedPalette = (ColorPalette)paletteComboBox.Items[_paletteSelectedIndex]; paletteLookup.TryGetValue(selectedPalette, out Func paletteMethod); palette = paletteMethod(_numColors); colorAxis.Palette = palette; } - private void InitilizeRenderMethod() + private void InitializeRenderMethod() { renderMethodLabel = new ToolStripLabel { @@ -223,22 +215,22 @@ public void UpdatePlot() model.InvalidatePlot(true); } - private static readonly Dictionary> paletteLookup = new Dictionary> + private static readonly Dictionary> paletteLookup = new Dictionary> { - { ColorPalettes.Cividis, (numColors) => OxyPalettes.Cividis(numColors) }, - { ColorPalettes.Inferno, (numColors) => OxyPalettes.Inferno(numColors) }, - { ColorPalettes.Viridis, (numColors) => OxyPalettes.Viridis(numColors) }, - { ColorPalettes.Magma, (numColors) => OxyPalettes.Magma(numColors) }, - { ColorPalettes.Plasma, (numColors) => OxyPalettes.Plasma(numColors) }, - { ColorPalettes.BlackWhiteRed, (numColors) => OxyPalettes.BlackWhiteRed(numColors) }, - { ColorPalettes.BlueWhiteRed, (numColors) => OxyPalettes.BlueWhiteRed(numColors) }, - { ColorPalettes.Cool, (numColors) => OxyPalettes.Cool(numColors) }, - { ColorPalettes.Gray, (numColors) => OxyPalettes.Gray(numColors) }, - { ColorPalettes.Hot, (numColors) => OxyPalettes.Hot(numColors) }, - { ColorPalettes.Hue, (numColors) => OxyPalettes.Hue(numColors) }, - { ColorPalettes.HueDistinct, (numColors) => OxyPalettes.HueDistinct(numColors) }, - { ColorPalettes.Jet, (numColors) => OxyPalettes.Jet(numColors) }, - { ColorPalettes.Rainbow, (numColors) => OxyPalettes.Rainbow(numColors) }, + { ColorPalette.Cividis, (numColors) => OxyPalettes.Cividis(numColors) }, + { ColorPalette.Inferno, (numColors) => OxyPalettes.Inferno(numColors) }, + { ColorPalette.Viridis, (numColors) => OxyPalettes.Viridis(numColors) }, + { ColorPalette.Magma, (numColors) => OxyPalettes.Magma(numColors) }, + { ColorPalette.Plasma, (numColors) => OxyPalettes.Plasma(numColors) }, + { ColorPalette.BlackWhiteRed, (numColors) => OxyPalettes.BlackWhiteRed(numColors) }, + { ColorPalette.BlueWhiteRed, (numColors) => OxyPalettes.BlueWhiteRed(numColors) }, + { ColorPalette.Cool, (numColors) => OxyPalettes.Cool(numColors) }, + { ColorPalette.Gray, (numColors) => OxyPalettes.Gray(numColors) }, + { ColorPalette.Hot, (numColors) => OxyPalettes.Hot(numColors) }, + { ColorPalette.Hue, (numColors) => OxyPalettes.Hue(numColors) }, + { ColorPalette.HueDistinct, (numColors) => OxyPalettes.HueDistinct(numColors) }, + { ColorPalette.Jet, (numColors) => OxyPalettes.Jet(numColors) }, + { ColorPalette.Rainbow, (numColors) => OxyPalettes.Rainbow(numColors) }, }; } } diff --git a/src/Bonsai.ML.Visualizers/MultidimensionalArrayVisualizer.cs b/src/Bonsai.ML.Visualizers/MultidimensionalArrayVisualizer.cs index 456452aa..6977f8bd 100644 --- a/src/Bonsai.ML.Visualizers/MultidimensionalArrayVisualizer.cs +++ b/src/Bonsai.ML.Visualizers/MultidimensionalArrayVisualizer.cs @@ -23,29 +23,23 @@ namespace Bonsai.ML.Visualizers /// public class MultidimensionalArrayVisualizer : DialogTypeVisualizer { - - private int paletteSelectedIndex = 0; - private int renderMethodSelectedIndex = 0; - /// - /// Size of the window when loaded + /// Gets or sets the selected index of the color palette to use. /// - public Size Size { get; set; } = new Size(320, 240); + public int PaletteSelectedIndex { get; set; } /// - /// The selected index of the color palette to use + /// Gets or sets the selected index of the render method to use. /// - public int PaletteSelectedIndex { get => paletteSelectedIndex; set => paletteSelectedIndex = value; } - public int RenderMethodSelectedIndex { get => renderMethodSelectedIndex; set => renderMethodSelectedIndex = value; } + public int RenderMethodSelectedIndex { get; set; } private HeatMapSeriesOxyPlotBase Plot; /// public override void Load(IServiceProvider provider) { - Plot = new HeatMapSeriesOxyPlotBase(paletteSelectedIndex, renderMethodSelectedIndex) + Plot = new HeatMapSeriesOxyPlotBase(PaletteSelectedIndex, RenderMethodSelectedIndex) { - Size = Size, Dock = DockStyle.Fill, }; @@ -85,18 +79,14 @@ public override void Unload() } } - /// - /// Callback function to update the selected index when the selected combobox index has changed - /// private void PaletteIndexChanged(object sender, EventArgs e) { - var comboBox = Plot.PaletteComboBox; - paletteSelectedIndex = comboBox.SelectedIndex; + PaletteSelectedIndex = Plot.PaletteComboBox.SelectedIndex; } + private void RenderMethodIndexChanged(object sender, EventArgs e) { - var comboBox = Plot.RenderMethodComboBox; - renderMethodSelectedIndex = comboBox.SelectedIndex; + RenderMethodSelectedIndex = Plot.RenderMethodComboBox.SelectedIndex; } } } diff --git a/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs b/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs index 8e55be0e..4b9012c1 100644 --- a/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs +++ b/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs @@ -21,29 +21,23 @@ namespace Bonsai.ML.Visualizers /// public class MultivariatePDFVisualizer : DialogTypeVisualizer { - - private int paletteSelectedIndex = 0; - private int renderMethodSelectedIndex = 0; - /// - /// Size of the window when loaded + /// Gets or sets the selected index of the color palette to use. /// - public Size Size { get; set; } = new Size(320, 240); + public int PaletteSelectedIndex { get; set; } /// - /// The selected index of the color palette to use + /// Gets or sets the selected index of the render method to use. /// - public int PaletteSelectedIndex { get => paletteSelectedIndex; set => paletteSelectedIndex = value; } - public int RenderMethodSelectedIndex { get => renderMethodSelectedIndex; set => renderMethodSelectedIndex = value; } + public int RenderMethodSelectedIndex { get; set; } private HeatMapSeriesOxyPlotBase Plot; /// public override void Load(IServiceProvider provider) { - Plot = new HeatMapSeriesOxyPlotBase(paletteSelectedIndex, renderMethodSelectedIndex) + Plot = new HeatMapSeriesOxyPlotBase(PaletteSelectedIndex, RenderMethodSelectedIndex) { - Size = Size, Dock = DockStyle.Fill, }; @@ -62,10 +56,10 @@ public override void Show(object value) { var pdf = (MultivariatePDF)value; Plot.UpdateHeatMapSeries( - pdf.GridParameters.X0 - (1 / 2 * pdf.GridParameters.Xsteps), - pdf.GridParameters.X1 - (1 / 2 * pdf.GridParameters.Xsteps), - pdf.GridParameters.Y0 - (1 / 2 * pdf.GridParameters.Ysteps), - pdf.GridParameters.Y1 - (1 / 2 * pdf.GridParameters.Ysteps), + pdf.GridParameters.X0 - (1 / 2 * pdf.GridParameters.XSteps), + pdf.GridParameters.X1 - (1 / 2 * pdf.GridParameters.XSteps), + pdf.GridParameters.Y0 - (1 / 2 * pdf.GridParameters.YSteps), + pdf.GridParameters.Y1 - (1 / 2 * pdf.GridParameters.YSteps), pdf.Values ); Plot.UpdatePlot(); @@ -80,18 +74,14 @@ public override void Unload() } } - /// - /// Callback function to update the selected index when the selected combobox index has changed - /// private void PaletteIndexChanged(object sender, EventArgs e) { - var comboBox = Plot.PaletteComboBox; - paletteSelectedIndex = comboBox.SelectedIndex; + PaletteSelectedIndex = Plot.PaletteComboBox.SelectedIndex; } + private void RenderMethodIndexChanged(object sender, EventArgs e) { - var comboBox = Plot.RenderMethodComboBox; - renderMethodSelectedIndex = comboBox.SelectedIndex; + RenderMethodSelectedIndex = Plot.RenderMethodComboBox.SelectedIndex; } } } From 2b15f0f4927f3c5cd3524f7d996f9402af497f21 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Mon, 20 May 2024 14:04:41 +0100 Subject: [PATCH 12/14] Updated descriptions and replaced tabs with spaces --- .../LinearRegression/CreateKFModel.bonsai | 10 +++---- .../CreateMultivariatePDF.bonsai | 10 +++---- .../LinearRegression/GridParameters.cs | 26 +++++++++---------- .../LinearRegression/KFModelParameters.cs | 22 ++++++++-------- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai index 88ed603b..e04b18e0 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai @@ -12,8 +12,8 @@ Source1 - - + + @@ -22,8 +22,8 @@ - 25 - 2 + 25 + 2 2 @@ -73,7 +73,7 @@ - model = KalmanFilterLinearRegression(likelihood_precision_coef=25,prior_precision_coef=2,n_features=2,x=None,P=None) + model = KalmanFilterLinearRegression(likelihood_precision_coef=25, prior_precision_coef=2, n_features=2, x=None, P=None) diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai index 0ee02e63..ad6b361f 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai @@ -32,19 +32,19 @@ - + - + -1 1 - 100 + 100 -1 1 - 100 + 100 @@ -76,7 +76,7 @@ - model.pdf(x0=-1,x1=1,xsteps=100,y0=-1,y1=1,ysteps=100) + model.pdf(x0=-1, x1=1, xsteps=100, y0=-1, y1=1, ysteps=100) diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs index 70cfce6d..eeec392b 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs @@ -36,7 +36,7 @@ public class GridParameters /// Gets or sets the lower bound of the X axis. /// [JsonProperty("x0")] - [Description("The lower bound of the X axis")] + [Description("The lower bound of the X axis.")] public double X0 { get @@ -55,7 +55,7 @@ public double X0 /// Gets or sets the upper bound of the X axis. /// [JsonProperty("x1")] - [Description("The upper bound of the X axis")] + [Description("The upper bound of the X axis.")] public double X1 { get @@ -73,7 +73,7 @@ public double X1 /// Gets or sets the number of steps along the X axis. /// [JsonProperty("xsteps")] - [Description("The number of steps along the X axis")] + [Description("The number of steps along the X axis.")] public int XSteps { get @@ -91,7 +91,7 @@ public int XSteps /// Gets or sets the lower bound of the Y axis. /// [JsonProperty("y0")] - [Description("The lower bound of the Y axis")] + [Description("The lower bound of the Y axis.")] public double Y0 { get @@ -109,7 +109,7 @@ public double Y0 /// Gets or sets the upper bound of the Y axis. /// [JsonProperty("y1")] - [Description("The upper bound of the Y axis")] + [Description("The upper bound of the Y axis.")] public double Y1 { get @@ -127,7 +127,7 @@ public double Y1 /// Gets or sets the number of steps along the Y axis. /// [JsonProperty("ysteps")] - [Description("The number of steps along the Y axis")] + [Description("The number of steps along the Y axis.")] public int YSteps { get @@ -146,15 +146,15 @@ public int YSteps /// public IObservable Process() { - return Observable.Defer(() => Observable.Return( - new GridParameters { + return Observable.Defer(() => Observable.Return( + new GridParameters { X0 = _x0, X1 = _x1, XSteps = _xsteps, Y0 = _y0, Y1 = _y1, YSteps = _ysteps, - })); + })); } /// @@ -162,8 +162,8 @@ public IObservable Process() /// public IObservable Process(IObservable source) { - return Observable.Select(source, pyObject => - { + return Observable.Select(source, pyObject => + { return ConvertPyObject(pyObject); }); } @@ -198,10 +198,10 @@ public IObservable Process(IObservable source) { return Observable.Select(source, x => new GridParameters { - X0 = _x0, + X0 = _x0, X1 = _x1, XSteps = _xsteps, - Y0 = _y0, + Y0 = _y0, Y1 = _y1, YSteps = _ysteps, }); diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs index affbf288..46f8ae9a 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs @@ -36,7 +36,7 @@ public class KFModelParameters /// Gets or sets the likelihood precision coefficient. /// [JsonProperty("likelihood_precision_coef")] - [Description("The likelihood precision coefficient")] + [Description("The likelihood precision coefficient.")] [Category("Parameters")] public double LikelihoodPrecisionCoefficient { @@ -55,7 +55,7 @@ public double LikelihoodPrecisionCoefficient /// Gets or sets the prior precision coefficient. /// [JsonProperty("prior_precision_coef")] - [Description("The prior precision coefficient")] + [Description("The prior precision coefficient.")] [Category("Parameters")] public double PriorPrecisionCoefficient { @@ -74,7 +74,7 @@ public double PriorPrecisionCoefficient /// Gets or sets the number of features present in the model. /// [JsonProperty("n_features")] - [Description("The number of features")] + [Description("The number of features.")] [Category("Parameters")] public int NumFeatures { @@ -94,7 +94,7 @@ public int NumFeatures /// [XmlIgnore] [JsonProperty("x")] - [Description("The matrix representing the mean of the state")] + [Description("The matrix representing the mean of the state.")] [Category("ModelState")] public double[,] X { @@ -143,14 +143,14 @@ public KFModelParameters () /// public IObservable Process() { - return Observable.Defer(() => Observable.Return( - new KFModelParameters { - LikelihoodPrecisionCoefficient = _likelihood_precision_coef, + return Observable.Defer(() => Observable.Return( + new KFModelParameters { + LikelihoodPrecisionCoefficient = _likelihood_precision_coef, PriorPrecisionCoefficient = _prior_precision_coef, NumFeatures = _n_features, X = _x, P = _p - })); + })); } /// @@ -158,8 +158,8 @@ public IObservable Process() /// public IObservable Process(IObservable source) { - return Observable.Select(source, pyObject => - { + return Observable.Select(source, pyObject => + { var likelihood_precision_coefPyObj = pyObject.GetAttr("likelihood_precision_coef"); var prior_precision_coefPyObj = pyObject.GetAttr("prior_precision_coef"); var n_featuresPyObj = pyObject.GetAttr("n_features"); @@ -181,7 +181,7 @@ public IObservable Process(IObservable sour { return Observable.Select(source, x => new KFModelParameters { - LikelihoodPrecisionCoefficient = _likelihood_precision_coef, + LikelihoodPrecisionCoefficient = _likelihood_precision_coef, PriorPrecisionCoefficient = _prior_precision_coef, NumFeatures = _n_features, X = _x, From 5a380adf4ad9bda68883609f68ddeefa6a9dd913 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Mon, 20 May 2024 14:06:05 +0100 Subject: [PATCH 13/14] Converted tabs to spaces --- src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs b/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs index 65d1688a..57d3eabc 100644 --- a/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs +++ b/src/Bonsai.ML.LinearDynamicalSystems/StateComponent.cs @@ -80,11 +80,11 @@ private double Sigma(double variance) /// public IObservable Process() { - return Observable.Defer(() => Observable.Return( - new StateComponent { + return Observable.Defer(() => Observable.Return( + new StateComponent { Mean = _mean, Variance = _variance - })); + })); } /// From b0508a1c0c1f0b0a66b9f5ae0938d9f39d889bd5 Mon Sep 17 00:00:00 2001 From: ncguilbeault Date: Mon, 20 May 2024 15:08:33 +0100 Subject: [PATCH 14/14] Minor code refactoring --- .../HeatMapSeriesOxyPlotBase.cs | 5 +---- src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs | 12 +++--------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs b/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs index 2d1d3705..ee80be9b 100644 --- a/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs +++ b/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs @@ -41,10 +41,7 @@ internal class HeatMapSeriesOxyPlotBase : UserControl public ToolStripComboBox RenderMethodComboBox => renderMethodComboBox; - public StatusStrip StatusStrip - { - get => statusStrip; - } + public StatusStrip StatusStrip => statusStrip; /// /// Constructor of the TimeSeriesOxyPlotBase class. diff --git a/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs b/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs index 1ca3cb82..2f28fd17 100644 --- a/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs +++ b/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs @@ -36,22 +36,16 @@ internal class TimeSeriesOxyPlotBase : UserControl /// /// DateTime value that determines the starting time of the data values. /// - public DateTime StartTime {get;set;} + public DateTime StartTime { get; set; } /// /// Integer value that determines how many data points should be shown along the x axis. /// public int Capacity { get; set; } - public ToolStripComboBox ComboBox - { - get => comboBox; - } + public ToolStripComboBox ComboBox => comboBox; - public StatusStrip StatusStrip - { - get => statusStrip; - } + public StatusStrip StatusStrip => statusStrip; /// /// Constructor of the TimeSeriesOxyPlotBase class.