Random point inside annulus with a shifted hole - python

First of all, will appreciate if someone will give me a proper term for "annulus with a shifted hole", see exactly what kind of shape I mean on a picture below.
Back to main question: I want to pick a random point in the orange area, uniform distribution is not required. For a case of a usual annulus I would've picked random point in (r:R) range and a random angle, then transform those to x,y and it's done. But for this unusual shape... is there even a "simple" formula for that, or should I approach it by doing some kind of polygonal approximation of a shape?
I'm interested in a general approach but will appreciate an example in python, javascript or any coding language of your choice.

Here's a simple method that gives a uniform distribution with no resampling.
For simplicity assume that the center of the outer boundary circle (radius r_outer) is at (0, 0) and that the center of the inner circular boundary (radius r_inner) lies at (x_inner, y_inner).
Write D for the outer disk, H1 for the subset of the plane given by the off-center inner hole, and H2 for the central disk of radius r_inner, centered at (0, 0).
Now suppose that we ignore the fact that the inner circle is not central, and instead of sampling from D-H1 we sample from D-H2 (which is easy to do uniformly). Then we've made two mistakes:
there's a region A = H1 - H2 that we might sample from, even though those samples shouldn't be in the result.
there's a region B = H2 - H1 that we never sample from, even though we should
But here's the thing: the regions A and B are congruent: given any point (x, y) in the plane, (x, y) is in H2 if and only if (x_inner - x, y_inner - y) is in H1, and it follows that (x, y) is in A if and only if (x_inner - x, y_inner - y) is in B! The map (x, y) -> (x_inner - x, y_inner - y) represents a rotation by 180 degress around the point (0.5*x_inner, 0.5*y_inner). So there's a simple trick: generate from D - H2, and if we end up with something in H1 - H2, rotate to get the corresponding point of H2 - H1 instead.
Here's the code. Note the use of the square root of a uniform distribution to choose the radius: this is a standard trick. See this article, for example.
import math
import random
def sample(r_outer, r_inner, x_inner, y_inner):
"""
Sample uniformly from (x, y) satisfiying:
x**2 + y**2 <= r_outer**2
(x-x_inner)**2 + (y-y_inner)**2 > r_inner**2
Assumes that the inner circle lies inside the outer circle;
i.e., that hypot(x_inner, y_inner) <= r_outer - r_inner.
"""
# Sample from a normal annulus with radii r_inner and r_outer.
rad = math.sqrt(random.uniform(r_inner**2, r_outer**2))
angle = random.uniform(-math.pi, math.pi)
x, y = rad*math.cos(angle),rad*math.sin(angle)
# If we're inside the forbidden hole, reflect.
if math.hypot(x - x_inner, y - y_inner) < r_inner:
x, y = x_inner - x, y_inner - y
return x, y
And an example plot, generated by the following:
import matplotlib.pyplot as plt
samples = [sample(5, 2, 1.0, 2.0) for _ in range(10000)]
xs, ys = zip(*samples)
plt.scatter(xs, ys, s=0.1)
plt.axis("equal")
plt.show()

Do you really need exact sampling? Because with acceptance/rejection it should work just fine. I assume big orange circle is located at (0,0)
import math
import random
def sample_2_circles(xr, yr, r, R):
"""
R - big radius
r, xr, yr - small radius and its position
"""
x = xr
y = yr
cnd = True
while cnd:
# sample uniformly in whole orange circle
phi = 2.0 * math.pi * random.random()
rad = R * math.sqrt(random.random())
x = rad * math.cos(phi)
y = rad * math.sin(phi)
# check condition - if True we continue in the loop with sampling
cnd = ( (x-xr)**2 + (y-yr)**2 < r*r )
return (x,y)

Since you have shown no equation, algorithm, or code of your own, but just an outline of an algorithm for center-aligned circles, I'll also just give the outline of an algorithm here for the more general case.
The smaller circle is the image of the larger circle under a similarity transformation. I.e. there is a fixed point in the larger circle and a ratio (which is R/r, greater than one) such that you can take any point on the smaller circle, examine the vector from the fixed point to that point, and multiply that vector by the ratio, then the end of that vector when it starts from the fixed point is a point on the larger circle. This transformation is one-to-one.
So you can choose a random point on the smaller circle (choose the angle at random between 0 and two-pi) and choose a ratio at random between 1 and the proportionality ratio R/r between the circles. Then use that the similarity transformation with the same fixed point but using the random ratio to get the image point of the just-chosen point on the smaller circle. This is a random point in your desired region.
This method is fairly simple. In fact, the hardest mathematical part is finding the fixed point of the similarity transformation. But this is pretty easy, given the centers and radii of the two circles. Hint: the transformation takes the center of the smaller circle to the center of the larger circle.
Ask if you need more detail. My algorithm does not yield a uniform distribution: the points will be more tightly packed where the circles are closest together and less tightly packed where the circles are farthest apart.
Here is some untested Python 3.6.2 code that does the above. I'll test it and show a graphic for it when I can.
import math
import random
def rand_pt_between_circles(x_inner,
y_inner,
r_inner,
x_outer,
y_outer,
r_outer):
"""Return a random floating-point 2D point located between the
inner and the outer circles given by their center coordinates and
radii. No error checking is done on the parameters."""
# Find the fixed point of the similarity transformation from the
# inner circle to the outer circle.
x_fixed = x_inner - (x_outer - x_inner) / (r_outer - r_inner) * r_inner
y_fixed = y_inner - (y_outer - y_inner) / (r_outer - r_inner) * r_inner
# Find a a random transformation ratio between 1 and r_outer / r_inner
# and a random point on the inner circle
ratio = 1 + (r_outer - r_inner) * random.random()
theta = 2 * math.pi * random.random()
x_start = x_inner + r_inner * math.cos(theta)
y_start = y_inner + r_inner * math.sin(theta)
# Apply the similarity transformation to the random point.
x_result = x_fixed + (x_start - x_fixed) * ratio
y_result = y_fixed + (y_start - y_fixed) * ratio
return x_result, y_result

The acceptance/rejection method as described by Severin Pappadeux is probably the simplest.
For a direct approach, you can also work in polar coordinates, with the center of the hole as the pole.
The polar equation (Θ, σ) (sorry, no rho) of the external circle will be
(σ cosΘ - xc)² + (σ sinΘ - yc)² = σ² - 2(cosΘ xc + sinΘ yc)σ + xc² + yc² = R²
This is a quadratic equation in σ, that you can easily solve in terms of Θ. Then you can draw an angle in 0, 2π an draw a radius between r and σ.
This won't give you a uniform distribution, because the range of σ is a function of Θ and because of the polar bias. This might be fixed by computing a suitable transfer function, but this is a little technical and probably not tractable analytically.

Related

Raycasting to calculate the volume of a space

I'm trying to use ray casting to gather all the surfaces in a room and determine it's volume.
I have a centroid location where the rays will be coming from, but I'm drawing a blank on how to get the rays in all 360 degrees (in 3D space).
I'm not getting any points on the floors or ceilings, it's like it's doing a 60 degree spread rotated about the Z axis.
I think I have the rest of it working, but this is stumping me.
for y in range(360):
for x in range(360):
vector = DB.XYZ(math.sin(math.radians(x)), math.cos(math.radians(x)), math.cos(math.radians(y))).Normalize()
prox = ri.FindNearest(origin, direction).Proximity
point = origin + (direction * prox)
Look at it this way: x and y of vector are created from angle x (-> a circle in the plane) and then you add a z component which lies between -1 and 1 (which cos does). So it's obvious that you end up with a cylindrical distribution.
What you might want are spherical coordinates. Modify your code like this:
for y in range(-90, 91):
for x in range(360):
vector = DB.XYZ(math.sin(math.radians(x)) * cos(math.radians(y)),
math.cos(math.radians(x)) * cos(math.radians(y)),
math.sin(math.radians(y))) # Normalize unnecessary, since vector² = sin² * cos² + cos² * cos² + sin² = 1
prox = ri.FindNearest(origin, direction).Proximity
point = origin + (direction * prox)
But be aware that the angle distribution of rays is not uniform using spherical coordinates. At the poles it's more dense than at the equator. You can mitigate this e.g. by scaling the density of x down, depending on y. The surface elements scale down by cos(y)², so I think you have to scale by cos(y).

Visualisation of proximity of points to 4d-sphere in Python

I am looking for a way to visualise the proximity of points to a 4-dimensional sphere. For a circle I can simply use a scatter plot and observe the distribution of points near the unit circle as shown below. For a 3D sphere I can do something similar. However, how would I go about visualising this for a 4-dimensional sphere?
Is there a way to reduce the dimensionality to project the entire space into 3D? Obviously I can just take the norm of the points and see how close it is to 1, but I would like to have a visual aid of some sort.
Here is one way to convert 4-dimensional coordinates into 3-dimenstional coordinates that will give you a visualization of the distances of the points from the 4D sphere. Since you show no code or equations of your own I'll just give an overview. If you give more details on your own work then you can ask me for more details.
Take a point in 4 dimensions, let's say (x, y, z, w). Then convert those Cartesian coordinates to 4D spherical coordinates
(r, t1, t2, t3), where r is the distance of the point to the origin and t1, t2, t3 are reference angles. Formulas for the conversion are in Wikipedia's entry for n-sphere, though in my preferred transformation I would reverse the order of the Cartesian coordinates. In other words, we get the relations
w = r * cos(t1)
z = r * sin(t1) * cos(t2)
y = r * sin(t1) * sin(t2) * cos(t3)
x = r * sin(t1) * sin(t2) * sin(t3)
We now map that point to a point in 3D space by changing angle t1 to 90° (or pi/2 radians). This has the effect of "rotating" the point away from the w axis down into 3D space in regular spherical coordinates. The distances from the origin and from any 4-sphere centered at the origin were not changed. Now convert to 3D Cartesian coordinates with
z = r * cos(t2)
y = r * sin(t2) * cos(t3)
x = r * sin(t2) * sin(t3)
Now graph those as usual. Since distances to the origin and to the 4-sphere were not changed, this should be a useful visualization.
Looking at those equations, we realize that the values of x, y, and z were all divided by sin(t1). That means you could optimize the calculations by finding only sin(t1) with the formula
sin(t1) = sqrt((x*x + y*y + z*z) / (x*x + y*y + z*z + w*w))
There is no need to find r, t2, or t3 or even t1 itself. You need to be careful for the special case sin(t1) == 0.0, which happens only when x == y == z == 0. I would then map the 4D point (0, 0, 0, w) to the 3D point (w, 0, 0) and the visualization should still work well.
There are other, similar transformations you could use that may be more useful, such as changing angle t3 to zero rather than changing t1. This slightly reduces the calculations but you would need to permute the coordinates and the visualization uses only half the 3-sphere, I believe.
Of course, one way to graph that 3D point to a computer graphing surface is to now set t2 to 90° to get
y = r * cos(t3)
x = r * sin(t3)
and you will get a graph very much like the one you show in your question.
(NOTE: I changed the formulas above, based on further consideration of the best visualization.)

Straighten a spline using derivatives to determine rotation at each point

I'm working on straightening splines as a component of my larger project to straighten curved text.
After fitting a spline to my data points, I use scipy's splev to get the derivative of the spline at each point along the curve. Since the derivative gives me the slope of the tangent to the curve at a given point (unless I'm very confused), I determine the rotation needed to produce a straight line by comparing the derivative to a line with 0 slope.
Having established the rotation needed at each point to straighten my spline, I loop over each point and apply the corrective rotation to the current point and each preceding point.
The relevant code follows:
import numpy as np
from numpy import arange
from scipy import interpolate
import matplotlib.pyplot as plt
import math
import random
def rotate(origin, point, angle):
ox, oy = origin
px, py = point
qx = ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy)
qy = oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy)
return qx, qy
xxx = [0,2,4,4,2,0]
yyy = [0,2,4,6,8,10]
tckp, u = interpolate.splprep([xxx, yyy], s=3, k=2, nest=-1)
xpointsnew, ypointsnew = interpolate.splev(u, tckp)
dx, dy = interpolate.splev(u, tckp, der=1)
fullder = dy/dx
rotating_x = xxx
rotating_y = yyy
index = -1
for i in fullder:
index += 1
corrective_rotation = -(math.degrees(math.atan(0)-math.atan(fullder[index])))
print(corrective_rotation)
rotation_center = [rotating_x[index], rotating_y[index]]
target_indices = np.arange(0,index,1)
for i in target_indices:
rotation_target = [rotating_x[i], rotating_y[i]]
qx, qy = rotate(rotation_target,rotation_center,math.radians(corrective_rotation))
rotating_x[i] = qx
rotating_y[i] = qy
print(rotating_x)
print(rotating_y)
plt.plot(xpointsnew, ypointsnew, 'r-')
plt.plot(rotating_x, rotating_y, 'b-')
plt.show()
What I'm doing isn't working, but I'm not sure why. Not only is the resulting line not straight, it's also much shorter than the original curve. Is the approach outlined above fundamentally flawed in some way? Am I doing something stupid in my code? I would really appreciate a second pair of eyes.
A fundamental flaw of the algorithm is that it takes slope at a point as the amount of necessary rotation of one of two segments in which that point divides the curve. As an example, consider a straight line at 60 degrees. Your algorithm will produce rotation of 60 degrees at every knot of the line, in effect making them all 120-degree angles.
You are not rotating the entire curve, only a part of it (up to index in your version; after i in my version). The appropriate amount of rotation is how sharply the curve turns at that point, which is reflected by the change of its slope — not the slope itself.
Then there are minor details like
incorrect order of rotation_center and rotation_target in the list of arguments;
pointless conversion to degrees and back;
using atan(dy/dx) where atan2(dy, dx) should be used;
and the strange decision to rotate from the end of the curve.
Here is my version; the only changes are in the for loop.
for i in range(len(xxx)-1):
corrective_rotation = -(math.atan2(dy[i+1], dx[i+1]) - math.atan2(dy[i], dx[i]))
print(corrective_rotation)
rotation_center = [rotating_x[i], rotating_y[i]]
for k in range(i+1, len(xxx)):
rotation_target = [rotating_x[k], rotating_y[k]]
qx, qy = rotate(rotation_center, rotation_target, corrective_rotation)
rotating_x[k] = qx
rotating_y[k] = qy
By the way, plt.axes().set_aspect('equal') helps avoid the illusion that the curve changed length after rotation.
Finally, I should say that taking angles from the point values of the derivative of an interpolating spline is a very questionable decision. Finite differences at an appropriate scale are more robust.

Trouble in obtaining correct geometric transformation of cartesian coordinates

I am working in python and i have previous (x_prev,y_prev) = (1.5, 3) coordinate and current (x,y) = (2, 3.2)coordinate and angle difference between them and i want the next coordinate to be at a certain distance d with the same orientation as the current (x,y)coordinate. I have tried using the rotation and translation formula but it fails to give the proper answer. here is the code so far what i tried.
d = 0.5
angle = np.arctan2((y - y_prev), (x - x_prev))
x_ = x * np.cos(angle) - y * np.sin(angle) + (d * np.sinc(angle_/2)* np.cos(angle/2))
y_ = x * np.sin(angle) + y * np.cos(angle) + (d * np.sinc(angle_/2)* np.sin(angle/2))
the expected coordinate is approximately (x_,y_) = (2.5, 3.6) with the same orientation as the current but it results in wrong coordinate so is there anything i am missing.
Thanks in advance
I partly agree with #ImportanceOfBeingErnest that your question is a geometrical one. However, I'm adding an answer because numpy lets you avoid all that trigonometric work that you are trying to do in the first place.
What you want is to find the point (x_new,y_new) based on (x_prev,y_prev) and (x_now,y_now) such that the three points lie on the same line and the distance between (x_prev,y_prev) and (x_new,y_new) is a preset d.
You don't need trigonometry if you can work with proper two-dimensional vectors. You can normalize the vector (x_now,y_now) - (x_prev,y_prev) to get an orientation vector of the line along which you need to move from (x_prev,y_prev) in order to end up at (x_new,y_new). Numpy lets you handle this elegantly:
import numpy as np
x_prev,y_prev = (1.5, 3)
x_now,y_now = (2, 3.2)
d = 0.5
# use 2d arrays for elegant vector operations
# of course we can directly define these from coordinates if we want to
p_prev = np.array([x_prev,y_prev])
p_now = np.array([x_now,y_now])
# compute the unit direction vector for p_new - p_prev
t = p_now - p_prev
t /= np.linalg.norm(t) # use Euclidean norm by default
# p_new is simple now:
p_new = p_prev + d*t
print(p_prev)
print(p_now)
print(p_new)
The above results in (x_new,y_new)=(1.96423835,3.18569534). Your points are actually such that (x_now,y_now) is almost at 0.5 distance from (x_prev,y_prev), so the resulting vector is hardly different from the original one. But anyway, the above procedure will always give you a new point which is at the same angle from (x_prev,y_prev) as (x_now,y_now) but at the fixed distance.

Optimizing star profile (gaussian) fitting algorithm

I need to fit several thousand 2D gaussian functions to star profiles (14x14 pixel blocks) on CCD image and get centroid coordinates, FWHM over long and short axes and angle of rotation of the long axis. The problem is that my current code takes too long to execute. Several 10s of seconds on i7 processor and I need it to make much faster. Preferably as fast as possible. I tested several gaussian fitting functions and it appears that the one used in AsPyLib is the fastest http://www.aspylib.com/doc/aspylib_fitting.html
Below is the code that I'm trying to make run faster. Profiling showed that most of the time is spent inside mplfit function.So my question is if this can be accelerated? I tried cythonizing the code but it provided really minor boost. Calculating moments (10x faster) is not suitable for many images that I have due to noise issues which make estimates unreliable. And probably due to the fact that star profiles are often far from being gaussian due to aberrations.
Multiprocessing wasn't a solution either as new process creation overhead was too high for fitting just 1 star profile.
Any ideas where to look further?
import numpy as np
from scipy.optimize import leastsq
def fit_gauss_elliptical(xy, data):
"""
---------------------
Purpose
Fitting a star with a 2D elliptical gaussian PSF.
---------------------
Inputs
* xy (list) = list with the form [x,y] where x and y are the integer positions in the complete image of the first pixel (the one with x=0 and y=0) of the small subimage that is used for fitting.
* data (2D Numpy array) = small subimage, obtained from the full FITS image by slicing. It must contain a single object : the star to be fitted, placed approximately at the center.
---------------------
Output (list) = list with 8 elements, in the form [maxi, floor, height, mean_x, mean_y, fwhm_small, fwhm_large, angle]. The list elements are respectively:
- maxi is the value of the star maximum signal,
- floor is the level of the sky background (fit result),
- height is the PSF amplitude (fit result),
- mean_x and mean_y are the star centroid x and y positions, on the full image (fit results),
- fwhm_small is the smallest full width half maximum of the elliptical gaussian PSF (fit result) in pixels
- fwhm_large is the largest full width half maximum of the elliptical gaussian PSF (fit result) in pixels
- angle is the angular direction of the largest fwhm, measured clockwise starting from the vertical direction (fit result) and expressed in degrees. The direction of the smallest fwhm is obtained by adding 90 deg to angle.
---------------------
"""
#find starting values
dat=data.flatten()
maxi = data.max()
floor = np.ma.median(dat)
height = maxi - floor
if height==0.0: #if star is saturated it could be that median value is 32767 or 65535 --> height=0
floor = np.mean(dat)
height = maxi - floor
mean_x = (np.shape(data)[0]-1)/2
mean_y = (np.shape(data)[1]-1)/2
fwhm = np.sqrt(np.sum((data>floor+height/2.).flatten()))
fwhm_1 = fwhm
fwhm_2 = fwhm
sig_1 = fwhm_1 / (2.*np.sqrt(2.*np.log(2.)))
sig_2 = fwhm_2 / (2.*np.sqrt(2.*np.log(2.)))
angle = 0.
p0 = floor, height, mean_x, mean_y, sig_1, sig_2, angle
#---------------------------------------------------------------------------------
#fitting gaussian
def gauss(floor, height, mean_x, mean_y, sig_1, sig_2, angle):
A = (np.cos(angle)/sig_1)**2. + (np.sin(angle)/sig_2)**2.
B = (np.sin(angle)/sig_1)**2. + (np.cos(angle)/sig_2)**2.
C = 2.0*np.sin(angle)*np.cos(angle)*(1./(sig_1**2.)-1./(sig_2**2.))
#do not forget factor 0.5 in exp(-0.5*r**2./sig**2.)
return lambda x,y: floor + height*np.exp(-0.5*(A*((x-mean_x)**2)+B*((y-mean_y)**2)+C*(x-mean_x)*(y-mean_y)))
def err(p,data):
return np.ravel(gauss(*p)(*np.indices(data.shape))-data)
p = leastsq(err, p0, args=(data), maxfev=200)
p = p[0]
#---------------------------------------------------------------------------------
#formatting results
floor = p[0]
height = p[1]
mean_x = p[2] + xy[0]
mean_y = p[3] + xy[1]
#angle gives the direction of the p[4]=sig_1 axis, starting from x (vertical) axis, clockwise in direction of y (horizontal) axis
if np.abs(p[4])>np.abs(p[5]):
fwhm_large = np.abs(p[4]) * (2.*np.sqrt(2.*np.log(2.)))
fwhm_small = np.abs(p[5]) * (2.*np.sqrt(2.*np.log(2.)))
angle = np.arctan(np.tan(p[6]))
else: #then sig_1 is the smallest : we want angle to point to sig_y, the largest
fwhm_large = np.abs(p[5]) * (2.*np.sqrt(2.*np.log(2.)))
fwhm_small = np.abs(p[4]) * (2.*np.sqrt(2.*np.log(2.)))
angle = np.arctan(np.tan(p[6]+np.pi/2.))
output = [maxi, floor, height, mean_x, mean_y, fwhm_small, fwhm_large, angle]
return output
I have come across this method before but not implemented it:
http://www.nature.com/nmeth/journal/v9/n7/full/nmeth.2071.html
Might be worth a look as a short-cut method. There is even code to copy on his website http://physics-server.uoregon.edu/~raghu/particle_tracking.html

Categories

Resources