Login  Register

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

Posted by Fred Damen on Mar 23, 2018; 5:42pm
URL: http://imagej.273.s1.nabble.com/fitting-y-f-x-data-to-arbitrary-functions-in-ImageJ-tp5020273p5020323.html

Thanks Herbie for spotting this.

Yep, I'm an idiot.  This is why the setOffsetMultiplySlopeParams does not
help.  The actual data is complex and I only have the magnitude version.

Some times the hardest things to see are staring you in the face,

Fred

On Fri, March 23, 2018 12:30 pm, Herbie wrote:

> Sorry to intrude,
>
> but the function in question is
>
> Math.abs( p[1] + p[2]*Math.exp(-x/p[0]) )
>
> where the absolute value may make the situation more complicated.
>
> Best regards
>
> Herbie
>
> ::::::::::::::::::::::::::::::::::::::::
> Am 23.03.18 um 17:21 schrieb Fred Damen:
>> Greetings Michael,
>>
>> Thanks for the reply.
>>
>> I must have missed the NaN trick in the documentation.
>>
>> The fit function (UserFunction), is:
>>             public double userFunction(double[] p, double x) {
>>                return Math.abs( p[1] + p[2]*Math.exp(-x/p[0]) );
>>                }
>>
>> which was in the example plugin below my signature, which includes sample
>> data
>> and plot for the fit mentioned in 2 and can produce the exception if the
>> variable 'ipv' is replaced with the null value.
>>
>> Note that without the call to setOffsetMultiplySlopeParams the fits are
>> acceptable almost all of the time,  i.e., only about 10-20 apparently valid
>> datum fail with obnoxious results out of 40%*64x64 fits.
>>
>> My main interest in using the method setOffsetMultiplySlopeParams is
>> efficiency.  Right now on a fast computer it takes about 2-5 seconds to
>> process a slice.
>>
>> Thanks,
>>
>> Fred
>>
>> On Fri, March 23, 2018 9:00 am, Michael Schmid wrote:
>>> 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
>>>
>>
>> --
>> 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