Skip to content

Commit ee24e7f

Browse files
committed
moved some functions to fit.py
1 parent 493cc27 commit ee24e7f

File tree

2 files changed

+118
-38
lines changed

2 files changed

+118
-38
lines changed

analysis/fit.py

Lines changed: 117 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ def fit_least_squares(model_fn, init_params, fixed_params=None, bounds=None, mod
6363
:param tuple[tuple[float]] bounds: (lbs, ubs). If None, -/+ infinity used for all parameters.
6464
:param model_jacobian: Jacobian of the model function as a list, [df/dp[0], df/dp[1], ...]. If None, no jacobian used.
6565
:param kwargs: additional key word arguments will be passed through to scipy.optimize.least_squares
66-
:return:
66+
67+
:return fit_params: dictionary object. Uncertainty can be obtained from the square rootsof the diagonals of the
68+
covariance matrix, but these will only be meaningful if variances were appropriately provided for the cost function
6769
"""
6870

6971
# get default fixed parameters
@@ -117,7 +119,9 @@ def jac_fn_free(pfree): return pfull2pfree(model_jacobian(pfree2pfull(pfree))).t
117119

118120
# calculate chi squared
119121
nfree_params = np.sum(np.logical_not(fixed_params))
120-
red_chi_sq = np.sum(np.square(model_fn(pfit))) / (model_fn(init_params).size - nfree_params)
122+
# red_chi_sq = np.sum(np.square(model_fn(pfit))) / (model_fn(init_params).size - nfree_params)
123+
# scipy.optimize.least_squares minimizes s = 0.5 * \sum |fn(x_i)|^2, so need a factor of two to correct their cost
124+
red_chi_sq = (2 * fit_info["cost"]) / (model_fn(init_params).size - nfree_params)
121125

122126
# calculate covariances
123127
try:
@@ -510,10 +514,11 @@ def sum_gauss2d_jacobian(x, y, p):
510514

511515
def gauss3d(x, y, z, p):
512516
"""
517+
3D gaussian, with rotation parameterized by Euler angles
513518
514-
R_body = U_z(psi)^-1 U_y(theta)^-1 U_z(phi)^-1 * R
519+
r_body = U_z(psi)^-1 U_y(theta)^-1 U_z(phi)^-1 * r_lab
515520
U_z(phi)^-1 = [[cos(phi), -sin(phi), 0], [sin(phi), cos(phi), 0], [0, 0, 1]]
516-
f_rot(R) = f(R_body)
521+
f_rot(r_lab) = f(r_body) = f(U^{-1} * r_lab)
517522
518523
Take the z-axis in the frame of the object, and consider the z-axis in the lab frame. phi and theta describe
519524
how the transformation to overlap these two. psi gives the gives the angle the object is rotated about its own axis
@@ -528,20 +533,117 @@ def gauss3d(x, y, z, p):
528533
phi = p[8]
529534
theta = p[9]
530535
psi = p[10]
531-
xrot = (x - p[1]) * (np.cos(psi) * np.cos(theta) * np.cos(phi) - np.sin(psi) * np.sin(phi)) + \
532-
(y - p[2]) * (-1) * (np.cos(psi) * np.cos(theta) * np.sin(phi) + np.sin(psi) * np.cos(phi)) + \
533-
(z - p[3]) * (-1) * np.cos(psi) * np.sin(theta)
534-
535-
yrot = (x - p[1]) * (np.sin(psi) * np.cos(theta) * np.cos(phi) + np.cos(psi) * np.sin(phi)) + \
536-
(y - p[2]) * (-np.sin(psi) * np.cos(theta) * np.sin(phi) + np.cos(psi) * np.cos(phi)) + \
537-
(z - p[3]) * (-np.sin(psi) * np.sin(theta))
538-
zrot = (x - p[1]) * np.sin(theta) * np.cos(phi) + \
539-
(y - p[2]) * (- np.sin(theta) * np.sin(phi)) + \
540-
(z - p[3]) * np.cos(theta)
536+
rot_mat = euler_mat_inv(phi, theta, psi)
537+
xrot = (x - p[1]) * rot_mat[0, 0] + (y - p[2]) * rot_mat[0, 1] + (z - p[3]) * rot_mat[0, 2]
538+
yrot = (x - p[1]) * rot_mat[1, 0] + (y - p[2]) * rot_mat[1, 1] + (z - p[3]) * rot_mat[1, 2]
539+
zrot = (x - p[1]) * rot_mat[2, 0] + (y - p[2]) * rot_mat[2, 1] + (z - p[3]) * rot_mat[2, 2]
541540
val = p[0] * np.exp(-xrot**2 / (2 * p[4]**2) - yrot**2 / (2 * p[5] ** 2) - zrot**2 / (2 * p[6]**2)) + p[7]
542541

543542
return val
544543

545544

546545
def gauss3d_jacobian(x, y, z, p):
547-
pass
546+
bcast_shape = (x + y + z).shape
547+
548+
phi = p[8]
549+
theta = p[9]
550+
psi = p[10]
551+
552+
rot_mat = euler_mat_inv(phi, theta, psi)
553+
dphi, dtheta, dpsi = euler_mat_inv_derivatives(phi, theta, psi)
554+
555+
xrot = (x - p[1]) * rot_mat[0, 0] + (y - p[2]) * rot_mat[0, 1] + (z - p[3]) * rot_mat[0, 2]
556+
yrot = (x - p[1]) * rot_mat[1, 0] + (y - p[2]) * rot_mat[1, 1] + (z - p[3]) * rot_mat[1, 2]
557+
zrot = (x - p[1]) * rot_mat[2, 0] + (y - p[2]) * rot_mat[2, 1] + (z - p[3]) * rot_mat[2, 2]
558+
exp = np.exp(-xrot**2 / (2 * p[4]**2) - yrot**2 / (2 * p[5] ** 2) - zrot**2 / (2 * p[6]**2))
559+
560+
jac = [exp,
561+
p[0] * exp * (xrot / p[4]**2 * rot_mat[0, 0] + yrot / p[5]**2 * rot_mat[1, 0] + zrot / p[6]**2 * rot_mat[2, 0]),
562+
p[0] * exp * (xrot / p[4]**2 * rot_mat[0, 1] + yrot / p[5]**2 * rot_mat[1, 1] + zrot / p[6]**2 * rot_mat[1, 2]),
563+
p[0] * exp * (xrot / p[4]**2 * rot_mat[0, 2] + yrot / p[5]**2 * rot_mat[1, 2] + zrot / p[6]**2 * rot_mat[2, 2]),
564+
p[0] * exp * xrot ** 2 / p[4] ** 3,
565+
p[0] * exp * yrot ** 2 / p[5] ** 3,
566+
p[0] * exp * zrot ** 2 / p[6] ** 3,
567+
np.ones(bcast_shape),
568+
-p[0] * exp * (
569+
(xrot / p[4]**2) * ((x - p[1]) * dphi[0, 0] + (y - p[2]) * dphi[0, 1] + (z - p[3]) * dphi[0, 2]) +
570+
(yrot / p[5]**2) * ((x - p[1]) * dphi[1, 0] + (y - p[2]) * dphi[1, 1] + (z - p[3]) * dphi[1, 2]) +
571+
(zrot / p[6]**2) * ((x - p[1]) * dphi[2, 0] + (y - p[2]) * dphi[2, 1] + (z - p[3]) * dphi[2, 2])),
572+
-p[0] * exp * (
573+
(xrot / p[4] ** 2) * ((x - p[1]) * dtheta[0, 0] + (y - p[2]) * dtheta[0, 1] + (z - p[3]) * dtheta[0, 2]) +
574+
(yrot / p[5] ** 2) * ((x - p[1]) * dtheta[1, 0] + (y - p[2]) * dtheta[1, 1] + (z - p[3]) * dtheta[1, 2]) +
575+
(zrot / p[6] ** 2) * ((x - p[1]) * dtheta[2, 0] + (y - p[2]) * dtheta[2, 1] + (z - p[3]) * dtheta[2, 2])),
576+
-p[0] * exp * (
577+
(xrot / p[4] ** 2) * ((x - p[1]) * dpsi[0, 0] + (y - p[2]) * dpsi[0, 1] + (z - p[3]) * dpsi[0, 2]) +
578+
(yrot / p[5] ** 2) * ((x - p[1]) * dpsi[1, 0] + (y - p[2]) * dpsi[1, 1] + (z - p[3]) * dpsi[1, 2]) +
579+
(zrot / p[6] ** 2) * ((x - p[1]) * dpsi[2, 0] + (y - p[2]) * dpsi[2, 1] + (z - p[3]) * dpsi[2, 2]))
580+
]
581+
582+
return jac
583+
584+
585+
def euler_mat(phi, theta, psi):
586+
"""
587+
Define our euler angles connecting the body frame to the space/lab frame by
588+
589+
r_lab = U_z(phi) * U_y(theta) * U_z(psi) * r_body
590+
591+
Consider the z-axis in the body frame. This axis is then orientated at [cos(phi)*sin(theta), sin(phi)*sin(theta), cos(theta)]
592+
in the space frame. i.e. phi, theta are the usual polar angles. psi represents a rotation of the object about its own axis.
593+
594+
U_z(phi) = [[cos(phi), -sin(phi), 0], [sin(phi), cos(phi), 0], [0, 0, 1]]
595+
U_y(theta) = [[cos(theta), 0, sin(theta)], [0, 1, 0], [-sin(theta), 0, cos(theta)]]
596+
"""
597+
euler_mat = np.array([[np.cos(phi) * np.cos(theta) * np.cos(psi) - np.sin(phi) * np.sin(psi),
598+
-np.cos(phi) * np.cos(theta) * np.sin(psi) - np.sin(phi) * np.cos(psi),
599+
np.cos(phi) * np.sin(theta)],
600+
[np.sin(phi) * np.cos(theta) * np.cos(psi) + np.cos(phi) * np.sin(psi),
601+
-np.sin(phi) * np.cos(theta) * np.sin(psi) + np.cos(phi) * np.cos(psi),
602+
np.sin(phi) * np.sin(theta)],
603+
[-np.sin(theta) * np.cos(psi), np.sin(theta) * np.sin(psi), np.cos(theta)]])
604+
605+
return euler_mat
606+
607+
608+
def euler_mat_inv(phi, theta, psi):
609+
"""
610+
r_body = U_z(-psi) * U_y(-theta) * U_z(-phi) * r_lab
611+
"""
612+
613+
inv = euler_mat(-psi, -theta, -phi)
614+
return inv
615+
616+
617+
def euler_mat_derivatives(phi, theta, psi):
618+
dphi = np.array([[-np.sin(phi) * np.cos(theta) * np.cos(psi) - np.cos(phi) * np.sin(psi),
619+
np.sin(phi) * np.cos(theta) * np.sin(psi) - np.cos(phi) * np.cos(psi),
620+
-np.sin(phi) * np.sin(theta)],
621+
[ np.cos(phi) * np.cos(theta) * np.cos(psi) - np.sin(phi) * np.sin(psi),
622+
-np.cos(phi) * np.cos(theta) * np.sin(psi) - np.sin(phi) * np.cos(psi),
623+
np.cos(phi) * np.sin(theta)],
624+
[0, 0, 0]])
625+
dtheta = np.array([[-np.cos(phi) * np.sin(theta) * np.cos(psi),
626+
np.cos(phi) * np.sin(theta) * np.sin(psi),
627+
np.cos(phi) * np.cos(theta)],
628+
[-np.sin(phi) * np.sin(theta) * np.cos(psi),
629+
np.sin(phi) * np.sin(theta) * np.sin(psi),
630+
np.sin(phi) * np.cos(theta)],
631+
[-np.cos(theta) * np.cos(psi), np.cos(theta) * np.sin(psi), -np.sin(theta)]])
632+
dpsi = np.array([[-np.cos(phi) * np.cos(theta) * np.sin(psi) - np.sin(phi) * np.cos(psi),
633+
-np.cos(phi) * np.cos(theta) * np.cos(psi) + np.sin(phi) * np.sin(psi),
634+
0],
635+
[-np.sin(phi) * np.cos(theta) * np.sin(psi) + np.cos(phi) * np.cos(psi),
636+
-np.sin(phi) * np.cos(theta) * np.cos(psi) - np.cos(phi) * np.sin(psi),
637+
0],
638+
[np.sin(theta) * np.sin(psi), np.sin(theta) * np.cos(psi), 0]])
639+
640+
return dphi, dtheta, dpsi
641+
642+
643+
def euler_mat_inv_derivatives(phi, theta, psi):
644+
d1, d2, d3 = euler_mat_derivatives(-psi, -theta, -phi)
645+
dphi = -d3
646+
dtheta = -d2
647+
dpsi = -d1
648+
649+
return dphi, dtheta, dpsi

analysis/fit_psf.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ def oversample_pixel(x, y, z, ds, sf=3, euler_angles=(0., 0., 0.)):
501501

502502
# rotate points to correct position using normal vector
503503
# for now we will fix x, but lose generality
504-
mat = euler_mat(*euler_angles)
504+
mat = fit.euler_mat(*euler_angles)
505505
result = mat.dot(np.concatenate((xp.ravel()[None, :], yp.ravel()[None, :], zp.ravel()[None, :]), axis=0))
506506
xs, ys, zs = result
507507

@@ -512,28 +512,6 @@ def oversample_pixel(x, y, z, ds, sf=3, euler_angles=(0., 0., 0.)):
512512

513513
return xx_s, yy_s, zz_s
514514

515-
def euler_mat(phi, theta, psi):
516-
"""
517-
Define our euler angles connecting the body frame to the space/lab frame by
518-
519-
r_lab = U_z(phi) * U_y(theta) * U_z(psi) * r_body
520-
521-
Consider the z-axis in the body frame. This axis is then orientated at [cos(phi)*sin(theta), sin(phi)*sin(theta), cos(theta)]
522-
in the space frame. i.e. phi, theta are the usual polar angles. psi represents a rotation of the object about its own axis.
523-
524-
U_z(phi) = [[cos(phi), -sin(phi), 0], [sin(phi), cos(phi), 0], [0, 0, 1]]
525-
U_y(theta) = [[cos(theta), 0, sin(theta)], [0, 1, 0], [-sin(theta), 0, cos(theta)]]
526-
"""
527-
euler_mat = np.array([[np.cos(phi) * np.cos(theta) * np.cos(psi) - np.sin(phi) * np.sin(psi),
528-
-np.cos(phi) * np.cos(theta) * np.sin(psi) - np.sin(phi) * np.cos(psi),
529-
np.cos(phi) * np.sin(theta)],
530-
[np.sin(phi) * np.cos(theta) * np.cos(psi) + np.cos(phi) * np.sin(psi),
531-
-np.sin(phi) * np.cos(theta) * np.sin(psi) + np.cos(phi) * np.cos(psi),
532-
np.sin(phi) * np.sin(theta)],
533-
[-np.sin(theta) * np.cos(psi), np.sin(theta) * np.sin(psi), np.cos(theta)]])
534-
535-
return euler_mat
536-
537515
# fitting functions
538516
def fit_cont_psfmodel(img, wavelength, ni, model='gaussian',
539517
init_params=None, fixed_params=None, sd=None, xx=None, yy=None, zz=None, bounds=None):

0 commit comments

Comments
 (0)