Login  Register

Re: fitting y=f(x) data to arbitrary functions in ImageJ?

Posted by Michael Schmid on Mar 23, 2018; 2:00pm
URL: http://imagej.273.s1.nabble.com/fitting-y-f-x-data-to-arbitrary-functions-in-ImageJ-tp5020273p5020312.html

Hi Fred,

concerning (1), restricting parameter ranges:
You can have a function that returns NaN if the parameter enters an
invalid range. Then the CurveFitter will avoid this range.
Convergence will be rather bad if the best fit lies very close to a 'NaN
boundary', however.

 > On fits to some data the parameters returned are obnoxiously bad.
What is the fit function? maybe I have some idea how to improve the
situation.

Concering (2) setOffsetMultiplySlopeParams:
Parameter numbers that are not set should be -1. Note that 0 refers to
parameter 'a', 1 to 'b', etc. Since you can specify only two out of the
three arguments, at least one of (multiplyParam or slopeParam) them has
to be -1.
If you let me know the fit function, I can tell you the
setOffsetMultiplySlopeParams call should look like.

Concerning (3), NullPointerException:
I'll have a look at it.
Anyhow, if you have problems with the fit not converging properly, I
would strongly suggest setting initial parameter that give at least the
order of magnitude of the initial parameters. Better, specify also the
initialParamVariations (e.g. for each parameter 1/10 of the range that
it may have).


Michael
________________________________________________________________
On 22/03/2018 22:07, Fred Damen wrote:

> Greetings,
>
> First, thanks for expounding on CurveFitter.
>
> I have a few questions and a bug.
>
> a) Is there any way to keep the fitting within a given range for each
> parameter?  On fits to some data the parameters returned are obnoxiously bad.
> The input data resembles data that fits reasonably, i.e., I do not suspect
> that the initial parameters are outside the local minimum.  This is not using
> setOffsetMultiplySlopeParams.
>
> b) When using the method setOffsetMultiplySlopeParams and passing a value to
> doCustionFit's initialParamVariations parameter, set to what is documented as
> the default value that would be used if null is passed, the fitting is
> effectively a linear fit; see example below.  A reasonable fit is had if the
> call to setOffsetMultiplySlopeParams is commented out.  If this worked what
> kind of improvement would I expect to see in efficiency or goodnees-of-fit?
>
> b) When using the method setOffsetMultiplySlopeParams and passing a null value
> to doCustionFit's initialParamVariations parameter, a null pointer exception
> is thrown:
> ImageJ 1.50h; Java 1.8.0_91 [64-bit]; Linux 4.5.5-300.fc24.x86_64; 287MB of
> 1820MB (15%)
>
> java.lang.NullPointerException
> at ij.measure.CurveFitter.modifyInitialParamsAndVariations(CurveFitter.java:835)
> at ij.measure.CurveFitter.doFit(CurveFitter.java:178)
> at ij.measure.CurveFitter.doCustomFit(CurveFitter.java:283)
> at My_Plugin2.nonlinearFit(My_Plugin2.java:38)
> at My_Plugin2.run(My_Plugin2.java:14)
> at ij.plugin.PlugInExecuter.runCompiledPlugin(Compiler.java:318)
> at ij.plugin.PlugInExecuter.run(Compiler.java:307)
> at java.lang.Thread.run(Thread.java:745)
>
> Thanks in advance,
>
> Fred
>
> ----------------------------
> import ij.*;
> import ij.process.*;
> import ij.gui.*;
> import java.awt.*;
> import ij.plugin.*;
> import ij.plugin.frame.*;
> import ij.measure.*;
>
> public class My_Plugin2 implements PlugIn {
>
>    public void run(String arg) {
>      double[] x = {0.10,0.25,0.50,0.75,1.00,1.25,1.50,1.75,2.00,5.00};
>      double[] y =
> {1123.175,838.206,469.320,453.003,725.135,1094.360,1450.741,1787.361,2128.119,4670.120};
>      double[] p = nonlinearFit(x,y);
>      Plot plot = new Plot("","x","y");
>      plot.setLineWidth(2);
>      plot.setColor(Color.black);
>      plot.addPoints(x,y,PlotWindow.X);
>      double[] y2 = new double[y.length];
>      for(int i=0; i<y.length; i++)
>         y2[i] = Math.abs( p[1] + p[2]*Math.exp(-x[i]/p[0]) );
>      plot.setColor(Color.blue);
>      plot.addPoints(x,y2,PlotWindow.LINE);
>      plot.show();
>
>      }
>
>     private double[] nonlinearFit(double[] x, double[] y) {
>
>        CurveFitter cf = new CurveFitter(x, y);
>        double[] params = {1, 2*y[0], -(y[0]+y[y.length-1])};
>        double[] ipv = new double[params.length];
>        for(int i=0; i<ipv.length; i++)
>           ipv[i] = Math.abs(params[i]*0.1);
>
>        cf.setMaxIterations(200);
>        cf.setOffsetMultiplySlopeParams(1, 2, -1);
>        cf.doCustomFit(new UserFunction() {
>           @Override
>           public double userFunction(double[] p, double x) {
>              return Math.abs( p[1] + p[2]*Math.exp(-x/p[0]) );
>              }
>           }, params.length, "", params, ipv, false);
>        //IJ.log(cf.getResultString());
>
>        return cf.getParams();
>        }
> }
>
>
>
> On Tue, March 13, 2018 2:30 pm, Michael Schmid wrote:
>> Hi Kenneth,
>>
>> Concerning fitting an 8-parameter function:
>>
>> If the fit is not linear (as in the case of a difference of Gaussians),
>> having 8 fit parameters is a rather ambitious task, and there is a high
>> probability that the fit will run into a local minimum or some point
>> that looks like a local minimum to the fitting program.
>>
>> It would be best to reduce the number of parameters, e.g. using a fixed
>> ratio between the two sigma values in the Difference of Gaussians.
>>
>> You also need some reasonable guess for the initial values of the
>> parameters.
>>
>> For the ImageJ CurveFitter, if there are many parameters it is very
>> important to specify roughly how much the parameters can vary, these are
>> the 'initialParamVariations'
>> If cf is the CurveFitter, you will have
>>       cf.doCustomFit(UserFunction userFunction, int numParams,
>>           String formula, double[] initialParams,
>>           double[] initialParamVariations, boolean showSettings
>>
>> For the initialParamVariations, use e.g. 1/10th of how much the
>> respective parameter might deviate from the initial guess (only the
>> order of magnitude is important).
>>
>> If you have many parameters and your function can be written as, e.g.
>>       a + b*function(x; c,d,e...)
>> or
>>       a + b*x + function(x; c,d,e)
>>
>> you should also specify these parameters via
>>       cf.setOffsetMultiplySlopeParams(int offsetParam, int multiplyParam,
>> int slopeParam)
>> where 'offsetParam' is the number of the parameter that is only an
>> offset (in the examples above, 0 for 'a', 'multiplyParam' would be 1 for
>> 'b' in the first example above, or 'slopeParam' would be 1 for 'b' in
>> the second type of function above. You cannot have a 'multiplyParam' and
>> a 'slopeParam' at the same time, set the unused one to -1.
>>
>> Specifying an offsetParam and multiplyParam (or slopeParam) makes the
>> CurveFitter calculate these parameters via linear regression, so the
>> actual minimization does not include these parameters. In other words,
>> you get fewer parameters, which makes the fitting much more likely to
>> succeed.
>>
>> In my experience, if you end up with 3-4 parameters (not counting the
>> parameters eliminated by setOffsetMultiplySlopeParams), there is a good
>> chance that the fit will work very well, with 5-6 parameters it gets
>> difficult, and above 6 parameters the chance to get the correct result
>> is rather low.
>>
>> If you need to control the minimization process in detail, before
>> starting the fit, you can use
>>       Minimizer minimizer = cf.getMinimizer()
>> to get access to the Minimizer that will be used and you can use the
>> Minimizer's methods to control its behavior (e.g. allow it to do more
>> steps than by default by minimizer.setMaxIterations, setting it to try
>> more restarts, use different error values for more/less accurate
>> convergence, etc.
>>
>> Best see the documentation in the source code, e.g.
>>     https://github.com/imagej/imagej1/blob/master/ij/measure/CurveFitter.java
>>     https://github.com/imagej/imagej1/blob/master/ij/measure/Minimizer.java
>>
>> -------------
>>
>>   > Bonus question for Java Gurus:
>>   > How to declare the user function as a variable, call it,
>>   > and pass it to another function?
>>
>> public class MyFunction implements UserFunction {...
>>     public double userFunction(double[] params, double x) {
>>       return params[0]+params[1]*x;
>>     }
>> }
>>
>> public class PassingClass { ...
>>     UserFunction exampleFunction = new MyFunction(...);
>>     otherClass.doFitting(xData, yData, exampleFunction)
>> }
>>
>> Public class OtherClass { ...
>>     public void doFitting(double[] xData, double[] yData,
>>           UserFunction userFunction) {
>>       CurveFitter cf = new CurveFitter(xData, yData);
>>       cf.doCustomFit(userFunction, /*numParams=*/2, null,
>>           null, null, false);
>>     }
>> }
>>
>> -------------
>>
>> Best,
>>
>> Michael
>> ________________________________________________________________
>>
>>
>> On 13/03/2018 18:56, Fred Damen wrote:
>>> Below is a routine to fit MRI Inversion Recover data for T1.
>>>
>>> Note:
>>> a) CurveFitter comes with ImageJ.
>>> b) Calls are made to UserFunction once for each x.
>>> c) If your initial guess is not close it does not seem to converge.
>>>
>>> Bonus question for Java Gurus:
>>> How to declare the user function as a variable, call it, and pass it to
>>> another function?
>>>
>>> Enjoy,
>>>
>>> Fred
>>>
>>>      private double[] nonlinearFit(double[] x, double[] y) {
>>>
>>>         CurveFitter cf = new CurveFitter(x, y);
>>>         double[] params = {1, 2*y[0], -(y[0]+y[y.length-1])};
>>>
>>>         cf.setMaxIterations(200);
>>>         cf.doCustomFit(new UserFunction() {
>>>            @Override
>>>            public double userFunction(double[] p, double x) {
>>>               return Math.abs( p[1] + p[2]*Math.exp(-x/p[0]) );
>>>               }
>>>            }, params.length, "", params, null, false);
>>>         //IJ.log(cf.getResultString());
>>>
>>>         return cf.getParams();
>>>         }
>>>
>>>
>>> On Tue, March 13, 2018 11:06 am, Kenneth Sloan wrote:
>>>> I have some simple data: samples of y=f(x) at regularly spaced discrete
>>>> values
>>>> for x.  The data
>>>> is born as a simple array of y values, but I can turn that into (for
>>>> example)
>>>> a polyline, if that
>>>> will help.  I'm currently doing that to draw the data as an Overlay.  The
>>>> size
>>>> of the y array is between 500 and 1000. (think 1 y value for every integer
>>>> x-coordinate in an image - some y values may be recorded as "missing").
>>>>
>>>> Is there an ImageJ tool that will fit (more or less) arbitrary functions to
>>>> this data?  Approximately 8 parameters.
>>>>
>>>> The particular function I have in mind at the moment is a difference of
>>>> Gaussians.  The Gaussians
>>>> most likely have the same location (in x) - but this is not guaranteed, and
>>>> I'd prefer
>>>> to use this as a sanity check rather than impose it as a constraint.
>>>>
>>>> Note that the context is a Java plugin - not a macro.
>>>>
>>>> If not in ImageJ, perhaps someone could point me at a Java package that
>>>> will
>>>> do this.  I am far
>>>> from an expert in curve fitting, so please be gentle.
>>>>
>>>> If not, I can do it in R - but I prefer to do it "on the fly" while
>>>> displaying
>>>> the image, and the Overlay.
>>>>
>>>> --
>>>> Kenneth Sloan
>>>> [hidden email]
>>>> Vision is the art of seeing what is invisible to others.
>>>>
>>>> --
>>>> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>>>>
>>>
>>> --
>>> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>>>
>>
>> --
>> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>>
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html