Search code examples
arrayspython-3.xnumpyscipy-optimize

Scipy Curve Fit: "Result from function call is not a proper array of floats."


I am trying to fit a 2D Gaussian with an offset to a 2D array. The code is based on this thread here (which was written for Python2 while I am using Python3, therefore some changes were necessary to make it run somewhat):

import numpy as np
import scipy.optimize as opt

n_pixels = 2400

def twoD_Gaussian(data_list, amplitude, xo, yo, sigma_x, sigma_y, offset):
    x = data_list[0]
    y = data_list[1]
    theta = 0 # don't care about theta for the moment but want to leave the option in
    a = (np.cos(theta)**2)/(2*sigma_x**2) + (np.sin(theta)**2)/(2*sigma_y**2)
    b = -(np.sin(2*theta))/(4*sigma_x**2) + (np.sin(2*theta))/(4*sigma_y**2)
    c = (np.sin(theta)**2)/(2*sigma_x**2) + (np.cos(theta)**2)/(2*sigma_y**2)
    g = offset + amplitude*np.exp( - (a*((x-xo)**2) + 2*b*(x-xo)*(y-yo) + c*((y-yo)**2)))
    return g 

x = np.linspace(1, n_pixels, n_pixels) #starting with 1 because proper data is from a fits file
y = np.linspace(1, n_pixels, n_pixels)
x, y = np.meshgrid(x,y)

amp = -3
x0, y0 = n_pixels/2, n_pixels/2
sigma_x, sigma_y = 100, 100
offset = -1

initial_guess = np.asarray([amp, x0, y0, sigma_x, sigma_y, offset])

data_array = np.asarray([x, y])

testmap = twoD_Gaussian(data_array, initial_guess[0], initial_guess[1], initial_guess[2], initial_guess[3], initial_guess[4], initial_guess[5])

popt, pcov = opt.curve_fit(twoD_Gaussian, data_array, testmap, p0=initial_guess)

However, I first get a value error:

ValueError: object too deep for desired array

Which the traceback then traces to:

error: Result from function call is not a proper array of floats.

From what I understood in other threads with this other, this has to do with some part of the argument not being properly defined as an array, but e.g. as a symbolic object, which I do not understand since the output testmap (which is working as expected) is actually a numpy array, and all input into curve_fit is also either a numpy array or the function itself. What is the exact issue and how can I solve it?

edit: the full error if I try to run it from console is:

ValueError: object too deep for desired array
Traceback (most recent call last):
  File "fit-2dgauss.py", line 41, in <module>
    popt, pcov = opt.curve_fit(twoD_Gaussian, data_array, test, p0=initial_guess)
  File "/users/drhiem/.local/lib/python3.6/site-packages/scipy/optimize/minpack.py", line 784, in curve_fit
    res = leastsq(func, p0, Dfun=jac, full_output=1, **kwargs)
  File "/users/drhiem/.local/lib/python3.6/site-packages/scipy/optimize/minpack.py", line 423, in leastsq
gtol, maxfev, epsfcn, factor, diag)
minpack.error: Result from function call is not a proper array of floats.

I just noticed that instead of "error", it's now "minpack.error". I ran this in an ipython console environment beforehand for testing purposes, so maybe that difference is down to that, not sure how much this difference matters.


Solution

  • data_array is (2, 2400, 2400) float64 (from added print)

    testmap is (2400, 2400) float64 (again a diagnostic print)

    curve_fit docs talk about M length or (k,M) arrays.

    You are providing (2,N,N) and (N,N) shape arrays.

    Lets try flattening the N,N dimensions:

    In the objective function:

    def twoD_Gaussian(data_list, amplitude, xo, yo, sigma_x, sigma_y, offset):
        x = data_list[0]
        y = data_list[1]
        x = x.reshape(2400,2400)
        y = y.reshape(2400,2400)
        theta = 0 # don't care about theta for the moment but want to leave the option in
        a = (np.cos(theta)**2)/(2*sigma_x**2) + (np.sin(theta)**2)/(2*sigma_y**2)
        b = -(np.sin(2*theta))/(4*sigma_x**2) + (np.sin(2*theta))/(4*sigma_y**2)
        c = (np.sin(theta)**2)/(2*sigma_x**2) + (np.cos(theta)**2)/(2*sigma_y**2)
        g = offset + amplitude*np.exp( - (a*((x-xo)**2) + 2*b*(x-xo)*(y-yo) + c*((y-yo)**2)))
        return g.ravel()
    

    and

    and in the calls:

    testmap = twoD_Gaussian(data_array.reshape(2,-1), initial_guess[0], initial_guess[1], initial_guess[2], initial_guess[3], initial_guess[4], initial_guess[5])
    # shape (5760000,) float64
    print(type(testmap),testmap.shape, testmap.dtype)
    popt, pcov = opt.curve_fit(twoD_Gaussian, data_array.reshape(2,-1), testmap, p0=initial_guess)
    

    And it runs:

    1624:~/mypy$ python3 stack65587542.py 
    (2, 2400, 2400) float64
    <class 'numpy.ndarray'> (5760000,) float64
    

    popt and pcov:

    [-3.0e+00  1.2e+03  1.2e+03  1.0e+02  1.0e+02 -1.0e+00] 
    [[ 0. -0. -0.  0.  0. -0.]
     [-0.  0. -0. -0. -0. -0.]
     [-0. -0.  0. -0. -0. -0.]
     [ 0. -0. -0.  0.  0.  0.]
     [ 0. -0. -0.  0.  0.  0.]
     [-0. -0. -0.  0.  0.  0.]]
    

    The popt values are the same as initial_guess as expected with the exact testmap.

    So the basic issue is that you did not take the documented specifications seriously. That

    ValueError: object too deep for desired array

    error message is a bit obscure, though I vaguely recall seeing it before. Sometimes we get errors like this when inputs are ragged arrays and the result arrays is object dtype. But here it's simply a matter of shape.

    A past SO with similar problem and fix:

    Scipy curve_fit for Two Dimensions Not Working - Object Too Deep?

    ValueError When Performing scipy.stats test on Pandas Column Selection by Row

    Fitting a 2D Gaussian function using scipy.optimize.curve_fit - ValueError and minpack.error

    This is just a subset of SO with the same error message. Other scipy functions produce it. And often the problem is with shapes like (m,1) instead of (N,N). I'd be tempted to close this as a duplicate, but my long answer with debugging details may be instructive.