Find Pixels that Line Passes Over - python

I have the equation of a line in y=ax+b form, and a starting point, and I want to find all the pixels this line crosses over/into.
At the moment, I was just stepping the x value a bit, calculating y, truncating to find pixel index and adding it to a list if not already in the list, and continuing until reaching a destination point. Kind of as follows (python/pseudocode):
temp_x = start_x
prev_tested = None
pixel_list = []
while(not at destination):
temp_y = ... find y from x and line equation
pixel = (int(temp_y), int(temp_x))
if pixel is not the same as the prev_pixel:
pixel_list.append(pixel)
temp_x += some_step_value
But this just seems wildly inaccurate and inefficient (No need to tell me that in the answers, I understand this is a bad algo). The step value affects a lot. Too large and I will miss pixels (especially if there is a large slope). Too small and I am just wasting iterations. I figured that I can make my step value proportional to my slope, so that I try to minimize the number of iterations I have to do, while also not skipping over too much. But it is not perfect, still skipping over pixels that the line only barely enters the corner.
I feel like there has to be some kind of way to just absolutely determine which pixels a line is touching, but I have had no look finding anything myself. Is there some resource anyone could point me towards that could help with this?

Dx= X1 - X0
Dy= Y1 - Y0
D= Max(Abs(Dx), Abs(Dy))
for I= 0 to D
X= X0 + I * Dx / D
Y= Y0 + I * Dy / D
works in all cases (except the degenerate D=0) to join (X0, Y0) to (X1, Y1) using integer coordinates.
Technical note:
You can avoid the two divisions. One by the fact that the fraction simplifies to ±1, and the other by computing the quotient and remainder incrementally.
If you believe that this is not accurate enough, you can scale all coordinates by an arbitrary integer M, compute the points with step M and divide the coordinates by M.

Your step value should be always 1 . What to step over, however depends on your line being more on the horizontal or more on the vertical (that is "a < 1" or "a > 1". For one, step x on 1, and y will be a fraction of that, and for lines more on vertical, y will step with 1 and x will be a fraction of that.
def draw_line(a, b, start_x, destination):
result = []
x = start_x
y = a * x + b
result.append((int(x),int(y)))
while int(x) != destination[0] and int(y) != destination[1]:
if abs(a) < 1:
x += 1 if destination[0] > start_x else -1
y += (1 / a) if a!= 0 else 0
else:
y += 1 if destination[1] > y else -1
x += 1 / a
result.append((int(x), int(y)))
return result
# maybe there is some missing corner case for vertical lines.

Related

Steepest descent and finding optimal step size

I'm trying to a Steepest descent for a function with 2 variables. It works fine with known step size which = 0.3. But I want to find a way to optimize step size and create a function to find a good step size. I found something called Armijo–Goldstein condition but I didn't understand it and the formula was kind of confusing for me. So I ask your help, if you have an idea how to balance this 'cause everything that connected to the step size at my code is wrong, I think. It have to calculate step size deepening on x and y we're on, I guess.
x, y = f.optimal_range() ##getting random start
step = 0.3 ## <--- This number have to be random between 0 to 1. But my step size calculations are wrong so I can't do it.
while f.fprime_x(x) != 0: ##loop to find 0 point for derivative of a function on x
fx = -f.fprime_x(x)
x = x + (step * fx)
print(x)
if not f.delta_check(step, x, y): <--- Here's the problem. By the defenition the step have to be smaller if it doesn't pass the check, but if I make it smaller - it enters the eternal loop around the mi
step = step * 1.001
while f.fprime_y(y) != 0: ##loop to find 0 point for derivative of a function on x
fy = -f.fprime_y(y)
y = y + (step * fy)
print(x, y)
if not f.delta_check(step, x, y):
step = step * 1.001
print(f"\n\nMIN ({x}, {y})")
Here's the function of delta / step size checking:
def delta_check(delta, x, y):
ux = -fprime_x(x)
uy = -fprime_y(y)
f_del_xy = func(x + (delta * ux), y + (delta * uy))
return f_del_xy <= func(delta * ux, delta * uy) + delta
Here's a notional Armijo–Goldstein implementation. Can't test it without a data+function example, though.
# both should be less than, but usually close to 1
c = 0.8 # how much imperfection in function improvement we'll settle up with
tau = 0.8 # how much the step will be decreased at each iteration
x = np.array(f.optimal_range()) # assume everything is a vector; x is an n-dimensional coordinate
# NOTE: the part below is repeated for every X update
step = 0.3 # alpha in Armijo–Goldstein terms
gradient = np.array(f.fprime_x(x[0]), f.fprime_y(x[1]), ...)
# in the simplest case (SGD) p can point in the direction of gradient,
# but in general they don't have to be the same, e.g. because of added momentum
p = -gradient / ((gradient**2).sum() **0.5)
m = gradient.dot(p) # "happy case" improvement per unit step
t = - c * m # improvement we'll consider good enough
# func(*x) might be worth precomputing
while func(*x) - func(*(x + step*p)) < step * t: # good enough step size found
step *= tau
# update X and repeat

How do I construct a point from x,y, line and angle?

I am making a geometry interface in python (currently using tkinter) but I have stumbled upon a major problem: I need a function that is able to return a point, that is at a certain angle with a certain line segment, is a certain length apart from the vertex of the angle. We know the coordinates of the points of the line segment, and also the angle at which we want the point to be. I have attached an image below for a more graphical view of my question.
The problem: I can calculate it using trigonometry, where
x, y = vertex.getCoords()
endx = x + length * cos(radians(angle))
endy = y + length * sin(radians(angle))
p = Point(endx, endy)
The angle I input is in degrees. That calculation is true only when the line segment is parallel to the abscissa. But the sizes of the angles I get back are very strange, to say the least. I want the function to work wherever the first two points are on the tkinter canvas, whatever the angle is. I am very lost as to what I should do to fix it. What I found out: I get as output a point that when connected to the vertex, makes a line that is at the desired angle to the abscissa. So it works when the first arm(leg, shoulder) of the angle is parallel to the abscissa, then the function runs flawlessly (because of cross angles) - the Z formation. As soon as I make it not parallel, it becomes weird. This is because we are taking the y of the vertex, not where the foot of the perpendicular lands(C1 on the attached image). I am pretty good at math, so feel free to post some more technical solutions, I will understand them
EDIT: I just wanted to make a quick recap of my question: how should I construct a point that is at a certain angle from a line segment. I have already made functions that create the angle in respect to the X and Y axes, but I have no idea how i can make it in respect to the line inputted. Some code for the two functions:
def inRespectToXAxis(vertex, angle, length):
x, y = vertex.getCoords()
newx = x + length * cos(radians(angle))
newy = y + length * sin(radians(angle))
p = Point(abs(newx), abs(newy))
return p
def inRespectToYAxis(vertex, length, angle):
x, y = vertex.getCoords()
theta_rad = pi / 2 - radians(angle)
newx = x + length * cos(radians(angle))
newy = y + length * sin(radians(angle))
p = Point(newx, newy)
return p
Seems you want to add line segment angle to get proper result. You can calculate it using segment ends coordinates (x1,y1) and (x2,y2)
lineAngle = math.atan2(y2 - y1, x2 - x1)
Result is in radians, so apply it as
endx = x1 + length * cos(radians(angle) + lineAngle) etc

How do I program a spatially dependend random number distribution?

I wrote a routine that distributes circles randomly (uniformly) with an arbitrary diameter in my study area.
def no_nearby_dots(new_dot, dots_sim, min_distance):
for dot in dots_sim:
if np.sqrt((dot[0] - new_dot[0]) ** 2 + (dot[1] - new_dot[1]) ** 2) <= min_distance:
return False
return True
while realizations < simulations:
dots_sim = []
new_dot = True
dots_sim.append((np.random.uniform(xmin, xmax), np.random.uniform(ymin, ymax)))
failed_attempts = 0
while new_dot:
xp = np.random.uniform(xmin, xmax)
yp = np.random.uniform(ymin, ymax)
if no_nearby_dots((xp, yp), dots_sim, diameter):
dots_sim.append((xp, yp))
failed_attempts = 0
else:
failed_attempts += 1
if len(dots_sim) == n_sim:
new_dot = False
if failed_attempts > 2000:
new_dot = False
print('ERROR...exit loop')
break
x_sim = [dot[0] for dot in dots_sim]
y_sim = [dot[1] for dot in dots_sim]
I want to introduce a second circle around the initial ones where the possibility of distributing points reduces exponentially towards the inner border -> I want to prevent a "hard" border, the points are allowed to occur anywhere on the plane but not closer than diameter, additionally they can only occur to a certain degree between diameter and diameter2.
Are there any ideas how to do that?
Here is an idea.
Choose a random radius between diameter/2 and diameter2/2, then generate a random point in the circle formed by that radius. There are many ways to choose a radius that meets your requirements. For example, the following chooses a radius such that radii very close to diameter2/2 are much more likely to be chosen:
radius = (diameter1/2) + ((diameter2/2) - (diameter1/2)) * random.random()**(1/20)
Note that 1/20 is the 20th root of a uniform (0, 1) random number. Try changing 1/20 to a different value and see what happens.
There are other ways to choose a radius this way, and they can all be described by a probability density function (for more information, see the following answer: Generate a random point within a circle (uniformly), which shows how a linear density function leads to a uniform distribution of points in a circle).
I solved it, and this is what I did:
while realizations < simulations:
dots_sim = []
new_dot = True
dots_sim.append((np.random.uniform(x_min, x_max), np.random.uniform(y_min, y_max)))
failed_attempts = 0
while new_dot:
x = np.random.uniform(x_min, x_max)
y = np.random.uniform(y_min, y_max)
diameter_simulation = np.random.uniform(min_diameter, max_diameter)
if no_nearby_dots((x, y), dots_sim, diameter_simulation):
dots_sim.append((x, y))
failed_attempts = 0
else:
failed_attempts += 1
if len(dots_sim) == len(x_coordinate):
new_dot = False
if failed_attempts > 1000:
new_dot = False
print('ERROR... -> no more space to place QDs! -> exit loop!')
break
What I did was creating diameters for my circles also using uniformly distributed numbers in an arbitrary interval, which smoothes my cumulative distribution function. This is the solution I needed, but it might not fit the initial question very well (or the question was formulated inaccurately in the first place :p)

python decimal time to default time

in python 2.7
trying to get the correct time ( convert decimal minutes to default time HH:MM:SS )
first i got the distance from 2 different points (hexagon map) then multiply the distance by 13.3 ( the speed of unit per minutes )
import time
import math
from datetime import timedelta
def calculateDistance(x1,y1,x2,y2):
dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
return dist
dist = calculateDistance(16345,16318,16345,16314)
print dist
minutes = (13.3) * dist
print minutes
time = timedelta(minutes=minutes)
print(time)
the result is:
4.0 << the distance
53.2 << the time in minutes ( distance * 13.3 )
0:53:12 <<< the time but it's incorrect!
the result should be 0:53:20
any suggestions?
thank you
Providing an answer to what I think you are really asking, which is:
How do I calculate "distance" from point A to point B, where point A
is at the centre of concentric hexagons and the distance from A to B
is defined as the length of the apothem* of the hexagon on which B lies.
(* The apothem is is the length of the perpendicular from the centre of
the hexagon to an edge).
If this is what you mean, please update your question. There is nothing wrong in your code with the time conversion, only the distance measurement.
The answer...
It is assumed that underneath everything there is a bog-standard rectangular co-ordinate system. Your x1, y1 etc. are expressed in that system.
When the angle between the horizontal and the line AB is between 60 and 120 degrees, the hexagon that B lies on is the one with apothem length equal to the y co-ordinate of B (assuming A is at the origin). This is based on the information on the 5th post down on the link you provided in a comment.
It may be easier to see pictorially: B is on the hexagon of apothem 10, so the distance from A to B is 10. The y-coordinate of B is also 10.
When the angle between the horizontal and the line AB is some other value, the co-ordinates need to be rotated to get the angle to between 60 and 120. Then you can use (2).
In the code below, dist(x1, y1, x2, y2) should provide the distance in units.
import math
def dtor(deg):
return deg * math.pi/180
def eucl_dist(x1, y1, x2, y2):
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
def ang(x, y):
return math.atan2(y,x)
def rot(x,y,ang):
newx = x*math.cos(ang)-y*math.sin(ang)
newy = x*math.sin(ang)+y*math.cos(ang)
return (newx, newy)
def hexdist(x, y):
alpha = ang(x, y) # always top-right quadrant
if dtor(60) <= alpha < dtor(120):
dist = y
else:
if dtor(120) <= alpha < dtor(180):
newcoords = rot(x,y,dtor(-60))
elif dtor(0)<= alpha < dtor(60):
newcoords = rot(x,y,dtor(60))
elif dtor(-60)<= alpha < dtor(0):
newcoords = rot(x,y,dtor(120))
elif dtor(-120)<= alpha < dtor(-60):
newcoords = rot(x,y,dtor(180))
elif dtor(-180)<= alpha < dtor(-120):
newcoords = rot(x,y,dtor(120))
dist = hexdist(newcoords[0],newcoords[1])
return dist
def dist(x1,y1,x2,y2):
return hexdist(x2-x1, y2-y1)
Note that the results still do not match your numbers.
The first set of coordinates (16345,16318,16345,16314) produces 53.2 minutes, which is logical given that the x coordinate doesn't change. As others have explained, this is definitely 53 minutes and 12 seconds.
The other set of coordinates (in the comments, 16345,16318,16348,16301) gives 3:46:06, which is close to your estimate of 3:46:40 but not exactly the same. Could this be due to rounding of the co-ordinates?
This is definitely one of the weirdest questions I have seen here! I do wonder why the good old Euclidean metric was unsuitable.
The time is correct, since 0.2 minutes is 12 seconds.
You are using a time representation in base 10 for the seconds. When you convert it to seconds it is normal that you get 53.12 (60*0.20= 12 seconds)

Linear X Logarithmic scale

Given a line X pixels long like:
0-------|---V---|-------|-------|-------max
If 0 <= V <= max, in linear scale V position would be X/max*V pixels.
How can I calculate the pixel position for a logarithmic scale, and starting from the pixel position how can I get back the value of V?
It is not homework
I want to know the math (no "use FOO-plotlib" comments, please)
I like Python
A logarithmic scale has the effect of "zooming" the left side of the scale. Is it possible to do the same thing for the right side instead?
[UPDATE]
Thanks for the math lessons!
I ended up not using logarithms. I've simply used the average value (in a set of values) as the center of the scale. This control is used to select group boundary percentiles for a set of values that will be used to draw a choropleth chart.
If the user chooses a symmetric scale (red mark=average, green mark=center, darkness represents the number of occurrences of a value):
An asymmetric scale makes fine-grained adjustments easier:
So you've got some arbitrary value V, and you know that 0 <= V <= Vmax. You want to calculate the x-coordinate of a pixel, call it X, where your "screen" has x-coordinates from 0 to Xmax. As you say, to do this the "normal" way, you'd do
X = Xmax * V / Vmax
V = Vmax * X / Xmax
I like to think of it like I'm first normalizing the value to lie between 0 and 1 by calculating V / Vmax, and then I multiply this value by the maximum to get a value between 0 and that maximum.
To do the same logaritmically you need a different lower limit for the V value. If V is ever <= 0, you get a ValueError. So let's say 0 < Vmin <= V <= Vmax. Then you need to find out what logarithm to use, as there are infinitely many of them. Three are commonly encountered, those with base 2, e and 10, which results in x-axis that look like this:
------|------|------|------|---- ------|------|------|------|----
2^-1 2^0 2^1 2^2 == 0.5 1 2 4
------|------|------|------|---- ------|------|------|------|----
e^-1 e^0 e^1 e^2 == 0.4 1 2.7 7.4
------|------|------|------|---- ------|------|------|------|----
10^-1 10^0 10^1 10^2 == 0.1 1 10 100
So in principle, if we can get at the exponents from the expressions to the left, we can use the same principle as above to get a value between 0 and Xmax, and this is of course where log comes in. Assuming you use base b, you can use these expressions to convert back and forth:
from math import log
logmax = log(Vmax / Vmin, b)
X = Xmax * log(V / Vmin, b) / logmax
V = Vmin * b ** (logmax * X / Xmax)
It's almost the same way of thinking, except you need to first make sure that log(somevalue, b) will give you a non-negative value. You do this by dividing by Vmin inside the log function. Now you may divide by the maximum value the expression can yield, which is of course log(Vmax / Vmin, b), and you will get a value between 0 and 1, same as before.
The other way we need to first normalize (X / Xmax), then scale up again (* logmax) to the maximum expected by the inverse funciton. The inverse is to raise b to some value, by the way. Now if X is 0, b ** (logmax * X / Xmax) will equal 1, so to get the correct lower limit we multiply by Vmin. Or to put it another way, since the first thing we did going the other way was to divide by Vmin, we need to multiply with Vmin as the last thing we do now.
To "zoom" the "right side" of the equation, all you need to do is switch the equations, so you exponentiate going from V to X and take the logarithm going the other way. In principle, that is. Because you've also got to do something with the fact that X can be 0:
logmax = log(Xmax + 1, b)
X = b ** (logmax * (V - Vmin) / (Vmax - Vmin)) - 1
V = (Vmax - Vmin) * log(X + 1, b) / logmax + Vmin
Linear Logarithmic
Forward pos = V * X/max pos = log(V) * X/log(max)
Reverse V = pos * max/X V = B^( pos * log(max)/X )
(B is logarithm's base)
Obviously you must ensure that V >= 1 (V=1 will correspond to pos=0, V=0..1 corresponds to -inf..0, and for V<0 logarithm is not defined).
This can be easily extended for other functions. My measure of space is given in chars instead of pixels (thats why max==chars(or pixels)).
Only for positive values.
import math
def scale(myval, mode='lin'):
steps = 7
chars = max = 10 * steps
if mode=='log':
val = 10 * math.log10(myval)
else:
val = myval
coord = []
count = 0
not_yet = True
for i in range(steps):
for j in range(10):
count += 1
if val <= count and not_yet:
coord.append('V')
not_yet = False
pos = count
elif j==9:
coord.append('|')
else:
coord.append('*')
graph = ''.join(coord)
text = 'graph %s\n\n%s\nvalue = %5.1f rel.pos. = %5.2f\n'
print text % (mode, graph, myval, chars * pos/max)
scale(50, 'lin')
scale(50, 'log')
Hope the above is not considered FOO-plotlib. But damn it ! this is SO ! :-)

Categories