Plot density function on sphere surface using plotly (python) - python

I'm interested in plotting a real-valued function f(x,y,z)=a, where (x,y,z) is a 3D point on the sphere and a is a real number. I calculate the Cartesian coordinates of the points of the sphere as follows, but I have no clue on how to visualize the value of f on each of those points.
import plotly.graph_objects as go
import numpy as np
fig = go.Figure(layout=go.Layout(title=go.layout.Title(text=title), hovermode=False))
# Create mesh grid for spherical coordinates
phi, theta = np.mgrid[0.0:np.pi:100j, 0.0:2.0 * np.pi:100j]
# Get Cartesian mesh grid
x = np.sin(phi) * np.cos(theta)
y = np.sin(phi) * np.sin(theta)
z = np.cos(phi)
# Plot sphere surface
self.fig.add_surface(x=x, y=y, z=z, opacity=0.35)
fig.show()
I would imagine/expect/like a visualization like this
Additionally, I also have the gradient of f calculated in closed-form (i.e., for each (x,y,z) I calculate the 3D-dimensional gradient of f). Is there a way of plotting this vector field, similarly to what is shown in the figure above?

Here's an answer that's far from perfect, but hopefully that's enough for you to build on.
For the sphere itself, I don't know of any "shortcut" to do something like that in plotly, so my approach is simply to manually create a sphere mesh. Generating the vertices is simple, for example like you did - the slightly more tricky part is figuring out the vertex indices for the triangles (which depends on the vertex generation scheme). There are various algorithms to do that smoothly (i.e. generating a sphere with no "tip"), I hacked something crude just for the demonstration. Then we can use the Mesh3d object to display the sphere along with the intensities and your choice of colormap:
N = 100 # Sphere resolution (both rings and segments, can be separated to different constants)
theta, z = np.meshgrid(np.linspace(-np.pi, np.pi, N), np.linspace(-1, 1, N))
r = np.sqrt(1 - z ** 2)
x = r * np.cos(theta)
y = r * np.sin(theta)
x = x.ravel()
y = y.ravel()
z = z.ravel()
# Triangle indices
indices = np.arange(N * (N - 1) - 1)
i1 = np.concatenate([indices, (indices // N + 1) * N + (indices + 1) % N])
i2 = np.concatenate([indices + N, indices // N * N + (indices + 1) % N])
i3 = np.concatenate([(indices // N + 1) * N + (indices + 1) % N, indices])
# Point intensity function
def f(x, y, z):
return (np.cos(x * 2) + np.sin(y ** 2) + np.sin(z) + 3) / 6
fig = go.Figure(data=[
go.Mesh3d(
x=x,
y=y,
z=z,
colorbar_title='f(x, y, z)',
colorscale=[[0, 'gold'],
[0.5, 'mediumturquoise'],
[1, 'magenta']],
intensity = f(x, y, z),
i = i1,
j = i2,
k = i3,
name='y',
showscale=True
)
])
fig.show()
This yields the following interactive plot:
To add the vector field you can use the Cone plot; this requires some tinkering because when I simply draw the cones at the same x, y, z position as the sphere, some of the cones are partially or fully occluded by the sphere. So I generate another sphere, with a slightly larger radius, and place the cones there. I also played with some lighting parameters to make it black like in your example. The full code looks like this:
N = 100 # Sphere resolution (both rings and segments, can be separated to different constants)
theta, z = np.meshgrid(np.linspace(-np.pi, np.pi, N), np.linspace(-1, 1, N))
r = np.sqrt(1 - z ** 2)
x = r * np.cos(theta)
y = r * np.sin(theta)
x = x.ravel()
y = y.ravel()
z = z.ravel()
# Triangle indices
indices = np.arange(N * (N - 1) - 1)
i1 = np.concatenate([indices, (indices // N + 1) * N + (indices + 1) % N])
i2 = np.concatenate([indices + N, indices // N * N + (indices + 1) % N])
i3 = np.concatenate([(indices // N + 1) * N + (indices + 1) % N, indices])
# Point intensity function
def f(x, y, z):
return (np.cos(x * 2) + np.sin(y ** 2) + np.sin(z) + 3) / 6
# Vector field function
def grad_f(x, y, z):
return np.stack([np.cos(3 * y + 5 * x),
np.sin(z * y),
np.cos(4 * x - 3 * y + z * 7)], axis=1)
# Second sphere for placing cones
N2 = 50 # Smaller resolution (again rings and segments combined)
R2 = 1.05 # Slightly larger radius
theta2, z2 = np.meshgrid(np.linspace(-np.pi, np.pi, N2), np.linspace(-R2, R2, N2))
r2 = np.sqrt(R2 ** 2 - z2 ** 2)
x2 = r2 * np.cos(theta2)
y2 = r2 * np.sin(theta2)
x2 = x2.ravel()
y2 = y2.ravel()
z2 = z2.ravel()
uvw = grad_f(x2, y2, z2)
fig = go.Figure(data=[
go.Mesh3d(
x=x,
y=y,
z=z,
colorbar_title='f(x, y, z)',
colorscale=[[0, 'gold'],
[0.5, 'mediumturquoise'],
[1, 'magenta']],
intensity = f(x, y, z),
i = i1,
j = i2,
k = i3,
name='y',
showscale=True
),
go.Cone(
x=x2, y=y2, z=z2, u=uvw[:, 0], v=uvw[:, 1], w=uvw[:, 2], sizemode='absolute', sizeref=2, anchor='tail',
lighting_ambient=0, lighting_diffuse=0, opacity=.2
)
])
fig.show()
And yields this plot:
Hope this helps. There are a lot of tweaks to the display, and certainly better ways to construct a sphere mesh (e.g. see this article), so there should be a lot of freedom there (albeit at the cost of some work).
Good luck!

Related

How to Create 3D Torus from Circle Revolved about x=2r, r is the radius of circle (Python or JULIA)

I need help to create a torus out of a circle by revolving it about x=2r, r is the radius of the circle.
I am open to either JULIA code or Python code. Whichever that can solve my problem the most efficient.
I have Julia code to plot circle and the x=2r as the axis of revolution.
using Plots, LaTeXStrings, Plots.PlotMeasures
gr()
θ = 0:0.1:2.1π
x = 0 .+ 2cos.(θ)
y = 0 .+ 2sin.(θ)
plot(x, y, label=L"x^{2} + y^{2} = a^{2}",
framestyle=:zerolines, legend=:outertop)
plot!([4], seriestype="vline", color=:green, label="x=2a")
I want to create a torus out of it, but unable, meanwhile I have solid of revolution Python code like this:
# Calculate the surface area of y = sqrt(r^2 - x^2)
# revolved about the x-axis
import matplotlib.pyplot as plt
import numpy as np
import sympy as sy
x = sy.Symbol("x", nonnegative=True)
r = sy.Symbol("r", nonnegative=True)
def f(x):
return sy.sqrt(r**2 - x**2)
def fd(x):
return sy.simplify(sy.diff(f(x), x))
def f2(x):
return sy.sqrt((1 + (fd(x)**2)))
def vx(x):
return 2*sy.pi*(f(x)*sy.sqrt(1 + (fd(x) ** 2)))
vxi = sy.Integral(vx(x), (x, -r, r))
vxf = vxi.simplify().doit()
vxn = vxf.evalf()
n = 100
fig = plt.figure(figsize=(14, 7))
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222, projection='3d')
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224, projection='3d')
# 1 is the starting point. The first 3 is the end point.
# The last 200 is the number of discretization points.
# help(np.linspace) to read its documentation.
x = np.linspace(1, 3, 200)
# Plot the circle
y = np.sqrt(2 ** 2 - x ** 2)
t = np.linspace(0, np.pi * 2, n)
xn = np.outer(x, np.cos(t))
yn = np.outer(x, np.sin(t))
zn = np.zeros_like(xn)
for i in range(len(x)):
zn[i:i + 1, :] = np.full_like(zn[0, :], y[i])
ax1.plot(x, y)
ax1.set_title("$f(x)$")
ax2.plot_surface(xn, yn, zn)
ax2.set_title("$f(x)$: Revolution around $y$")
# find the inverse of the function
y_inverse = x
x_inverse = np.power(2 ** 2 - y_inverse ** 2, 1 / 2)
xn_inverse = np.outer(x_inverse, np.cos(t))
yn_inverse = np.outer(x_inverse, np.sin(t))
zn_inverse = np.zeros_like(xn_inverse)
for i in range(len(x_inverse)):
zn_inverse[i:i + 1, :] = np.full_like(zn_inverse[0, :], y_inverse[i])
ax3.plot(x_inverse, y_inverse)
ax3.set_title("Inverse of $f(x)$")
ax4.plot_surface(xn_inverse, yn_inverse, zn_inverse)
ax4.set_title("$f(x)$: Revolution around $x$ \n Surface Area = {}".format(vxn))
plt.tight_layout()
plt.show()
Here is a way that actually allows rotating any figure in the XY plane around the Y axis.
"""
Rotation of a figure in the XY plane about the Y axis:
ϕ = angle of rotation
z' = z * cos(ϕ) - x * sin(ϕ)
x' = z * sin(ϕ) + x * cos(ϕ)
y' = y
"""
using Plots
# OP definition of the circle, but we put center at x, y of 4, 0
# for the torus, otherwise we get a bit of a sphere
θ = 0:0.1:2.1π
x = 4 .+ 2cos.(θ) # center at (s, 0, 0)
y = 0 .+ 2sin.(θ)
# add the original z values as 0
z = zeros(length(x))
plot(x, y, z, color=:red)
# add the rotation axis
ϕ = 0:0.1:π/2 # for full torus use 2π at stop of range
xprime, yprime, zprime = Float64[], Float64[], Float64[]
for a in ϕ, i in eachindex(θ)
push!(zprime, z[i] + z[i] * cos(a) - x[i] * sin(a))
push!(xprime, z[i] * sin(a) + x[i] * cos(a))
push!(yprime, y[i])
end
plot!(xprime, yprime, zprime, alpha=0.3, color=:green)
Here is a way using the Meshes package for the construction of the mesh and the MeshViz package for the visualization. You'll just have to translate to fulfill your desiderata.
using Meshes
using MeshViz
using LinearAlgebra
using GLMakie
# revolution of the polygon defined by (x,y) around the z-axis
# x and y have the same length
function revolution(x, y, n)
u_ = LinRange(0, 2*pi, n+1)[1:n]
j_ = 1:(length(x) - 1) # subtract 1 because of periodicity
function f(u, j)
return [x[j] * sin(u), x[j] * cos(u), y[j]]
end
points = [f(u, j) for u in u_ for j in j_]
topo = GridTopology((length(j_), n), (true, true))
return SimpleMesh(Meshes.Point.(points), topo)
end
# define the section to be rotated: a circle
R = 3 # major radius
r = 1 # minor radius
ntheta = 100
theta_ = LinRange(0, 2*pi, ntheta)
x = [R + r*cos(theta) for theta in theta_]
y = [r*sin(theta) for theta in theta_]
# make mesh
mesh = revolution(x, y, 100)
# visualize mesh
viz(mesh)
EDIT: animation
using Meshes
using MeshViz
using LinearAlgebra
using GLMakie
using Makie
using Printf
function revolutionTorus(R, r, alpha; n1=30, n2=90)
theta_ = LinRange(0, 2, n1+1)[1:n1]
x = [R + r*cospi(theta) for theta in theta_]
y = [r*sinpi(theta) for theta in theta_]
full = alpha == 2
u_ = LinRange(0, alpha, n2 + full)[1:n2]
function f(u, j)
return [x[j] * sinpi(u), x[j] * cospi(u), y[j]]
end
points = [f(u, j) for u in u_ for j in 1:n1]
topo = GridTopology((n1, n2 - !full), (true, full))
return SimpleMesh(Meshes.Point.(points), topo)
end
# generates `nframes` meshes for alpha = 0 -> 2 (alpha is a multiple of pi)
R = 3
r = 1
nframes = 10
alpha_ = LinRange(0, 2, nframes+1)[2:(nframes+1)]
meshes = [revolutionTorus(R, r, alpha) for alpha in alpha_]
# draw and save the frames in a loop
for i in 1:nframes
# make a bounding box in order that all frames have the same aspect
fig, ax, plt =
viz(Meshes.Box(Meshes.Point(-4.5, -4.5, -2.5), Meshes.Point(4.5, 4.5, 2.5)); alpha = 0)
ax.show_axis = false
viz!(meshes[i])
scale!(ax.scene, 1.8, 1.8, 1.8)
png = #sprintf "revolutionTorus%02d.png" i
Makie.save(png, fig)
end
# make GIF with ImageMagick
comm = #cmd "convert -delay 1x2 'revolutionTorus*.png' revolutionTorus.gif"
run(comm)

Draw a circle with a specified tilt angle in three-dimensional space with Python

I want to draw a circle with a specified angle of inclination in 3D space using Python. Similar to the image below:
Image
I can already draw circles in 2D. I modified my program by referring to the link below:
Masking a 3D numpy array with a tilted disc
import numpy as np
import matplotlib.pyplot as plt
r = 5.0
a, b, c = (0.0, 0.0, 0.0)
angle = np.pi / 6 # "tilt" of the circle
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(-10,10)
ax.set_ylim(-10,10)
ax.set_zlim(-10,10)
phirange = np.linspace(0, 2 * np.pi, 300) #to make a full circle
x = a + r * np.cos(phirange)
y = b + r * np.sin(phirange)
z= c
ax.plot(x, y, z )
plt.show()
Now I can draw the circle in 3D space, but I can't get the circle to tilt at the angle I want.
I tried to modify the code in the Z part, the circle can be tilted, but not the result I want.
z = c + r * np.cos(phirange) * np.sin(angle)
Result image:
Do the X and Y parts also need to be modified? What should I do?
update: the circle tilt with other axis
Let i = (1, 0, 0), j = (0, 1, 0). Those are the direction vectors of the x-axis and y-axis, respectively. Those two vectors form an orthonormal basis of the horizontal plane. Here "orthonormal" means the two vectors are orthogonal and both have length 1.
A circle on the horizontal plane with centre C and radius r consists in all points that can be written as C + r * (cos(theta) * i + sin(theta) * j), for all values of theta in range [0, 2 pi]. Note that this works with i and j, but it would have worked equally with any other orthonormal basis of the horizontal plane.
A circle in any other plane can be described exactly the same way, by replacing i and j with two vectors that form an orthonormal basis of that plane.
According to your image, the "tilted plane at angle tilt" has the following orthonormal basis:
a = (cos(tilt), 0, sin(tilt))
b = (0, 1, 0)
You can check that these are two vectors in your plane, that they are orthogonal and that they both have norm 1. Thus they are indeed an orthonormal basis of your plane.
Therefore a circle in your plane, with centre C and radius r, can be described as all the points C + r * (cos(theta) * a + sin(theta) * b), where theta is in range [0, 2 pi].
In terms of x,y,z, this translates into the following system of three parametric equations:
x = x_C + r * cos(theta) * x_a + r * sin(theta) * x_b
y = y_C + r * cos(theta) * y_a + r * sin(theta) * y_b
z = z_C + r * cos(theta) * z_a + r * sin(theta) * z_b
This simplifies a lot, because x_b, y_a, z_b are all equal to 0:
x = x_C + r * cos(theta) * x_a # + sin(theta) * x_b, but x_b == 0
y = y_C + r * sin(theta) * y_b # + cos(theta) * y_a, but y_a == 0
z = z_C + r * cos(theta) * z_a # + sin(theta) * z_b, but z_b == 0
Replacing x_a, y_b and z_a by their values:
x = x_C + r * cos(tilt) * cos(theta)
y = y_C + r * sin(theta)
z = z_C + r * sin(tilt) * cos(theta)
In python:
import numpy as np
import matplotlib.pyplot as plt
# parameters of circle
r = 5.0 # radius
x_C, y_C, z_C = (0.0, 0.0, 0.0) # centre
tilt = np.pi / 6 # tilt of plane around y-axis
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(-10,10)
ax.set_ylim(-10,10)
ax.set_zlim(-10,10)
theta = np.linspace(0, 2 * np.pi, 300) #to make a full circle
x = x_C + r * np.cos(tilt) * np.cos(theta)
y = y_C + r * np.sin(theta)
z = z_C + r * np.sin(tilt) * np.cos(theta)
ax.plot(x, y, z )
plt.show()

How to plot phase planes of polar differential equations

I am trying to plot some phase planes with limit circles. I have the differential equations in polar form:
drdt = r(r^2 -1) dθdt = 1
is there a way to plot them from this using matplotlib? I was able to plot one that I have the original system form by using the original system like this:
xvalues, yvalues = np.meshgrid(np.arange(-2, 2, 0.1), np.arange(-2, 2, 0.1))
xdot = xvalues + yvalues - xvalues * (xvalues ** 2 + yvalues ** 2)
ydot = -xvalues + yvalues - yvalues * (xvalues ** 2 + yvalues ** 2)
plt.streamplot(xvalues, yvalues, xdot, ydot)
plt.grid();
plt.show()
this system is
dxdt = x + y - x(x^2 + y2)
dydt = -x + y - y(x^2 + y2)
and its polar form is:
drdt = r(1-r^2) dθdt = 1
But it would be tedious to covert them all to the original system, if its even possible. Appreciate any help I can get.
Update
So I came up with a method that comes close enough by solving the differential with scipy for a range of initial radii, and then plotting on X, Y with the conversion formula
times = np.linspace(0, 10)
r0s = np.arange(0, 3, 0.2)
for r0 in r0s:
z0 = [r0, r0]
solution = odeint(rmodel, z0, times)
x = solution[:,0] * np.cos(solution[:,1])
y = solution[:,0] * np.sin(solution[:,1])
plt.plot(x, y)
This works well enough for now but I would really like a better solution.
So I came up with a method that comes close enough by solving the differential with scipy for a range of initial radii, and then plotting on X, Y with the conversion formula
times = np.linspace(0, 10)
r0s = np.arange(0, 3, 0.2)
for r0 in r0s:
z0 = [r0, r0]
solution = odeint(rmodel, z0, times)
x = solution[:,0] * np.cos(solution[:,1])
y = solution[:,0] * np.sin(solution[:,1])
plt.plot(x, y)
This works well enough for my purposes.

How to get the x,y coordinates of a offset spline from a x,y list of points and offset distance

I need to make an offset parallel enclosure of an airfoil profile curve, but I cant figure out how to make all the points be equidistant to the points on the primary profile curve at desired distance.
this is my example airfoil profile
this is my best and not good approach
EDIT #Patrick Solution for distance 0.2
You'll have to special-case slopes of infinity/zero, but the basic approach is to use interpolation to calculate the slope at a point, and then find the perpendicular slope, and then calculate the point at that distance.
I have modified the example from here to add a second graph. It works with the data file you provided, but you might need to change the sign calculation for a different envelope.
EDIT As per your comments about wanting the envelope to be continuous, I have added a cheesy semicircle at the end that gets really close to doing this for you. Essentially, when creating the envelope, the rounder and more convex you can make it, the better it will work. Also, you need to overlap the beginning and the end, or you'll have a gap.
Also, it could almost certainly be made more efficient -- I am not a numpy expert by any means, so this is just pure Python.
def offset(coordinates, distance):
coordinates = iter(coordinates)
x1, y1 = coordinates.next()
z = distance
points = []
for x2, y2 in coordinates:
# tangential slope approximation
try:
slope = (y2 - y1) / (x2 - x1)
# perpendicular slope
pslope = -1/slope # (might be 1/slope depending on direction of travel)
except ZeroDivisionError:
continue
mid_x = (x1 + x2) / 2
mid_y = (y1 + y2) / 2
sign = ((pslope > 0) == (x1 > x2)) * 2 - 1
# if z is the distance to your parallel curve,
# then your delta-x and delta-y calculations are:
# z**2 = x**2 + y**2
# y = pslope * x
# z**2 = x**2 + (pslope * x)**2
# z**2 = x**2 + pslope**2 * x**2
# z**2 = (1 + pslope**2) * x**2
# z**2 / (1 + pslope**2) = x**2
# z / (1 + pslope**2)**0.5 = x
delta_x = sign * z / ((1 + pslope**2)**0.5)
delta_y = pslope * delta_x
points.append((mid_x + delta_x, mid_y + delta_y))
x1, y1 = x2, y2
return points
def add_semicircle(x_origin, y_origin, radius, num_x = 50):
points = []
for index in range(num_x):
x = radius * index / num_x
y = (radius ** 2 - x ** 2) ** 0.5
points.append((x, -y))
points += [(x, -y) for x, y in reversed(points)]
return [(x + x_origin, y + y_origin) for x, y in points]
def round_data(data):
# Add infinitesimal rounding of the envelope
assert data[-1] == data[0]
x0, y0 = data[0]
x1, y1 = data[1]
xe, ye = data[-2]
x = x0 - (x0 - x1) * .01
y = y0 - (y0 - y1) * .01
yn = (x - xe) / (x0 - xe) * (y0 - ye) + ye
data[0] = x, y
data[-1] = x, yn
data.extend(add_semicircle(x, (y + yn) / 2, abs((y - yn) / 2)))
del data[-18:]
from pylab import *
with open('ah79100c.dat', 'rb') as f:
f.next()
data = [[float(x) for x in line.split()] for line in f if line.strip()]
t = [x[0] for x in data]
s = [x[1] for x in data]
round_data(data)
parallel = offset(data, 0.1)
t2 = [x[0] for x in parallel]
s2 = [x[1] for x in parallel]
plot(t, s, 'g', t2, s2, 'b', lw=1)
title('Wing with envelope')
grid(True)
axes().set_aspect('equal', 'datalim')
savefig("test.png")
show()
If you are willing (and able) to install a third-party tool, I'd highly recommend the Shapely module. Here's a small sample that offsets both inward and outward:
from StringIO import StringIO
import matplotlib.pyplot as plt
import numpy as np
import requests
import shapely.geometry as shp
# Read the points
AFURL = 'http://m-selig.ae.illinois.edu/ads/coord_seligFmt/ah79100c.dat'
afpts = np.loadtxt(StringIO(requests.get(AFURL).content), skiprows=1)
# Create a Polygon from the nx2 array in `afpts`
afpoly = shp.Polygon(afpts)
# Create offset airfoils, both inward and outward
poffafpoly = afpoly.buffer(0.03) # Outward offset
noffafpoly = afpoly.buffer(-0.03) # Inward offset
# Turn polygon points into numpy arrays for plotting
afpolypts = np.array(afpoly.exterior)
poffafpolypts = np.array(poffafpoly.exterior)
noffafpolypts = np.array(noffafpoly.exterior)
# Plot points
plt.plot(*afpolypts.T, color='black')
plt.plot(*poffafpolypts.T, color='red')
plt.plot(*noffafpolypts.T, color='green')
plt.axis('equal')
plt.show()
And here's the output; notice how the 'bowties' (self-intersections) on the inward offset are automatically removed:

Extracting 1D ellipse from 2D image

I've trying to simulate a 2D Sérsic profile and then testing an extraction routine on it. However, when I do a test by extracting all the points lying along an ellipse supposedly aligned with an image, I get a periodic function. It is meant to be a straight line since all points along the ellipse should have equal intensity, although there will be a small amount of deviation due to rounding errors in the rough coordinate estimation (get_I()).
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import NearestNDInterpolator
def rotate(x, y, angle):
x1 = x*np.cos(angle) + y*np.sin(angle)
y1 = y*np.cos(angle) - x*np.sin(angle)
return x1, y1
def sersic_1d(R, mu0, h, n, zp=0):
exponent = (R / h) ** (1 / n)
I0 = np.exp((zp - mu0) / 2.5)
return I0 * np.exp(-1.* exponent)
def sersic_2d(x, y, e, i, mu0, h, n, zp=0):
xp, yp = rotate(x, y, i)
alpha = np.arctan2(yp, xp * (1-e))
a = xp / np.cos(alpha)
b = a * (1 - e)
# R2 = (a*a) + ((1 - (e*e)) * yp*yp)
return sersic_1d(a, mu0, h, n, zp)
def ellipse(x0, y0, a, e, i, theta):
b = a * (1 - e)
x = a * np.cos(theta)
y = b * np.sin(theta)
x, y = rotate(x, y, i)
return x + x0, y + y0
def get_I(x, y, Z):
return Z[np.round(x).astype(int), np.round(y).astype(int)]
if __name__ == '__main__':
n = np.linspace(-100,100,1000)
nx, ny = np.meshgrid(n, n)
Z = sersic_2d(nx, ny, 0.5, 0., 0, 50, 1, 25)
theta = np.linspace(0, 2*np.pi, 1000.)
a = 100.
e = 0.5
i = np.pi / 4.
x, y = ellipse(0, 0, a, e, i, theta)
I = get_I(x, y, Z)
plt.plot(I)
# plt.imshow(Z)
plt.show()
However, What I actually get is a massive periodic function. I've checked the alignment and it's correct and the float-> int rounding errors can't account for this kind of shift?
Any ideas?
There are two things that strike me as odd, one of which for sure is not what you wanted, the other I'm not sure about because astronomy is not my field of expertise.
The first is in your function get_I:
def get_I(x, y, Z):
return Z[np.round(x).astype(int), np.round(y).astype(int)]
When you call that function, x an y outline an ellipse, with its center at the origin (0,0). That means x and y both become negative at some point. The indexing you perfom in that function will then take values from the array's last elements, because Z[0,0] is in fact the top left corner of the image (which you plotted, but commented), while Z[-1, -1] is the bottom right corner. What you want is to take the values of Z that are on the ellipse contour, but both have to have the same center. To do that, you would first make sure you use an uneven amount of samples for n (which ultimately defines the shape of Z) and second, you would add an indexing offset:
def get_I(x, y, Z):
offset = Z.shape[0]//2
return Z[np.round(y).astype(int) + offset, np.round(x).astype(int) + offset]
...
n = np.linspace(-100,100,1001) # changed from 1000 to 1001 to ensure a point of origin is present and that the image exhibits point symmetry
Also notice that I changed the order of y and x in get_I: that's because you first index along the rows (for which we usually take the y-coordinate) and only then along the columns (which map to the x-coordinate in most conventions).
The second item that struck me as unusual is that your ellipse has its axes at an angle of pi/4 with respect to the horizontal axis, whereas your sersic (which maps to the 2D array of Z) does not have a tilt at all.
Changing all that, I end up with this code:
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
def rotate(x, y, angle):
x1 = x*np.cos(angle) + y*np.sin(angle)
y1 = y*np.cos(angle) - x*np.sin(angle)
return x1, y1
def sersic_1d(R, mu0, h, n, zp=0):
exponent = (R / h) ** (1 / n)
I0 = np.exp((zp - mu0) / 2.5)
return I0 * np.exp(-1.* exponent)
def sersic_2d(x, y, e, ang, mu0, h, n, zp=0):
xp, yp = rotate(x, y, ang)
alpha = np.arctan2(yp, xp * (1-e))
a = xp / np.cos(alpha)
b = a * (1 - e)
return sersic_1d(a, mu0, h, n, zp)
def ellipse(x0, y0, a, e, i, theta):
b = a * (1 - e) # half of a
x = a * np.cos(theta)
y = b * np.sin(theta)
x, y = rotate(x, y, i) # rotated by 45deg
return x + x0, y + y0
def get_I(x, y, Z):
offset = Z.shape[0]//2
return Z[np.round(y).astype(int) + offset, np.round(x).astype(int) + offset]
#return Z[np.round(y).astype(int), np.round(x).astype(int)]
if __name__ == '__main__':
n = np.linspace(-100,100,1001) # changed
nx, ny = np.meshgrid(n, n)
ang = 0;#np.pi / 4.
Z = sersic_2d(nx, ny, 0.5, ang=0, mu0=0, h=50, n=1, zp=25)
f, ax = plt.subplots(1,2)
dn = n[1]-n[0]
ax[0].imshow(Z, cmap='gray', aspect='equal', extent=[-100-dn/2, 100+dn/2, -100-dn/2, 100+dn/2])
theta = np.linspace(0, 2*np.pi, 1000.)
a = 20. # decreased long axis of ellipse to see the intensity-map closer to the "center of the galaxy"
e = 0.5
x, y = ellipse(0,0, a, e, ang, theta)
I = get_I(x, y, Z)
ax[0].plot(x,y) # easier to see where you want the intensities
ax[1].plot(I)
plt.show()
and this image:
The intensity variations look like quantisation noise to me, with the exception of the peaks, which are due to the asymptote in sersic_1d.

Categories