Custom fitting plugins#
The profile fitting tool is designed to be easily extended, so that you can plug in your own model fitting code worrying about GUI code. We will walk through several examples of custom fitting plugins, to demonstrate the various features of the plugin system.
Simple line fitter#
Our first example is a simple linear model:
1from glue.core.fitters import BaseFitter1D 2from glue.config import fit_plugin 3import numpy as np 4 5 6@fit_plugin 7class LineFit(BaseFitter1D): 8 label = "Line" 9 10 def fit(self, x, y, dy, constraints): 11 return np.polyfit(x, y, 1) 12 13 def predict(self, fit_result, x): 14 return np.polyval(fit_result, x)
Let’s look at this line by line:
Line 6 wraps a subclass of BaseFitter1D in the
All plugins follow this basic structure.
Line 8 gives this class a label, which is used in the GUI to label this model in the model selection dropdown.
Line 10 overrides the
fit() method. All plugins must implement fit, which
takes at least 4 parameters:
x: A numpy array of X values
y: A numpy array of Y values
dy: A numpy array of the errors on each Y value, or none
constraints: A dictionary of constraints (more on this later)
The fit method can do whatever it wants. Here, we are using
numpy.polyfit() to fit a 1st-order polynomial to the data. We ignore dy and constraints.
We return the result from polyfit – Glue doesn’t care what fit returns,
it just passes that to other methods (as we will now see)
Line 13 overrides the
predict() method. Again, all models must define this method.
It takes 2 inputs – whatever was returned from
fit(), and an array of X values
to evaluate the model at. This method must return a array of model-predicted Y
values at each X location. We use
numpy.polyval() to do this
This code is enough to let us fit lines to data:
In order for Glue to find this code, we need to copy this file to the same directory as config.py (
~/.glue by default), and add
import line_fit_plugin to
Polynomial fitter, with Options#
Generalizing the line fitter above to higher degree polynomials is trivial, since
polyfit/polyval both handle this case. We might want to make the degree of the fit a user-settable
parameter. We can do this by adding a UI
option, and a few keywords to our class:
1from glue.core.fitters import BaseFitter1D 2from glue.core.simpleforms import IntOption 3from glue.config import fit_plugin 4import numpy as np 5 6 7@fit_plugin 8class PolynomialFitter(BaseFitter1D): 9 label = "Polynomial" 10 degree = IntOption(min=0, max=5, default=3, label="Polynomial Degree") 11 12 def fit(self, x, y, dy, constraints, degree=2): 13 return np.polyfit(x, y, degree) 14 15 def predict(self, fit_result, x): 16 return np.polyval(fit_result, x) 17 18 def summarize(self, fit_result, x, y, dy=None): 19 return "Coefficients:\n" + "\n".join("%e" % coeff 20 for coeff in fit_result.tolist())
This code adds a few new features:
Line 10 adds an
IntOption named degree to the class. Likewise,
the fit method takes a keyword named degree, and uses this to fit a
polynomial of order
degree (e.g., degree=2 corresponds to a parabola).
This extra information allows Glue to add a widget to the settings window:
This plugin also overrides the
summarize() method. Summarize returns a string, which is used as the display in the fit summary window.
Model with constraints#
Models like those found in
astropy.modeling support fixing or
constraining certain parameters. If you would like to add user-settable
constraints to your model, add a
param_names list to the class:
class ConstrainedGaussian(BaseFitter1D): param_names = ['amplitude'] ...
Glue uses this information to let the user fix or limit parameters
in the settings tab. This information is passed to the
constraints is a dictionary whose keys are
parameter names. Each value is itself a dictionary with 4 entries:
valueof the parameter, or None if not set by the user
fixed, which is True if the parameter should be held fixed
limits, which is None if the value is unconstrained, or a list of minimum/maximum allowed values
from astropy.modeling import models, fitting @fit_plugin class Gaussian(AstropyFitter1D): model_cls = models.Gaussian1D fitting_cls = fitting.NonLinearLSQFitter label = "Gaussian" def parameter_guesses(self, x, y, dy): return dict(amplitude=1, stddev=1, mean=1)
parameter_guesses() method is optional, and provides initial guesses
for the model parameters if they weren’t set by the user.
Fit plugins can also override the
plot() method, to customize how the model fit is drawn on the profile.
Example: Gaussian fitting with Emcee#
emcee plugin example combines many
of these ideas.