I have an image of stars, like this one:
Now, I want to get the 100 brightest stars, and reduce these to four data points each: 1. X-coordinate, 2. Y-coordinate, 3. brightness, 4. radius. That's it. Basically, I want to reduce these 800x480px = 384000 data points into 100*4 data points while still keeping most information (star position, star brightness and star radius).
My current approach, to find the brightest stars:
np.where(star_image_array**2 > threshold, 1, 0)
Then I run a gaussian filter on the result, and do another selection for the highest values. But this still doesn't solve the problem of how to select distinct star coordinates (not to mention the brightness and radius).
Could someone point me to the right direction to solve this challenge, or provide me with some resources? Thanks!
You could use contours to find the stars in your thresholded image, something like this:
ret, mask = cv2.threshold(img, 100, 255, cv2.CV_8U)
contours, hierarchy = cv2.findContours(mask, 1, 2)
stars = []
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 2:
continue
x, y, w, h = cv2.boundingRect(cnt)
# 1. X-coordinate
x_coord = x + w/2.0
# 2. Y-coordinate
y_coord = y + h/2.0
# 3. brightness
star_mask = np.zeros(img.shape,np.uint8)
cv2.drawContours(star_mask, [cnt], 0, 255, -1)
mean_val = cv2.mean(img, mask=star_mask)[0]
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(img, mask=star_mask)
# 4. radius
radius = np.sqrt(area/(2*np.pi))
stars.append({'x': x_coord,
'y': y_coord,
'mean_brightness': mean_val,
'max_brightness': max_val,
'radius': radius})
There is a colab with the example code here.
Related
I have been trying to work out how to calculate Poincaré sections for a system of non-linear ODEs, using a paper on the exact system as reference, and have been wrestling with numpy to try and make it run better. This is intended to run within a bounded domain.
Currently, I have the following code
import numpy as np
from scipy.integrate import odeint
X = 0
Y = 1
Z = 2
def generate_poincare_map(function, initial, plane, iterations, delta):
intersections = []
p_i = odeint(function, initial.flatten(), [0, delta])[-1]
for i in range(1, iterations):
p_f = odeint(function, p_i, [i * delta, (i+1) * delta])[-1]
if (p_f[Z] > plane) and (p_i[Z] < plane):
intersections.append(p_i[:2])
if (p_f[Z] > plane) and (p_i[Z] < plane):
intersections.append(p_i[:2])
p_i = p_f
return np.stack(intersections)
This is pretty wasteful due to the integration solely between successive time steps, and seems to produce incorrect results. The original reference includes sections along the lines of
whereas mine tend to result in something along the lines of
Do you have any advice on how to proceed to make this more correct, and perhaps a little faster?
To get a Pointcaré map of the ABC flow
def ABC_ode(u,t):
A, B, C = 0.75, 1, 1 # matlab parameters
x, y, z = u
return np.array([
A*np.sin(z)+C*np.cos(y),
B*np.sin(x)+A*np.cos(z),
C*np.sin(y)+B*np.cos(x)
])
def mysolver(u0, tspan): return odeint(ABC_ode, u0, tspan, atol=1e-10, rtol=1e-11)
you have first to understand that the dynamical system is really about the points (cos(x),sin(x)) etc. on the unit circle. So values different by multiples of 2*pi represent the same point. In the computation of the section one has to reflect this, either by computing it on the Cartesian product of the 3 circles. Let's stay with the second variant, and chose [-pi,pi] as the fundamental period to have the zero location well in the center. Keep in mind that jumps larger pi are from the angle reduction, not from a real crossing of that interval.
def find_crosssections(x0,y0):
u0 = [x0,y0,0]
px = []
py = []
u = mysolver(u0, np.arange(0, 4000, 0.5)); u0 = u[-1]
u = np.mod(u+pi,2*pi)-pi
x,y,z = u.T
for k in range(len(z)-1):
if z[k]<=0 and z[k+1]>=0 and z[k+1]-z[k]<pi:
# find a more exact intersection location by linear interpolation
s = -z[k]/(z[k+1]-z[k]) # 0 = z[k] + s*(z[k+1]-z[k])
rx, ry = (1-s)*x[k]+s*x[k+1], (1-s)*y[k]+s*y[k+1]
px.append(rx);
py.append(ry);
return px,py
To get a full picture of the Poincare cross-section and avoid duplicate work, use a grid of squares and mark if one of the intersections already fell in it. Only start new iterations from the centers of free squares.
N=20
grid = np.zeros([N,N], dtype=int)
for i in range(N):
for j in range(N):
if grid[i,j]>0: continue;
x0, y0 = (2*i+1)*pi/N-pi, (2*j+1)*pi/N-pi
px, py = find_crosssections(x0,y0)
for rx,ry in zip(px,py):
m, n = int((rx+pi)*N/(2*pi)), int((ry+pi)*N/(2*pi))
grid[m,n]=1
plt.plot(px, py, '.', ms=2)
You can now play with the density of the grid and the length of the integration interval to get the plot a little more filled out, but all characteristic features are already here. But I'd recommend re-programming this in a compiled language, as the computation will take some time.
I had attached also the schematic to depict my question.
I need to rotate the vector V with the base point P by an angle and find the new vector V'.
The rotation axis is say for is about a local y axis at point P (which is parallel to global Y axis)
Subsequently, I need to rotate the initial vector V about x axis which is parallel to global Y axis.
The main reason for the rotation is to find the new vector V' at point P. Both the rotations are independent and each of the rotation provides a new V'. I'm programming this in VB.net and output is a double() of new vector V'.
Just apply the two rotations independently (see Wikipedia). The base point does not play any role in this because it is just a constant offset that never changes. If I got your description right, you want the following:
//rotation about y-axis
iAfterRot1 = cos(phi1) * i + sin(phi1) * k
jAfterRot1 = j
kAfterRot1 = -sin(phi1) * i + cos(phi) * k
//rotation about x-axis
iAfterRot2 = iAfterRot1
jAfterRot2 = cos(phi2) * jAfterRot1 - sin(phi2) * kAfterRot1
kAfterRot2 = sin(phi2) * jAfterRot1 + cos(phi2) * kAfterRot1
I'm a little confused about the difference between the DLT algorithm described here and the homography estimation described here. In both of these techniques, we are trying to solve for the entries of a 3x3 matrix by using at least 4 point correspondences. In both methods, we set up a system where we have a "measurement" matrix and we use SVD to solve for the vector of elements that make up H. I was wondering why there are two techniques that seem to do the same thing, and why one might be used over the other.
You have left-right image correspondences {p_i} <-> {p'_i}, where p_i = (x_i, y_i), etc.
Normalizing them to the unit square means computing two shifts m=(mx, my), m'=(mx', my'), and two scales s=(sx,sy), s'=(sx',sy') such that q_i = (p_i - m) / s and q_i' = (p_i' - m') / s', and both the {q_i} and the {q'_i} transformed image points are centered at (0,0) and approximately contained within a square of unit side length. A little math shows that a good choice for the m terms are the averages of the x,y coordinates in each set of image points, and for the s terms you use the standard deviations (or twice the standard deviation) times 1/sqrt(2).
You can express this normalizing transformation in matrix form: q = T p,
where T = [[1/sx, 0, -mx/sx], [0, 1/sy, -my/sy], [0, 0, 1]], and likewise q' = T' p'.
You then compute the homography K between the {q_i} and {q'_i} points: q_i' = K q_i.
Finally, you denormalize K into the origina (un-normalized) coordinates thus: H = inv(T') K T, and H is the desired homography that maps {p} into {p'}.
Given a convex 3d polygon (convex hull) How can I determine the correct direction for normal surface/vertex vectors? As the polygon is convex, by correct I mean outward facing (away from the centroid).
def surface_normal(centroid, p1, p2, p3):
a = p2-p1
b = p3-p1
n = np.cross(a,b)
if **test including centroid?** :
return n
else:
return -n # change direction
I actually need the normal vertex vectors as I am exporting as a .obj file, but I am assuming that I would need to calculate the surface vectors before hand and combine them.
This solution should work under the assumption of a convex hull in 3d. You calculate the normal as shown in the question. You can normalize the normal vector with
n /= np.linalg.norm(n) # which should be sqrt(n[0]**2 + n[1]**2 + n[2]**2)
You can then calculate the center point of your input triangle:
pmid = (p1 + p2 + p3) / 3
After that you calculate the distance of the triangle-center to your surface centroid. This is
dist_centroid = np.linalg.norm(pmid - centroid)
The you can calculate the distance of your triangle_center + your normal with the length of the distance to the centroid.
dist_with_normal = np.linalg.norm(pmid + n * dist_centroid - centroid)
If this distance is larger than dist_centroid, then your normal is facing outwards. If it is smaller, it is pointing inwards. If you have a perfect sphere and point towards the centroid, it should almost be zero. This may not be the case for your general surface, but the convexity of the surface should make sure, that this is enough to check for its direction.
if(dist_centroid < dist_with_normal):
n *= -1
Another, nicer option is to use a scalar product.
pmid = (p1 + p2 + p3) / 3
if(np.dot(pmid - centroid, n) < 0):
n *= -1
This checks if your normal and the vector from the mid of your triangle to the centroid have the same direction. If that is not so, change the direction.
I have done an FFT on an audio signal and want to plot the results on a logarithmic scale like this
http://abletonuniverse.altervista.org/wp-content/uploads/2013/04/Spectrum.jpg
but for some reason I can't figure out how to scale the data to fit that logarithmic curve (starting from 0, ending at the nyquist frequency i.e. Samplingrate / block size).
Currently this is what I have
CGContextMoveToPoint(context, 0, rect.size.height);
// Skip the DC Offset (index 0)
for (int i = 1; i < _bins; i++) {
// Get frequency from bin
float binFreq = i * 44100.0 / (_bins * 2);
// Map to rect coordinate space
float x = log10f(binFreq) * rect.size.width / log10f(44100.0 / 2);
float y = _freqArray[i] * rect.size.height / -130.0;
// Draw line
CGContextAddLineToPoint(context, x, y);
}
My data comes in as 512 points of dB between 0 and ~-130 in _freqArray. Ignore the y component of the plotted point.
My data is not scaled correctly as my first bin (~43 hz) is log10(43)= 1.633 and the end of the graph is log10(22050) = 4.3, so the space between my first bin is taking up over a third of the window. It is important that I don't just put my first bin at the far left of the window the far left must be 0.
Does anybody know a correct way to scale the data into neat logarithmic bins such as in the picture? They have three neat bins representing orders of magnitude of 10, and it conveniently ends right at 22050.
For reference, this is what I have now, you can see the long straight line at the left of the spectrum. The first kink in the line is 43 hz i.e. My first bin. I am yet to put a grid up, but that will come when I figure out the scaling.
A 0Hz frequency cannot be shown on log-scale frequency axis. The left side shown on the graph you referenced corresponds to 10Hz. You can map the frequency coordinate such that this selected minimum frequency (e.g. minFrequencyToDisplay=10Hz) is drawn at a value of x=0, and the last bin drawn at a value of x=rect.size.width-1, like so:
// Map to rect coordinate space
float logMinFreq = log10f(minFrequencyToDisplay);
float x = (log10f(binFreq)-logMinFreq ) * (rect.size.width-1) /
(log10f(44100.0 / 2)-logMinFreq );
Gridlines at 100Hz, 1kHz and 10kHz can be obtained with the same formula.