Login  Register

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

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

Hi Herbie, Fred,

oops, yes, I missed the 'abs'!

So one can't use the built-in version, and fitting certainly becomes tricky!
One should probably rewrite the problem as
   y = a * (1 + b*exp(-x/c))
or, if the c parameter is known to be always positive, the following may
be better:
   y = a * (1 + b*exp(-x/(c*c)))

Then, the pre-exponential factor has to be calculated as a*b.

Like this, the ImageJ CurveFitter can eliminate the factor 'a' as a
'multiplyParameter' via linear regression, and it becomes a
two-parameter fit.

If the data typically look like the example data, with a minimum where
the argument of the 'abs' passes through zero,
   0.1    1123.175
   0.25    838.206
   0.5    469.32
   0.75    453.003
   1    725.135
   1.25    1094.36
   1.5    1450.741
   1.75    1787.361
   2    2128.119
   5    4670.12
then one should take the minimum and find initial b and c values where
the function becomes zero at the minimum (here named xOfMin):
   1 + b*exp(-xOfMin/c) = 0
or
   b = -exp(xOfMin/c)

If there is no prior knowledge for c, this parameter might be taken
equal to the difference between the highest and lowest x value.
(For the second fit function with c*c, replace 'c' with 'c*c' everywhere.)

Michael
________________________________________________________________
On 23/03/2018 18:30, 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();
 >>>>         }
 >>>> }

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