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
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]
diff --git a/src/Bonsai.ML.LinearDynamicalSystems/Bonsai.ML.LinearDynamicalSystems.csproj b/src/Bonsai.ML.LinearDynamicalSystems/Bonsai.ML.LinearDynamicalSystems.csproj
index 4f672b63..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.0
+ 0.2.0
diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai
new file mode 100644
index 00000000..e04b18e0
--- /dev/null
+++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateKFModel.bonsai
@@ -0,0 +1,112 @@
+
+
+
+
+
+ Source1
+
+
+
+
+
+
+
+
+
+
+
+
+ 25
+ 2
+ 2
+
+
+
+
+
+
+
+ model
+
+
+
+ model
+
+
+ Name
+
+
+
+
+
+ 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)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
new file mode 100644
index 00000000..ad6b361f
--- /dev/null
+++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/CreateMultivariatePDF.bonsai
@@ -0,0 +1,136 @@
+
+
+
+
+
+ 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..eeec392b
--- /dev/null
+++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/GridParameters.cs
@@ -0,0 +1,217 @@
+using System.ComponentModel;
+using System;
+using System.Reactive.Linq;
+using Newtonsoft.Json;
+using Python.Runtime;
+
+namespace Bonsai.ML.LinearDynamicalSystems.LinearRegression
+{
+
+ ///
+ /// Represents an operator that creates the 2D grid parameters used for calculating the PDF of a multivariate distribution.
+ ///
+ [Combinator]
+ [Description("Creates the 2D grid parameters used for calculating the PDF of a multivariate distribution.")]
+ [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;
+
+ ///
+ /// Gets or sets the lower bound of the X axis.
+ ///
+ [JsonProperty("x0")]
+ [Description("The lower bound of the X axis.")]
+ public double X0
+ {
+ get
+ {
+ return _x0;
+ }
+ set
+ {
+ _x0 = value;
+ _x0String = double.IsNaN(_x0) ? "None" : _x0.ToString();
+
+ }
+ }
+
+ ///
+ /// Gets or sets the upper bound of the X axis.
+ ///
+ [JsonProperty("x1")]
+ [Description("The upper bound of the X axis.")]
+ public double X1
+ {
+ get
+ {
+ return _x1;
+ }
+ set
+ {
+ _x1 = value;
+ _x1String = double.IsNaN(_x1) ? "None" : _x1.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets the number of steps along the X axis.
+ ///
+ [JsonProperty("xsteps")]
+ [Description("The number of steps along the X axis.")]
+ public int XSteps
+ {
+ get
+ {
+ return _xsteps;
+ }
+ set
+ {
+ _xsteps = value >= 0 ? value : _xsteps;
+ _xstepsString = _xsteps.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets the lower bound of the Y axis.
+ ///
+ [JsonProperty("y0")]
+ [Description("The lower bound of the Y axis.")]
+ public double Y0
+ {
+ get
+ {
+ return _y0;
+ }
+ set
+ {
+ _y0 = value;
+ _y0String = double.IsNaN(_y0) ? "None" : _y0.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets the upper bound of the Y axis.
+ ///
+ [JsonProperty("y1")]
+ [Description("The upper bound of the Y axis.")]
+ public double Y1
+ {
+ get
+ {
+ return _y1;
+ }
+ set
+ {
+ _y1 = value;
+ _y1String = double.IsNaN(_y1) ? "None" : _y1.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets the number of steps along the Y axis.
+ ///
+ [JsonProperty("ysteps")]
+ [Description("The 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);
+ });
+ }
+
+ ///
+ /// 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 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
new file mode 100644
index 00000000..46f8ae9a
--- /dev/null
+++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/KFModelParameters.cs
@@ -0,0 +1,199 @@
+using System.ComponentModel;
+using Newtonsoft.Json;
+using System;
+using System.Reactive.Linq;
+using Python.Runtime;
+using System.Xml.Serialization;
+
+namespace Bonsai.ML.LinearDynamicalSystems.LinearRegression
+{
+
+ ///
+ /// Represents an operator that creates the model parameters for a Kalman Filter Linear Regression python class
+ ///
+ [Combinator]
+ [Description("Creates the model parameters used for initializing a Kalman Filter Linear Regression (KFLR) python class")]
+ [WorkflowElementCategory(ElementCategory.Source)]
+ public class KFModelParameters
+ {
+
+ private double _likelihood_precision_coef;
+
+ private double _prior_precision_coef;
+
+ private int _n_features;
+
+ private double[,] _x = null;
+ private double[,] _p = null;
+
+ private string _likelihood_precision_coefString;
+ private string _prior_precision_coefString;
+ private string _n_featuresString;
+ private string _xString;
+ private string _pString;
+
+ ///
+ /// Gets or sets the likelihood precision coefficient.
+ ///
+ [JsonProperty("likelihood_precision_coef")]
+ [Description("The likelihood precision coefficient.")]
+ [Category("Parameters")]
+ public double LikelihoodPrecisionCoefficient
+ {
+ get
+ {
+ return _likelihood_precision_coef;
+ }
+ set
+ {
+ _likelihood_precision_coef = value;
+ _likelihood_precision_coefString = double.IsNaN(_likelihood_precision_coef) ? "None" : _likelihood_precision_coef.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets the prior precision coefficient.
+ ///
+ [JsonProperty("prior_precision_coef")]
+ [Description("The prior precision coefficient.")]
+ [Category("Parameters")]
+ public double PriorPrecisionCoefficient
+ {
+ get
+ {
+ return _prior_precision_coef;
+ }
+ set
+ {
+ _prior_precision_coef = value;
+ _prior_precision_coefString = double.IsNaN(_prior_precision_coef) ? "None" : _prior_precision_coef.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets the number of features present in the model.
+ ///
+ [JsonProperty("n_features")]
+ [Description("The number of features.")]
+ [Category("Parameters")]
+ public int NumFeatures
+ {
+ get
+ {
+ return _n_features;
+ }
+ set
+ {
+ _n_features = value;
+ _n_featuresString = _n_features.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets the matrix representing the mean of the state.
+ ///
+ [XmlIgnore]
+ [JsonProperty("x")]
+ [Description("The 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);
+ }
+ }
+
+ ///
+ /// Gets or sets the matrix representing the covariance between state components.
+ ///
+ [XmlIgnore]
+ [JsonProperty("P")]
+ [Description("The matrix representing the covariance between state components.")]
+ [Category("ModelState")]
+ public double[,] P
+ {
+ get
+ {
+ return _p;
+ }
+ set
+ {
+ _p = value;
+ _pString = _p == null ? "None" : NumpyHelper.NumpyParser.ParseArray(_p);
+ }
+ }
+
+ ///
+ /// Constructs a KF Model Parameters class.
+ ///
+ public KFModelParameters ()
+ {
+ LikelihoodPrecisionCoefficient = 25;
+ PriorPrecisionCoefficient = 2;
+ }
+
+ ///
+ /// Generates parameters for a Kalman Filter Linear Regression Model
+ ///
+ public IObservable Process()
+ {
+ return Observable.Defer(() => Observable.Return(
+ new KFModelParameters {
+ LikelihoodPrecisionCoefficient = _likelihood_precision_coef,
+ PriorPrecisionCoefficient = _prior_precision_coef,
+ NumFeatures = _n_features,
+ X = _x,
+ P = _p
+ }));
+ }
+
+ ///
+ /// 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 = pyObject.GetAttr("likelihood_precision_coef");
+ var prior_precision_coefPyObj = pyObject.GetAttr("prior_precision_coef");
+ var n_featuresPyObj = pyObject.GetAttr("n_features");
+
+ return new KFModelParameters {
+ LikelihoodPrecisionCoefficient = likelihood_precision_coefPyObj,
+ PriorPrecisionCoefficient = _prior_precision_coef,
+ NumFeatures = n_featuresPyObj,
+ X = _x,
+ P = _p
+ };
+ });
+ }
+
+ ///
+ /// Generates parameters for a Kalman Filter Linear Regression Model on each input
+ ///
+ public IObservable Process(IObservable source)
+ {
+ return Observable.Select(source, x =>
+ new KFModelParameters {
+ LikelihoodPrecisionCoefficient = _likelihood_precision_coef,
+ PriorPrecisionCoefficient = _prior_precision_coef,
+ NumFeatures = _n_features,
+ X = _x,
+ P = _p
+ });
+ }
+
+ 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}";
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs
new file mode 100644
index 00000000..6fc92c6c
--- /dev/null
+++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/MultivariatePDF.cs
@@ -0,0 +1,46 @@
+using System.ComponentModel;
+using System;
+using System.Reactive.Linq;
+using Python.Runtime;
+using System.Xml.Serialization;
+
+namespace Bonsai.ML.LinearDynamicalSystems.LinearRegression
+{
+ ///
+ /// 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 PyObject into a multivariate PDF.
+ ///
+ 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
new file mode 100644
index 00000000..75aa6fea
--- /dev/null
+++ b/src/Bonsai.ML.LinearDynamicalSystems/LinearRegression/PerformInference.bonsai
@@ -0,0 +1,149 @@
+
+
+
+
+
+ Source1
+
+
+
+
+
+
+
+
+ model
+
+
+ Name
+
+
+
+
+
+ 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/Reshape.cs b/src/Bonsai.ML.LinearDynamicalSystems/Reshape.cs
new file mode 100644
index 00000000..8279e6bc
--- /dev/null
+++ b/src/Bonsai.ML.LinearDynamicalSystems/Reshape.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Reactive.Linq;
+using System.ComponentModel;
+
+namespace Bonsai.ML.LinearDynamicalSystems
+{
+ ///
+ /// 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
+ {
+ ///
+ /// 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)
+ {
+
+ 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($"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];
+
+ 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/SerializeToJson.cs b/src/Bonsai.ML.LinearDynamicalSystems/SerializeToJson.cs
index f01437ca..1cbf5a64 100644
--- a/src/Bonsai.ML.LinearDynamicalSystems/SerializeToJson.cs
+++ b/src/Bonsai.ML.LinearDynamicalSystems/SerializeToJson.cs
@@ -114,5 +114,10 @@ public IObservable Process(IObservable source)
{
return Process(source);
}
+
+ public IObservable Process(IObservable source)
+ {
+ return Process(source);
+ }
}
}
diff --git a/src/Bonsai.ML.LinearDynamicalSystems/Slice.cs b/src/Bonsai.ML.LinearDynamicalSystems/Slice.cs
new file mode 100644
index 00000000..9ea20cae
--- /dev/null
+++ b/src/Bonsai.ML.LinearDynamicalSystems/Slice.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Reactive.Linq;
+using System.ComponentModel;
+
+namespace Bonsai.ML.LinearDynamicalSystems
+{
+ ///
+ /// Represents an operator that slices a 2D multi-dimensional array.
+ ///
+ [Combinator]
+ [Description("Slices a 2D multi-dimensional array.")]
+ [WorkflowElementCategory(ElementCategory.Transform)]
+ public class Slice
+ {
+
+ ///
+ /// 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)
+ {
+
+ 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;
+ }
+
+ int rowCount = rowEnd - rowStart;
+ int colCount = colEnd - colStart;
+
+ double[,] slicedArray = new double[rowCount, colCount];
+
+ 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..57d3eabc 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,15 @@ public double Variance
}
///
- /// Extracts a single state compenent from the full state
+ /// Initializes a new instance of the class.
+ ///
+ public StateComponent()
+ {
+ }
+
+ ///
+ /// 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)
{
@@ -65,5 +74,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 b56c401b..5ff25c64 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,61 @@ 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,
+ n_features: int,
+ x: list[list[float]] = None,
+ P: list[list[float]] = None,
+ ) -> None:
+
+ self.likelihood_precision_coef = likelihood_precision_coef
+ self.prior_precision_coef = 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))))
+ 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):
+ 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):
+
+ 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)
+ 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..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.0
+ 0.2.0
diff --git a/src/Bonsai.ML.Visualizers/ColorPalette.cs b/src/Bonsai.ML.Visualizers/ColorPalette.cs
new file mode 100644
index 00000000..5f887fe7
--- /dev/null
+++ b/src/Bonsai.ML.Visualizers/ColorPalette.cs
@@ -0,0 +1,20 @@
+namespace Bonsai.ML.Visualizers
+{
+ internal enum ColorPalette
+ {
+ 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
new file mode 100644
index 00000000..ee80be9b
--- /dev/null
+++ b/src/Bonsai.ML.Visualizers/HeatMapSeriesOxyPlotBase.cs
@@ -0,0 +1,233 @@
+using System.Windows.Forms;
+using OxyPlot;
+using OxyPlot.Series;
+using OxyPlot.WindowsForms;
+using System.Drawing;
+using System;
+using OxyPlot.Axes;
+using System.Collections.Generic;
+
+namespace Bonsai.ML.Visualizers
+{
+ internal class HeatMapSeriesOxyPlotBase : UserControl
+ {
+ private PlotView view;
+ private PlotModel model;
+ private HeatMapSeries heatMapSeries;
+ private LinearColorAxis colorAxis;
+
+ private ToolStripComboBox paletteComboBox;
+ private ToolStripLabel paletteLabel;
+ private int _paletteSelectedIndex;
+ private OxyPalette palette;
+
+ private ToolStripComboBox renderMethodComboBox;
+ private ToolStripLabel renderMethodLabel;
+ private int _renderMethodSelectedIndex;
+ private HeatMapRenderMethod renderMethod = HeatMapRenderMethod.Bitmap;
+
+ private StatusStrip statusStrip;
+
+ private int _numColors = 100;
+
+ ///
+ /// Event handler which can be used to hook into events generated when the combobox values have changed.
+ ///
+ public event EventHandler PaletteComboBoxValueChanged;
+
+ public ToolStripComboBox PaletteComboBox => paletteComboBox;
+
+ public event EventHandler RenderMethodComboBoxValueChanged;
+
+ public ToolStripComboBox RenderMethodComboBox => renderMethodComboBox;
+
+ public StatusStrip StatusStrip => statusStrip;
+
+ ///
+ /// 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 paletteSelectedIndex, int renderMethodSelectedIndex, int numColors = 100)
+ {
+ _paletteSelectedIndex = paletteSelectedIndex;
+ _renderMethodSelectedIndex = renderMethodSelectedIndex;
+ _numColors = numColors;
+ Initialize();
+ }
+
+ private void Initialize()
+ {
+ view = new PlotView
+ {
+ Dock = DockStyle.Fill,
+ };
+
+ model = new PlotModel();
+
+ heatMapSeries = new HeatMapSeries {
+ X0 = 0,
+ X1 = 100,
+ Y0 = 0,
+ Y1 = 100,
+ Interpolate = true,
+ RenderMethod = renderMethod,
+ CoordinateDefinition = HeatMapCoordinateDefinition.Edge
+ };
+
+ colorAxis = new LinearColorAxis {
+ Position = AxisPosition.Right,
+ };
+
+ model.Axes.Add(colorAxis);
+ model.Series.Add(heatMapSeries);
+
+ view.Model = model;
+ Controls.Add(view);
+
+ InitializeColorPalette();
+ InitializeRenderMethod();
+
+ 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 InitializeColorPalette()
+ {
+ paletteLabel = new ToolStripLabel
+ {
+ Text = "Color palette:",
+ AutoSize = true
+ };
+
+ paletteComboBox = new ToolStripComboBox()
+ {
+ Name = "palette",
+ AutoSize = true,
+ };
+
+ foreach (var value in Enum.GetValues(typeof(ColorPalette)))
+ {
+ paletteComboBox.Items.Add(value);
+ }
+
+ paletteComboBox.SelectedIndexChanged += PaletteComboBoxSelectedIndexChanged;
+ paletteComboBox.SelectedIndex = _paletteSelectedIndex;
+ UpdateColorPalette();
+ }
+
+ private void PaletteComboBoxSelectedIndexChanged(object sender, EventArgs e)
+ {
+ if (paletteComboBox.SelectedIndex != _paletteSelectedIndex)
+ {
+ _paletteSelectedIndex = paletteComboBox.SelectedIndex;
+ UpdateColorPalette();
+ PaletteComboBoxValueChanged?.Invoke(this, e);
+ UpdatePlot();
+ }
+ }
+
+ private void UpdateColorPalette()
+ {
+ var selectedPalette = (ColorPalette)paletteComboBox.Items[_paletteSelectedIndex];
+ paletteLookup.TryGetValue(selectedPalette, out Func paletteMethod);
+ palette = paletteMethod(_numColors);
+ colorAxis.Palette = palette;
+ }
+
+ private void InitializeRenderMethod()
+ {
+ 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;
+ heatMapSeries.Y0 = y0;
+ heatMapSeries.Y1 = y1;
+ heatMapSeries.Data = data;
+ }
+
+ public void UpdatePlot()
+ {
+ model.InvalidatePlot(true);
+ }
+
+ private static readonly Dictionary> paletteLookup = new Dictionary>
+ {
+ { 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
new file mode 100644
index 00000000..6977f8bd
--- /dev/null
+++ b/src/Bonsai.ML.Visualizers/MultidimensionalArrayVisualizer.cs
@@ -0,0 +1,92 @@
+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
+ {
+ ///
+ /// Gets or sets the selected index of the color palette to use.
+ ///
+ public int PaletteSelectedIndex { get; set; }
+
+ ///
+ /// Gets or sets the selected index of the render method to use.
+ ///
+ public int RenderMethodSelectedIndex { get; set; }
+
+ private HeatMapSeriesOxyPlotBase Plot;
+
+ ///
+ public override void Load(IServiceProvider provider)
+ {
+ Plot = new HeatMapSeriesOxyPlotBase(PaletteSelectedIndex, RenderMethodSelectedIndex)
+ {
+ 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();
+ }
+ }
+
+ private void PaletteIndexChanged(object sender, EventArgs e)
+ {
+ PaletteSelectedIndex = Plot.PaletteComboBox.SelectedIndex;
+ }
+
+ private void RenderMethodIndexChanged(object sender, EventArgs e)
+ {
+ RenderMethodSelectedIndex = Plot.RenderMethodComboBox.SelectedIndex;
+ }
+ }
+}
diff --git a/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs b/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs
new file mode 100644
index 00000000..4b9012c1
--- /dev/null
+++ b/src/Bonsai.ML.Visualizers/MultivariatePDFVisualizer.cs
@@ -0,0 +1,87 @@
+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
+ {
+ ///
+ /// Gets or sets the selected index of the color palette to use.
+ ///
+ public int PaletteSelectedIndex { get; set; }
+
+ ///
+ /// Gets or sets the selected index of the render method to use.
+ ///
+ public int RenderMethodSelectedIndex { get; set; }
+
+ private HeatMapSeriesOxyPlotBase Plot;
+
+ ///
+ public override void Load(IServiceProvider provider)
+ {
+ Plot = new HeatMapSeriesOxyPlotBase(PaletteSelectedIndex, RenderMethodSelectedIndex)
+ {
+ 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 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.Values
+ );
+ Plot.UpdatePlot();
+ }
+
+ ///
+ public override void Unload()
+ {
+ if (!Plot.IsDisposed)
+ {
+ Plot.Dispose();
+ }
+ }
+
+ private void PaletteIndexChanged(object sender, EventArgs e)
+ {
+ PaletteSelectedIndex = Plot.PaletteComboBox.SelectedIndex;
+ }
+
+ private void RenderMethodIndexChanged(object sender, EventArgs e)
+ {
+ RenderMethodSelectedIndex = Plot.RenderMethodComboBox.SelectedIndex;
+ }
+ }
+}
diff --git a/src/Bonsai.ML.Visualizers/StateComponentVisualizer.cs b/src/Bonsai.ML.Visualizers/StateComponentVisualizer.cs
index 6a71301c..716aa75e 100644
--- a/src/Bonsai.ML.Visualizers/StateComponentVisualizer.cs
+++ b/src/Bonsai.ML.Visualizers/StateComponentVisualizer.cs
@@ -80,7 +80,6 @@ protected override void Show(DateTime time, object value)
{
_startTime = time;
Plot.StartTime = _startTime.Value;
- // Plot.ResetSeries();
Plot.ResetAxes();
}
diff --git a/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs b/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs
index b4e2e440..9256364e 100644
--- a/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs
+++ b/src/Bonsai.ML.Visualizers/TimeSeriesOxyPlotBase.cs
@@ -31,6 +31,8 @@ internal class TimeSeriesOxyPlotBase : UserControl
///
public int Capacity { get; set; }
+ public StatusStrip StatusStrip => statusStrip;
+
///
/// Buffer the data beyond the capacity.
///
@@ -84,6 +86,8 @@ private void Initialize()
view.MouseClick += new MouseEventHandler(onMouseClick);
Controls.Add(statusStrip);
+ Controls.Add(statusStrip);
+
AutoScaleDimensions = new SizeF(6F, 13F);
}