@@ -63,7 +63,9 @@ def fit_least_squares(model_fn, init_params, fixed_params=None, bounds=None, mod
63
63
:param tuple[tuple[float]] bounds: (lbs, ubs). If None, -/+ infinity used for all parameters.
64
64
:param model_jacobian: Jacobian of the model function as a list, [df/dp[0], df/dp[1], ...]. If None, no jacobian used.
65
65
: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
67
69
"""
68
70
69
71
# get default fixed parameters
@@ -117,7 +119,9 @@ def jac_fn_free(pfree): return pfull2pfree(model_jacobian(pfree2pfull(pfree))).t
117
119
118
120
# calculate chi squared
119
121
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 )
121
125
122
126
# calculate covariances
123
127
try :
@@ -510,10 +514,11 @@ def sum_gauss2d_jacobian(x, y, p):
510
514
511
515
def gauss3d (x , y , z , p ):
512
516
"""
517
+ 3D gaussian, with rotation parameterized by Euler angles
513
518
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
515
520
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 )
517
522
518
523
Take the z-axis in the frame of the object, and consider the z-axis in the lab frame. phi and theta describe
519
524
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):
528
533
phi = p [8 ]
529
534
theta = p [9 ]
530
535
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 ]
541
540
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 ]
542
541
543
542
return val
544
543
545
544
546
545
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
0 commit comments