I want to plot the motion of a positive charge in a cylindrically symmetric magnetic field.
I am assuming a cylinder around the z-axis, with the magnetic field going in clockwise direction. The B-field has magnitude of 6T and the distance R from the z-axis is 3m. The charged particle is launched in positive direction along the z-axis and has the energy 2 MeV.
I am uncertain of how to simulate this B-field correctly. I was thinking to create the B-field in cylindrical coordinates,
cylinder from 0 to 2pi:
theta=numpy.linspace(0, 2*numpy.pi, 360)
x=r*numpy.cos(theta)
y=r*numpy.sin(theta)
Bx=B0*(numpy.cos(numpy.arctan2(y,x)
By=B0*(-numpy.sin(numpy.arctan2(y,x)))
Bz=0
And then create a vector B=[Bx, By, Bz] from which I would calculate the acceleration using Lorentz force for a timespan t.
But I think I am going in circles with this. Is there another way to create a cylindrically symmetric magnetic field?
With solve_ivp():
There are only two functions, the first one, B() takes care of the geometry of the magnetic field (rotationally invariant relative to the z axis and radially invariant, with always the same magnitude at each point), and the second one f() takes care of the physics, providing the velocity and the Lorentz's acceleration generated by the magnetic field B(), calculated at each point. This latter function is the right hand side of the differential equations of motion, which goes into solve_ivp().
'''
Dynamics in cylindrical homogeneous magnetic field
'''
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
def B(y, B_templet):
r = np.array([y[0], y[1], 0]) # vector aligned with the x,y projection of the position vector y
r = r / np.sqrt(r[0]**2 + r[1]**2) # unit vector aligned with the x,y projection of the position vector y
r_perp = np.array([-r[1], r[0], 0]) # unit vector perpendicular to the x,y projection of position vector y
B_field = B_templet[0] * r + B_templet[1] * r_perp
B_field[2] = B_templet[2]
return B_field
def f(t, y, parameters):
mass, charge, B_templet = parameters
return np.concatenate( (y[3:6], (charge/mass)*np.cross(y[3:6], B(y[0:3], B_templet))) )
charge = 1
mass = 1
B_direction = np.array([0.3,1,0.1])
B_magnitude = 1
xv_start = np.array([3, 0, 0, 0, 0, 2])
time_step = 0.01
n_iter = 5000
t_span=[0,n_iter*time_step]
B_direction = B_magnitude * B_direction / np.sqrt(B_direction.dot(B_direction))
sol = solve_ivp(fun = lambda t, y : f(t, y, (mass, charge, B_direction)), t_span=t_span, y0=xv_start, max_step=time_step)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
r = 35
ax.set_xlim((-r, r))
ax.set_ylim((-r, r))
ax.set_zlim((-r, r))
ax.plot(sol.y[0,:], sol.y[1,:], sol.y[2,:], 'r-')
ax.plot(sol.y[0,0], sol.y[1,0], sol.y[2,0], 'bo')
ax.plot(sol.y[0,-1], sol.y[1,-1], sol.y[2,-1], 'go')
plt.show()
Maybe this is what you want?
'''
Dynamics in a cylindrical magnetic field
'''
import numpy as np
import matplotlib.pyplot as plt
def B(x, B_templet):
r = np.array([x[0], x[1], 0]) # vector aligned with the x,y projection of the position vector x
r = r / np.sqrt(r[0]**2 + r[1]**2) # unit vector aligned with the x,y projection of the position vector x
r_perp = np.array([-r[1], r[0], 0]) # unit vector perpendicular to the x,y projection of the position vector x
B_field = B_templet[0] * r + B_templet[1] * r_perp
B_field[2] = B_templet[2]
return B_field
def f(x, mass, charge, B_templet):
return np.concatenate( (x[3:6], (charge/mass)*np.cross(x[3:6], B(x[0:3], B_templet))) )
def time_step_f(x, mass, charge, B_templet, t_step):
k1 = f(x, mass, charge, B_templet)
k2 = f(x + t_step*k1/2, mass, charge, B_templet)
k3 = f(x + t_step*k2/2, mass, charge, B_templet)
k4 = f(x + t_step*k3, mass, charge, B_templet)
return x + t_step * (k1 + 2*k2 + 2*k3 + k4) / 6
def flow_f(x_initial, mass, charge, B_templet, t_step, n_iter):
traj = np.empty( (n_iter, x_initial.shape[0]), dtype = float )
traj[0, :] = x_initial
for m in range(n_iter-1):
traj[m+1,:] = time_step_f(traj[m,:], mass, charge, B_templet, t_step)
return traj
charge = 1
mass = 1
B_direction = np.array([0.3,1,0.1])
B_magnitude = 3
xv_start = np.array([3, 0, 0, 0, 0, 2])
time_step = 0.01
n_iter = 5000
B_direction = B_magnitude * B_direction / np.sqrt(B_direction.dot(B_direction))
xv = flow_f(xv_start, mass, charge, B_direction, time_step, n_iter)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
r = 20
ax.set_xlim((-r, r))
ax.set_ylim((-r, r))
ax.set_zlim((-r, r))
ax.plot(xv[:,0], xv[:,1], xv[:,2], 'r-')
ax.plot(xv[0,0], xv[0,1], xv[0,2], 'bo')
ax.plot(xv[-1,0], xv[-1,1], xv[-1,2], 'go')
plt.show()
Related
By using python, I want to plot 6 points in same diagram where first 3 points are 3 initial (x,y,z) position points and other 3 points are 3 updated position points (X,Y,Z).
The updated points formulas are X =𝑥+𝑟𝜇𝑥 , Y=𝑦+𝑟𝜇y , and Z=𝑧+𝑟*𝜇z .
I plotted the 3 initial points. The problem is that all the 3 updated points have same value, then same point in the diagram. How can I make them different?
I think the problem is that in the Updated xyz positions, it takes only this point (x,y,z)=(3,6,9). How can I make these 3 updated position points different?
# Updated Positions and Direction Cosines
############################################
# Request libraries
############################################
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import random
from random import gauss
import math
from math import log, cos, acos
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
############################################
# Initial Positions and Direction Cosines
############################################
# Photons Number
PhotonsTotalNumber = 3
#PhotonsUniqueNumber= np.array(0,PhotonsTotalNumber)
# Initial Direction Cosines
Initial_ux= math.cos(math.radians(90))
Initial_uy= math.cos(math.radians(90))
Initial_uz= math.cos(math.radians(0))
# Initial xyz Positions
Initialx = np.array([1,2,3])
Initialy = np.array([4,5,6])
Initialz = np.array([7,8,9])
for x, y, z in zip(Initialx, Initialy, Initialz):
text = '({},{}, {})'.format(x,y,z)
ax.text(x,y,z, text, zdir=(1, 1, 1))
print(x,y,z)
############################################
# Photon Propagation Equations
############################################
# Random Numbers Function
def get_rand_number(min_value, max_value):
range = max_value - min_value
choice = random.uniform(0,1)
return min_value + range*choice
# Photon path length
r = -math.log(get_rand_number(0,1))/0.15 # 0.15 attenuation coefficients for clear water
# The anisotropy factor g
values = [1,-1]
g = np.random.choice(values)
g = int(g)
# Scattering Angles (theta and phi)
theta = (((1 + g*g - ((1 - g*g)/(1 - g + 2*g*get_rand_number(0,1)))**2)/(2*g)))
phi = 2 * math.radians(180) * get_rand_number(0,1)
Theta = gauss( 0, theta)
Phi = gauss( 0, phi)
############################################
# Updated Positions and Direction Cosines
############################################
# Updated direction cosines
New_ux = math.sin(Theta) * math.cos(Phi)
New_uy = math.sin(Theta) * math.sin(Phi)
New_uz = (Initial_uz/abs(Initial_uz))*math.cos(Theta)
# Updated xyz positions
Updatedx = x + (r * New_ux)
Updatedy = y + (r * New_uy)
Updatedz = z + (r * New_uz)
UpdatedX = np.array ([Updatedx,Updatedx,Updatedx])
UpdatedY = np.array ([Updatedy,Updatedy,Updatedy])
UpdatedZ = np.array ([Updatedz,Updatedz,Updatedz])
for X, Y, Z in zip(UpdatedX, UpdatedY, UpdatedZ):
text = '({},{}, {})'.format(X,Y,Z)
ax.text(X,Y,Z, text, zdir=(1, 1, 1))
ax.scatter(Initialx,Initialy,Initialz, s=100,c='b', label='True Position')
ax.scatter(UpdatedX,UpdatedY,UpdatedZ, s=100,c='g', label='True Position')
ax.set_xlabel("x axis")
ax.set_ylabel("y axis")
ax.set_zlabel("z axis")
plt.show()
####################
I have following three points in 3d space with their respective normal vectors.
A, B, C are positions and N_A, N_B,and N_C are their respective normal vectors.
A = np.array([ 348.92065834, -1402.3305998, 32.69313966])
N_A = np.array([-0.86925426, 0.02836434, -0.49355091])
B = np.array([282.19332067, 82.52027998, -5.92595371])
N_B = np.array([-0.82339849, 0.43041935, 0.3698028])
C = np.array([247.37475615, -3.70129865, -22.10494737])
N_C = np.array([-0.83989222, 0.23796899, 0.48780305])
Three points are almost in one plane, but there is a slight directional change between the two closest point B and C. From there to the point A, I assumed there could be a curvature in X-Y coordinates as shown , but parabole in X-Z coordinates as shown in
Supposed condition is cylinder can fit the three points if the curvature was much obvious. And considering their respective normal vectors from X and Z coordinates, B and C normal vectors face lower direction while the that of A faces upper direction. So, all in all it it could be a paraboloid. Question is how to fit them taking their normal vectors into account. If not possible, then how to fit them with some curvature from X and Y direction.
Here is a code for the plot
import numpy as np
import matplotlib.pyplot as plt
def set_axes_radius(ax, origin, radius):
ax.set_xlim3d([origin[0] - radius, origin[0] + radius])
ax.set_ylim3d([origin[1] - radius, origin[1] + radius])
ax.set_zlim3d([origin[2] - radius, origin[2] + radius])
def set_axes_equal(ax, zoom=1.):
'''
Make axes of 3D plot have equal scale so that spheres appear as spheres,
cubes as cubes, etc.. This is one possible solution to Matplotlib's
ax.set_aspect("equal") and ax.axis("equal") not working for 3D.
input:
ax: a matplotlib axis, e.g., as output from plt.gca().
'''
limits = np.array([
ax.get_xlim3d(),
ax.get_ylim3d(),
ax.get_zlim3d(),
])
origin = np.mean(limits, axis=1)
radius = 0.5 * np.max(np.abs(limits[:, 1] - limits[:, 0])) / zoom
set_axes_radius(ax, origin, radius)
%matplotlib qt
# positions and their respective normal vectors
A = np.array([ 348.92065834, -1402.3305998, 32.69313966])
N_A = np.array([-0.86925426, 0.02836434, -0.49355091])
B = np.array([282.19332067, 82.52027998, -5.92595371])
N_B = np.array([-0.82339849, 0.43041935, 0.3698028])
C = np.array([247.37475615, -3.70129865, -22.10494737])
N_C = np.array([-0.83989222, 0.23796899, 0.48780305])
# A plane is given by
# a*x + b*y + c*z + d = 0
# where (a, b, c) is the normal.
# If the point (x, y, z) lies on the plane, then solving for d yield:
# d = -(a*x + b*y + c*z)
d_A = -np.sum(N_A * A)
d_B = -np.sum(N_B * B)
d_C = -np.sum(N_C * C)
# Create a meshgrid:
delta = 200
xlim_A = A[0] - delta, A[0] + delta
ylim_A = A[1] - delta, A[1] + delta
xx_A, yy_A = np.meshgrid(np.arange(*xlim_A), np.arange(*ylim_A))
xlim_B = B[0] - delta, B[0] + delta
ylim_B = B[1] - delta, B[1] + delta
xx_B, yy_B = np.meshgrid(np.arange(*xlim_B), np.arange(*ylim_B))
xlim_C = C[0] - delta, C[0] + delta
ylim_C = C[1] - delta, C[1] + delta
xx_C, yy_C = np.meshgrid(np.arange(*xlim_C), np.arange(*ylim_C))
# Solving the equation above for z:
# z = -(a*x + b*y +d) / c
zz_A = -(N_A[0] * xx_A + N_A[1] * yy_A + d_A) / N_A[2]
zz_B = -(N_B[0] * xx_B + N_B[1] * yy_B + d_B) / N_B[2]
zz_C = -(N_C[0] * xx_C + N_C[1] * yy_C + d_C) / N_C[2]
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.plot_surface(xx_A, yy_A, zz_A, alpha=0.5, color='g')
ax.plot_surface(xx_B, yy_B, zz_B, alpha=0.5, color='cyan')
ax.plot_surface(xx_C, yy_C, zz_C, alpha=0.5, color='crimson')
# Plot point.
x_A, y_A, z_A = A
x_B, y_B, z_B = B
x_C, y_C, z_C = C
Plane_A, = ax.plot(x_A, y_A, z_A, marker='o', markersize=5, color='g')
Plane_A.set_label('A position')
ax.legend()
Plane_B, = ax.plot(x_B, y_B, z_B, marker='o', markersize=5, color='cyan')
Plane_B.set_label('B position')
ax.legend()
Plane_C, = ax.plot(x_C, y_C, z_C, marker='o', markersize=5, color='crimson')
Plane_C.set_label('C position')
ax.legend()
# Plot normal.
dx_A, dy_A, dz_A = delta * N_A
ax.quiver(x_A, y_A, z_A, dx_A, dy_A, dz_A, arrow_length_ratio=0.15, linewidth=3, color='g')
dx_B, dy_B, dz_B = delta * N_B
ax.quiver(x_B, y_B, z_B, dx_B, dy_B, dz_B, arrow_length_ratio=0.15, linewidth=3, color='cyan')
dx_C, dy_C, dz_C = delta * N_C
ax.quiver(x_B, y_C, z_C, dx_C, dy_C, dz_C, arrow_length_ratio=0.15, linewidth=3, color='crimson')
# Enforce equal axis aspects so that the normal also appears to be normal.
ax.set_xlim(xmax=1500,xmin=-1500)
ax.set_ylim(ymax=400, ymin=-400)
zlim = max(A[2], B[2], C[2]) - delta, max(A[2], B[2], C[2]) + delta
ax.set_zlim(*zlim)
ax = plt.gca()
#ax.set_box_aspect([1,1,1])
set_axes_equal(ax)
ax.set_xlabel('X', fontsize=20)
ax.set_ylabel('Y', fontsize=20)
ax.set_zlabel('Z', fontsize=20)
plt.show()
Meta-problems: next time,
explain what you're actually doing (attempting regression on interplanetary electromagnetic bow shock from satellite readings) or else this is clearly an x/y problem
invest some time in learning fundamental vector calculus, which is inescapable for this class of problem
Do not discard the magnitude from your field readings.
It is physically invalid to attempt a surface fit containing the three satellite points. A surface is only applicable if the magnitude read at each satellite is exactly the same, and you've described that it isn't. The magnetic field is literally a field: a vector field with orientations defined at all points in spacetime.
When you perform regression to establish an estimate for the vector field orientation as a function of position, you will not have one surface. You will have three different isosurfaces, one for each of your satellites, each with a different path through space where the field magnitude is constant.
Don't perform your calculations in rectilinear space. Perform them in polar space. Your unit normals have too many degrees of freedom for regression (they have three when functionally they need to only have two).
You need many, many, many more data points to do meaningful regression. As it stands, "you could fit anything". So the following results are numerically accurate but physically meaningless.
Interpret the system as three independent variables (x, y, z), and two dependent variables (θ and φ, the unit normal rotation angles). This is vulnerable to gimbal lock but we disregard that here.
After all of the above bad news, the good news is that - as interpreted - the system is highly linear. (It's no surprise given the poverty of data.) You can express the fit as a linear matrix transformation:
import numpy as np
points = np.array((
(348.92065834, -1402.33059980, 32.69313966),
(282.19332067, 82.52027998, -5.92595371),
(247.37475615, -3.70129865, -22.10494737),
))
rect_normals = np.array((
(-0.86925426, 0.02836434, -0.49355091),
(-0.82339849, 0.43041935, 0.36980280),
(-0.83989222, 0.23796899, 0.48780305),
))
x, y, z = rect_normals.T
angles = np.stack((
np.arccos(z), # θ
np.arctan2(y, x), # φ
)).T
transform, *_ = np.linalg.lstsq(points, angles, rcond=None)
print('Transformation matrix:')
print(transform)
print()
est_angles = points # transform
print('Measured vs. estimated angles:')
print(np.stack((angles, est_angles)))
print()
θ, φ = est_angles.T
est_normals = np.stack((
np.sin(θ) * np.cos(φ),
np.sin(θ) * np.sin(φ),
np.cos(θ),
)).T
print('Measured vs. estimated unit normals:')
print(np.stack((rect_normals, est_normals)))
print()
Transformation matrix:
[[ 0.00435349 0.00901185]
[-0.00038691 -0.00064318]
[ 0.00077585 -0.02867279]]
Measured vs. estimated angles:
[[[2.08696421 3.10897357]
[1.19199956 2.65992277]
[1.06122503 2.86549615]]
[[2.08696421 3.10897357]
[1.19199956 2.65992277]
[1.06122503 2.86549615]]]
Measured vs. estimated unit normals:
[[[-0.86925426 0.02836434 -0.49355091]
[-0.82339849 0.43041935 0.3698028 ]
[-0.83989222 0.23796899 0.48780305]]
[[-0.86925426 0.02836434 -0.49355091]
[-0.82339849 0.43041935 0.3698028 ]
[-0.83989222 0.23796899 0.48780305]]]
So the fit is essentially perfect. The above result is related to (though not equal to) the gradient. To find your isosurfaces - one for each satellite - you need to perform a vector integral. If you do not know how to do this, it's time for research in a different community such as physics or mathematics StackExchange sites.
I have a vector field of shape (width, height, 2) where the third axis holds the magnitude and angle of each vector.
I want to perform a transformation (rotation and translation) around a given pivot point. I would expect that either numpy or scipy have a function for calculating the transformation matrix, but have so far found nothing.
This is what I've tride so far btw. Just iterating through each coordinate and calculating the new magnitude. The vectors are in polar coordinates.
pivot = (magnitude.shape[1]/2, magnitude.shape[0])
for c in range(magnitude.shape[1]):
for r in range(magnitude.shape[0]):
magnitude[r, c] -= dist + 2*np.linalg.norm((pivot[0]-r, pivot[1]-c))*np.sin(np.abs(angle/2))
From your description I am assuming that
field_x = field[:,:,0] * np.sin(field[:,:,1])
field_y = field[:,:,1] * np.cos(field[:,:,1])
If you want to rotate around the origin with no translation it is just add the rotation angle to field[:,:,1].
Rotation matrix is useful when you want to apply the same transformation to all the points that are in an affine form. (you have a polar representation).
It is not clear to me how exactly you want to do the transformation, so I will give you a function that rotates the point around a given pivot and then translate.
To translate around a given pivot you have to first translate the center of rotation to the origin, apply the rotation and, then you translate the center of rotation back to the original position.
def translate(x, y, tx, ty):
return (x + tx, y + ty)
def rotate(x, y, angle):
c = np.cos(angle)
s = np.sin(angle)
return x * c - y * s, x * s + y * c
def polar2cartesian(field):
c = np.sin(field[:,:,1])
s = np.cos(field[:,:,1])
r = field[:,:,0]
return r*c, r*s
def cartesian2polar(x, y):
result = np.empty(x.shape + (2,))
result[:,:,0] = np.sqrt(x**2 + y**2)
result[:,:,1] = np.arctan2(y, x);
return result;
def transform(field1, pivot, angle, translation):
x,y = polar2cartesian(field1)
px,py = polar2cartesian(pivot)
tx,ty = polar2cartesian(translation)
# move the center of rotation to the origin
x, y = translate(x, y, -px, -py);
x, y = rotate(x, y, angle)
x, y = translate(x, y, tx, ty)
return cartesian2polar(x, y)
To illustrate the use I will use some 1000 x 1 field with constant radius and constant rotation.
r = np.linspace(0, 2*np.pi, 1000)
field1 = np.zeros((1000, 1, 2))
field2 = np.zeros((1000, 1, 2))
field3 = np.zeros((1000, 1, 2))
plt.figure(figsize=(5, 5))
R = 10;
field1[:,0,0] = 1
field1[:,0,1] = 0
field2[:,0,0] = R+1
field2[:,0,1] = r
field3[:,0,0] = 0
field3[:,0,1] = R*r;
field4[:,0,0] = R;
field4[:,0,1] = r
plt.plot(*polar2cartesian(transform(field1, field3, field3[:,:,1], field4)))
field4[:,0,0] = R+2
field3[:,0,1] = -(R+2)*r + np.pi
plt.plot(*polar2cartesian(transform(field1, field3, field3[:,:,1], field4)))
plt.plot(*polar2cartesian(field2))
Interpretation The first plot is a circle of radius R+1 the second is the trajectory of a point in a circle of radius one rolling inside the circle of radius R+1, and the third plot is the trajectory of a point in a circle of radius one rolling outside the circle of radius R+1
To test my 2D Gaussian fitting code for images of bright objects, I'm running it on a Point Spread Function (PSF) that was constructed and fit by the WISE team. On their website, they list the parameters for the central PSF for each band: the FWHM along the major and minor axes, and position angle (this is the angle from the y-axis). All the information is available here: WISE PSF information, but below is an image of those parameters for the central PSF, and the corresponding image of the PSF for band 3.
So, I have downloaded the corresponding FITS image for the central PSF in band 3 (all the images are available to download via the above link), and tried running my code on it. However, my code does not return the parameters I would expect, and the parameters change depending on if I fit to a subimage (and depending on the size of this), or the whole image, which is kind of worrying.
I am wondering if there's a way to make my Gaussian fit code recover the most accurate parameters -- or maybe another fitting method would be more effective. But I'm mostly concerned at the fact that the output parameters of my Gaussian fit can become so obviously wrong. Below is my code.
from scipy import optimize
import numpy as np
from astropy.io import fits
image = 'wise-w3-psf-wpro-09x09-05x05.fits' #WISE central PSF
stacked_image = fits.getdata(image)
# Center of image (the PSF is centered)
x0 = np.shape(stacked_image)[1]//2
y0 = np.shape(stacked_image)[0]//2
# Normalize image so peak = 1
def normalize(image):
image *= 1/np.max(image)
return image
stacked_image = normalize(stacked_image)
def gaussian_func(xy, x0, y0, sigma_x, sigma_y, amp, theta, offset):
x, y = xy
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)
inner = a * (x-x0)**2
inner += 2*b*(x-x0)*(y-y0)
inner += c * (y-y0)**2
return (offset + amp * np.exp(-inner)).ravel()
def Sigma2width(sigma):
return 2 * np.sqrt(2*np.log(2)) * sigma
def generate(data_set):
xvec = np.arange(0, np.shape(data_set)[1], 1)
yvec = np.arange(0, np.shape(data_set)[0], 1)
X, Y = np.meshgrid(xvec, yvec)
return X, Y
# METHOD 1: Fit subimage of PSF to Gaussian
# Guesses
theta_guess = np.deg2rad(96) #I believe that the angle in the Gaussian corresponds to CCW from the x-axis (so use WISE position angle + 90 degrees)
sigma_x = 5
sigma_y = 4
amp = 1 #I know this is true since I normalized it
subimage = stacked_image[y0-50:y0+50, x0-50:x0+50]
offset = np.min(subimage)
guesses = [np.shape(subimage)[1]//2, np.shape(subimage)[0]//2, sigma_x, sigma_y, amp, theta_guess, offset]
xx, yy = generate(subimage)
pred_params, uncert_cov = optimize.curve_fit(gaussian_func, (xx.ravel(), yy.ravel()), subimage.ravel(), p0=guesses)
width_x, width_y = Sigma2width(np.abs(pred_params[2]))*0.275, Sigma2width(np.abs(pred_params[3]))*0.275 #multiply by pixel scale factor (available on website) to get FWHMs in arcseconds
x_0, y_0 = pred_params[0]+(x0-50), pred_params[1]+(y0-50) #add back origin
theta_deg = np.rad2deg(pred_params[5])
pred_params[5] = theta_deg
pred_params[0] = x_0
pred_params[1] = y_0
if theta_deg < 90:
pos_angle = theta_deg+90
elif theta_deg >= 90:
pos_angle = theta_deg-90
print('PREDICTED FWHM x, y in arcsecs:', width_x, width_y)
print('FIT PARAMS [x0, y0, sigma_x, sigma_y, amp, theta, offset]:', pred_params)
print('POSITION ANGLE:', pos_angle)
# Output: PREDICTED FWHM x, y in arcsecs: 6.4917 5.4978
# FIT PARAMS [x0, y0, sigma_x, sigma_y, amp, theta, offset]: [3.195e+02 3.189e+02 1.002e+01 8.489e+00 8.695e-01 8.655e+01 2.613e-02]
# POSITION ANGLE: 176.556
# METHOD 2: Fit whole image to Gaussian
# Guesses
theta_guess = np.deg2rad(96)
sigma_x = 5
sigma_y = 4
amp = 1
offset = np.median(stacked_image)
guesses = [x0, y0, sigma_x, sigma_y, amp, theta_guess, offset]
# Sigmas - manual estimation
ylim, xlim = np.shape(stacked_image)
x, y = np.arange(0, xlim, 1), np.arange(0, ylim, 1)
ypix, xpix = np.where(stacked_image==amp)
y_range = np.take(stacked_image, ypix[0], axis=0)
x_range = np.take(stacked_image, xpix[0], axis=1)
xx, yy = generate(stacked_image)
pred_params, uncert_cov = optimize.curve_fit(gaussian_func, (xx.ravel(), yy.ravel()), stacked_image.ravel(), p0=guesses)
width_x, width_y = Sigma2width(np.abs(pred_params[2]))*0.275, Sigma2width(np.abs(pred_params[3]))*0.275 #in arcsecs
theta = pred_params[5]
print('PREDICTED FWHM x, y in arcsecs:', width_x, width_y)
print('FIT PARAMS [x0, y0, sigma_x, sigma_y, amp, theta, offset]:', pred_params)
# Output:
# PREDICTED FWHM x, y in arcsecs: 7.088 6.106
# FIT PARAMS [x0, y0, sigma_x, sigma_y, amp, theta, offset]: [3.195e+02 3.190e+02 1.095e+01 9.429e+00 8.378e-01 1.521e+00 7.998e-04]
if theta < 90:
pos_angle = 90+np.rad2deg(theta)
elif theta >= 90:
pos_angle = 90-np.rad2deg(theta)
print('POSITION ANGLE:', pos_angle)
# POSITION ANGLE: 177.147
You can see in the (rounded) outputs that the amplitudes my Gaussian fits return aren't even 1, and the other parameters (FWHMs and angles) don't match up with the correct parameters shown in the table either.
If I fit a subimage, it seems that the amplitude gets closer and closer to (but never reaches) 1 the smaller I make the subimage, but then the FWHMs might get too small compared to the real values. Why am I not getting back the correct results, and how can I make the fit as accurate as possible?
I've been trying to use scipy.interpolate.bisplrep() and scipy.interpolate.interp2d() to find interpolants for data on my (218x135) 2D spherical-polar grid. To these I pass 2D arrays, X and Y, of the Cartesian positions of my grid nodes. I keep getting errors like the following (for linear interp. with interp2d):
"Warning: No more knots can be added because the additional knot would coincide
with an old one. Probably cause: s too small or too large a weight
to an inaccurate data point. (fp>s)
kx,ky=1,1 nx,ny=4,5 m=29430 fp=1390609718.902140 s=0.000000"
I get a similar result for bivariate splines with the default value of the smoothing parameter s etc. My data are smooth. I've attached my code below in case I'm doing something obviously wrong.
Any ideas?
Thanks!
Kyle
class Field(object):
Nr = 0
Ntheta = 0
grid = np.array([])
def __init__(self, Nr, Ntheta, f):
self.Nr = Nr
self.Ntheta = Ntheta
self.grid = np.empty([Nr, Ntheta])
for i in range(Nr):
for j in range(Ntheta):
self.grid[i,j] = f[i*Ntheta + j]
def calculate_lines(filename):
ri,ti,r,t,Br,Bt,Bphi,Bmag = np.loadtxt(filename, skiprows=3,\
usecols=(1,2,3,4,5,6,7,9), unpack=True)
Nr = int(max(ri)) + 1
Ntheta = int(max(ti)) + 1
### Initialise coordinate grids ###
X = np.empty([Nr, Ntheta])
Y = np.empty([Nr, Ntheta])
for i in range(Nr):
for j in range(Ntheta):
indx = i*Ntheta + j
X[i,j] = r[indx]*sin(t[indx])
Y[i,j] = r[indx]*cos(t[indx])
### Initialise field objects ###
Bradial = Field(Nr=Nr, Ntheta=Ntheta, f=Br)
### Interpolate the fields ###
intp_Br = interpolate.interp2d(X, Y, Bradial.grid, kind='linear')
#rbf_0 = interpolate.Rbf(X,Y, Bradial.grid, epsilon=2)
return
Added 27Aug: Kyle followed this up on a
scipy-user thread.
30Aug: #Kyle, it looks as though there's a mixup between Cartesion X,Y and polar Xnew,Ynew.
See "polar" in the too-long notes below.
# griddata vs SmoothBivariateSpline
# http://stackoverflow.com/questions/3526514/
# problem-with-2d-interpolation-in-scipy-non-rectangular-grid
# http://www.scipy.org/Cookbook/Matplotlib/Gridding_irregularly_spaced_data
# http://en.wikipedia.org/wiki/Natural_neighbor
# http://docs.scipy.org/doc/scipy/reference/tutorial/interpolate.html
from __future__ import division
import sys
import numpy as np
from scipy.interpolate import SmoothBivariateSpline # $scipy/interpolate/fitpack2.py
from matplotlib.mlab import griddata
__date__ = "2010-10-08 Oct" # plot diffs, ypow
# "2010-09-13 Sep" # smooth relative
def avminmax( X ):
absx = np.abs( X[ - np.isnan(X) ])
av = np.mean(absx)
m, M = np.nanmin(X), np.nanmax(X)
histo = np.histogram( X, bins=5, range=(m,M) ) [0]
return "av %.2g min %.2g max %.2g histo %s" % (av, m, M, histo)
def cosr( x, y ):
return 10 * np.cos( np.hypot(x,y) / np.sqrt(2) * 2*np.pi * cycle )
def cosx( x, y ):
return 10 * np.cos( x * 2*np.pi * cycle )
def dipole( x, y ):
r = .1 + np.hypot( x, y )
t = np.arctan2( y, x )
return np.cos(t) / r**3
#...............................................................................
testfunc = cosx
Nx = Ny = 20 # interpolate random Nx x Ny points -> Newx x Newy grid
Newx = Newy = 100
cycle = 3
noise = 0
ypow = 2 # denser => smaller error
imclip = (-5., 5.) # plot trierr, splineerr to same scale
kx = ky = 3
smooth = .01 # Spline s = smooth * z2sum, see note
# s is a target for sum (Z() - spline())**2 ~ Ndata and Z**2;
# smooth is relative, s absolute
# s too small => interpolate/fitpack2.py:580: UserWarning: ier=988, junk out
# grr error message once only per ipython session
seed = 1
plot = 0
exec "\n".join( sys.argv[1:] ) # run this.py N= ...
np.random.seed(seed)
np.set_printoptions( 1, threshold=100, suppress=True ) # .1f
print 80 * "-"
print "%s Nx %d Ny %d -> Newx %d Newy %d cycle %.2g noise %.2g kx %d ky %d smooth %s" % (
testfunc.__name__, Nx, Ny, Newx, Newy, cycle, noise, kx, ky, smooth)
#...............................................................................
# interpolate X Y Z to xnew x ynew --
X, Y = np.random.uniform( size=(Nx*Ny, 2) ) .T
Y **= ypow
# 1d xlin ylin -> 2d X Y Z, Ny x Nx --
# xlin = np.linspace( 0, 1, Nx )
# ylin = np.linspace( 0, 1, Ny )
# X, Y = np.meshgrid( xlin, ylin )
Z = testfunc( X, Y ) # Ny x Nx
if noise:
Z += np.random.normal( 0, noise, Z.shape )
# print "Z:\n", Z
z2sum = np.sum( Z**2 )
xnew = np.linspace( 0, 1, Newx )
ynew = np.linspace( 0, 1, Newy )
Zexact = testfunc( *np.meshgrid( xnew, ynew ))
if imclip is None:
imclip = np.min(Zexact), np.max(Zexact)
xflat, yflat, zflat = X.flatten(), Y.flatten(), Z.flatten()
#...............................................................................
print "SmoothBivariateSpline:"
fit = SmoothBivariateSpline( xflat, yflat, zflat, kx=kx, ky=ky, s = smooth * z2sum )
Zspline = fit( xnew, ynew ) .T # .T ??
splineerr = Zspline - Zexact
print "Zspline - Z:", avminmax(splineerr)
print "Zspline: ", avminmax(Zspline)
print "Z: ", avminmax(Zexact)
res = fit.get_residual()
print "residual %.0f res/z2sum %.2g" % (res, res / z2sum)
# print "knots:", fit.get_knots()
# print "Zspline:", Zspline.shape, "\n", Zspline
print ""
#...............................................................................
print "griddata:"
Ztri = griddata( xflat, yflat, zflat, xnew, ynew )
# 1d x y z -> 2d Ztri on meshgrid(xnew,ynew)
nmask = np.ma.count_masked(Ztri)
if nmask > 0:
print "info: griddata: %d of %d points are masked, not interpolated" % (
nmask, Ztri.size)
Ztri = Ztri.data # Nans outside convex hull
trierr = Ztri - Zexact
print "Ztri - Z:", avminmax(trierr)
print "Ztri: ", avminmax(Ztri)
print "Z: ", avminmax(Zexact)
print ""
#...............................................................................
if plot:
import pylab as pl
nplot = 2
fig = pl.figure( figsize=(10, 10/nplot + .5) )
pl.suptitle( "Interpolation error: griddata - %s, BivariateSpline - %s" % (
testfunc.__name__, testfunc.__name__ ), fontsize=11 )
def subplot( z, jplot, label ):
ax = pl.subplot( 1, nplot, jplot )
im = pl.imshow(
np.clip( z, *imclip ), # plot to same scale
cmap=pl.cm.RdYlBu,
interpolation="nearest" )
# nearest: squares, else imshow interpolates too
# todo: centre the pixels
ny, nx = z.shape
pl.scatter( X*nx, Y*ny, edgecolor="y", s=1 ) # for random XY
pl.xlabel(label)
return [ax, im]
subplot( trierr, 1,
"griddata, Delaunay triangulation + Natural neighbor: max %.2g" %
np.nanmax(np.abs(trierr)) )
ax, im = subplot( splineerr, 2,
"SmoothBivariateSpline kx %d ky %d smooth %.3g: max %.2g" % (
kx, ky, smooth, np.nanmax(np.abs(splineerr)) ))
pl.subplots_adjust( .02, .01, .92, .98, .05, .05 ) # l b r t
cax = pl.axes([.95, .05, .02, .9]) # l b w h
pl.colorbar( im, cax=cax ) # -1.5 .. 9 ??
if plot >= 2:
pl.savefig( "tmp.png" )
pl.show()
Notes on 2d interpolation, BivariateSpline vs. griddata.
scipy.interpolate.*BivariateSpline and matplotlib.mlab.griddata
both take 1d arrays as arguments:
Znew = griddata( X,Y,Z, Xnew,Ynew )
# 1d X Y Z Xnew Ynew -> interpolated 2d Znew on meshgrid(Xnew,Ynew)
assert X.ndim == Y.ndim == Z.ndim == 1 and len(X) == len(Y) == len(Z)
The inputs X,Y,Z describe a surface or cloud of points in 3-space:
X,Y (or latitude,longitude or ...) points in a plane,
and Z a surface or terrain above that.
X,Y may fill most of the rectangle [Xmin .. Xmax] x [Ymin .. Ymax],
or may be just a squiggly S or Y inside it.
The Z surface may be smooth, or smooth + a bit of noise,
or not smooth at all, rough volcanic mountains.
Xnew and Ynew are usually also 1d, describing a rectangular grid
of |Xnew| x |Ynew| points where you want to interpolate or estimate Z.
Znew = griddata(...) returns a 2d array over this grid, np.meshgrid(Xnew,Ynew):
Znew[Xnew0,Ynew0], Znew[Xnew1,Ynew0], Znew[Xnew2,Ynew0] ...
Znew[Xnew0,Ynew1] ...
Znew[Xnew0,Ynew2] ...
...
Xnew,Ynew points far from any of the input X,Y s spell trouble.
griddata checks this:
A masked array is returned if any grid points are outside convex
hull defined by input data (no extrapolation is done).
("Convex hull" is the area inside an imaginary
rubber band stretched around all the X,Y points.)
griddata works by first constructing a Delaunay triangulation
of the input X,Y, then doing
Natural neighbor
interpolation. This is robust and quite fast.
BivariateSpline, though, can extrapolate,
generating wild swings without warning.
Furthermore, all the *Spline routines in Fitpack
are very sensitive to smoothing parameter S.
Dierckx's book (books.google isbn 019853440X p. 89) says:
if S is too small, the spline approximation is too wiggly
and picks up too much noise (overfit);
if S is too large the spline will be too smooth
and signal will be lost (underfit).
Interpolation of scattered data is hard, smoothing not easy, both together really hard.
What should an interpolator do with big holes in XY, or with very noisy Z ?
("If you want to sell it, you're going to have to describe it.")
Yet more notes, fine print:
1d vs 2d: Some interpolators take X,Y,Z either 1d or 2d.
Others take 1d only, so flatten before interpolating:
Xmesh, Ymesh = np.meshgrid( np.linspace(0,1,Nx), np.linspace(0,1,Ny) )
Z = f( Xmesh, Ymesh ) # Nx x Ny
Znew = griddata( Xmesh.flatten(), Ymesh.flatten(), Z.flatten(), Xnew, Ynew )
On masked arrays: matplotlib handles them just fine,
plotting only unmasked / non-NaN points.
But I wouldn't bet that that a bozo numpy/scipy functions would work at all.
Check for interpolation outside the convex hull of X,Y like this:
Znew = griddata(...)
nmask = np.ma.count_masked(Znew)
if nmask > 0:
print "info: griddata: %d of %d points are masked, not interpolated" % (
nmask, Znew.size)
# Znew = Znew.data # array with NaNs
On polar coordinates:
X,Y and Xnew,Ynew should be in the same space,
both Cartesion, or both in [rmin .. rmax] x [tmin .. tmax].
To plot (r, theta, z) points in 3d:
from mpl_toolkits.mplot3d import Axes3D
Znew = griddata( R,T,Z, Rnew,Tnew )
ax = Axes3D(fig)
ax.plot_surface( Rnew * np.cos(Tnew), Rnew * np.sin(Tnew), Znew )
See also (haven't tried this):
ax = subplot(1,1,1, projection="polar", aspect=1.)
ax.pcolormesh(theta, r, Z)
Two tips for the wary programmer:
check for outliers, or funny scaling:
def minavmax( X ):
m = np.nanmin(X)
M = np.nanmax(X)
av = np.mean( X[ - np.isnan(X) ]) # masked ?
histo = np.histogram( X, bins=5, range=(m,M) ) [0]
return "min %.2g av %.2g max %.2g histo %s" % (m, av, M, histo)
for nm, x in zip( "X Y Z Xnew Ynew Znew".split(),
(X,Y,Z, Xnew,Ynew,Znew) ):
print nm, minavmax(x)
check interpolation with simple data:
interpolate( X,Y,Z, X,Y ) -- interpolate at the same points
interpolate( X,Y, np.ones(len(X)), Xnew,Ynew ) -- constant 1 ?