Rotating a 2d sub-array using numpy without aliasing effects - numpy

I would like to rotate only the positive value pixels in my 2d array some degree about the center point. The data represents aerosol concentrations from a plume dispersion model, and the chimney position is the origin of rotation.
I would like to rotate this dispersion pattern given the wind direction.
The concentrations are first calculated for a wind direction along the x-axis and then translated to their rotated position using a 2d linear rotation about the center point of my array (the chimney position) for all points whose concentration is > 0.
The input X,Y to the rotation formula are pixel indexes.
My problem is that the output is aliased since integers become floats. In order to obtain integers, I rounded up or down the output. However, this creates null cells which become increasingly numerous as the angle increases.
Can anyone help me find a solution to my problem? I would like to fix this problem if possible using numpy, or a minimum of packages...
The part of my script that deals with computing the concentrations and rotating the pixel by 50°N is the following. Thank you for your help.
def linear2D_rotation(xcoord,ycoord,azimuth_degrees):
radians = (90 - azimuth_degrees) * (np.pi / 180) # in radians
xcoord_rotated = (xcoord * np.cos(radians)) - (ycoord * np.sin(radians))
ycoord_rotated = (xcoord * np.sin(radians)) + (ycoord * np.cos(radians))
return xcoord_rotated,ycoord_rotated
u_orient = 50 # wind orientation in degres from North
kernel = np.zeros((NpixelY, NpixelX)) # initialize matrix
Yc = int((NpixelY - 1) / 2) # position of central pixel
Xc = int((NpixelX - 1) / 2) # position of central pixel
nk = 0
for Y in list(range(0,NpixelX)):
for X in list(range(0,NpixelY)):
# compute concentrations only in positive x-direction
if (X-Xc)>0:
# nnumber of pixels to origin point (chimney)
dx = ((X-Xc)+1)
dy = ((Y-Yc)+1)
# distance of point to origin (chimney)
DX = dx*pixel_size_X
DY = dy*pixel_size_Y
# compute diffusivity coefficients
Sy, Sz = calcul_diffusivity_coeff(DX, stability_class)
# concentration at ground level below the centerline of the plume
C = (Q / (2 * np.pi * u * Sy * Sz)) * \
np.exp(-(DY / (2 * Sy)) ** 2) * \
(np.exp(-((Z - H) / (2 * Sz)) ** 2) + np.exp(-((Z + H) / (2 * Sz)) ** 2)) # at point away from center line
C = C * 1e9 # convert MBq to Bq
# rotate only if concentration value at pixel is positive
if C > 1e-12:
X_rot, Y_rot = linear2D_rotation(xcoord=dx, ycoord=dy,azimuth_degrees=u_orient)
X2 = int(round(Xc+X_rot))
Y2 = int(round(Yc-Y_rot)) # Y increases downwards
# pixels that fall out of bounds -> ignore
if (X2 > (NpixelX - 1)) or (X2 < 0) or (Y2 > (NpixelY - 1)):
continue
else:
# replace new pixel position in kernel array
kernel[Y2, X2] = C
The original array to be rotated
The rotated array by 40°N showing the data loss

Your problem description is not 100% clear, but here are a few recommendations:
1.) Don't reinvent the wheel. There are standard solutions for things like rotating pixels. Use them! In this case
scipy.ndimage.affine_transform for performing the rotation
a homogeneous coordinate matrix for specifying the rotation
nearest neighbor interpolation (parameter order=0 in code below).
2.) Don't loop where not necessary. The speed you gain by not processing non-positive pixels is nothing against the speed you lose by looping. Compiled functions can ferry around a lot of redundant zeros before hand-written python code catches up with them.
3.) Don't expect a solution that maps pixels one-to-one because it is a fact that there will be points that are no ones nearest neighbor and points that are nearest neighbor to multiple other points. With that in mind, you may want to consider a higher order, smoother interpolation.
Comparing your solution to the standard tools solution we find that the latter
gives a comparable result much faster and without those hole artifacts.
Code (without plotting). Please note that I had to transpose and flipud to align the results :
import numpy as np
from scipy import ndimage as sim
from scipy import stats
def mock_data(n, Theta=50, put_neg=True):
y, x = np.ogrid[-20:20:1j*n, -9:3:1j*n, ]
raster = stats.norm.pdf(y)*stats.norm.pdf(x)
if put_neg:
y, x = np.ogrid[-5:5:1j*n, -3:9:1j*n, ]
raster -= stats.norm.pdf(y)*stats.norm.pdf(x)
raster -= (stats.norm.pdf(y)*stats.norm.pdf(x)).T
return {'C': raster * 1e-9, 'Theta': Theta}
def rotmat(Theta, offset=None):
theta = np.radians(Theta)
c, s = np.cos(theta), np.sin(theta)
if offset is None:
return np.array([[c, -s] [s, c]])
R = np.array([[c, -s, 0], [s, c,0], [0,0,1]])
to, fro = np.identity(3), np.identity(3)
offset = np.asanyarray(offset)
to[:2, 2] = offset
fro[:2, 2] = -offset
return to # R # fro
def f_pp(C, Theta):
m, n = C.shape
clipped = np.maximum(0, 1e9 * data['C'])
clipped[:, :n//2] = 0
M = rotmat(Theta, ((m-1)/2, (n-1)/2))
return sim.affine_transform(clipped, M, order = 0)
def linear2D_rotation(xcoord,ycoord,azimuth_degrees):
radians = (90 - azimuth_degrees) * (np.pi / 180) # in radians
xcoord_rotated = (xcoord * np.cos(radians)) - (ycoord * np.sin(radians))
ycoord_rotated = (xcoord * np.sin(radians)) + (ycoord * np.cos(radians))
return xcoord_rotated,ycoord_rotated
def f_OP(C, Theta):
kernel = np.zeros_like(C)
m, n = C.shape
for Y in range(m):
for X in range(n):
if X > n//2:
c = C[Y, X] * 1e9
if c > 1e-12:
dx = X - n//2 + 1
dy = Y - m//2 + 1
X_rot, Y_rot = linear2D_rotation(xcoord=dx, ycoord=dy,azimuth_degrees=Theta)
X2 = int(round(n//2+X_rot))
Y2 = int(round(m//2-Y_rot)) # Y increases downwards
# pixels that fall out of bounds -> ignore
if (X2 > (n - 1)) or (X2 < 0) or (Y2 > (m - 1)):
continue
else:
# replace new pixel position in kernel array
kernel[Y2, X2] = c
return kernel
n = 100
data = mock_data(n, 70)

Related

docplex does not support square root np.sqrt(x), please suggest an alternative solution

I have implemented docplex model with python, everything goes well: problem formulation, variables, constraints and objective function.
The problem raised when I tried to compute Euclidian distance Between two centers of circles, simply numpy can do it np.sqrt((x2-x1)**2 + (y2-y1)**2).
please suggest me how to resolve this issue, I could not find quadratic equation for Euclidian distance without using square roots.
Working code attached, were you can see the issue at constraint 1.
from docplex.mp.model import Model
import numpy as np
def packing_cplex(CDA, R, H, items):
# continuous variables x,y range from -R to +R
x = [ CDA.continuous_var(name="x{}:".format(i), lb=-R, ub=R)
for i in range(len(items))]
y = [ CDA.continuous_var(name="y{}:".format(i), lb=-R, ub=R)
for i in range(len(items))]
# integer variable z range from 0 to H
z = [ CDA.integer_var(name="z{}:".format(i), lb=0, ub=H)
for i in range(len(items))]
# indicator denotes whether an item is packed into the container
# i=1, .., n, values 1/0
d = [ CDA.binary_var(name="d{}:".format(i))
for i in range(len(items))]
# sqrt root issue--> np.sqrt((x[i]**2 + y[i]**2))
# 1.constraint packed items and container radius
CDA.add_quadratic_constraints(R >= (items[i][0] + (x[i]**2 + y[i]**2)**1 )
for i in range(len(items)))
# 2.constraint packed items and container height
for i in range(len(items)):
CDA.add( CDA.if_then( d[i]==1, H >= ( items[i][1] + z[i] ) ) )
# objective maximise max∑_(i=0 .. n-1 (π * ri^2 * hi * di)
CDA.set_objective("max", np.sum([d[i] * np.pi * items[i][0]**2 * items[i][1]
for i in range(len(items))]))
# radius and height of cylinder container
R, H = 3, 2
volume = np.pi * R**2 * H
# pack, [(ri,hi), ... ([rn,hn]) where ri/hi is radius/height of item
items = [(1,2),(1,2),(1,2),(1,2),(1,2),(1,2),(1,2)]
CDA = Model(name='CDA')
packing_cplex(CDA, R, H, items)
CDA.print_information()
solution = CDA.solve()
utilization = solution._objective / volume
print('Utilization (%) ', utilization)
I expect alternative solution for square roots
If you have non linear functions you can use CPOptimizer within cplex.
For instance, https://github.com/AlexFleischerParis/zoodocplex/blob/master/zoononlinear.py
Or with square root:
from docplex.cp.model import CpoModel
mdl = CpoModel(name='buses')
nbbus40 = mdl.integer_var(0,1000,name='nbBus40')
nbbus30 = mdl.integer_var(0,1000,name='nbBus30')
mdl.add(nbbus40*40 + nbbus30*30 >= 300)
#non linear objective
mdl.minimize(mdl.power(nbbus40,0.5)*500 + mdl.power(nbbus30,0.5)*400)
msol=mdl.solve()
print(msol[nbbus40]," buses 40 seats")
print(msol[nbbus30]," buses 30 seats")
And to handle your decimal variables see
https://github.com/AlexFleischerParis/zoodocplex/blob/master/zoodecimalcpo.py
from docplex.cp.model import CpoModel
mdl = CpoModel(name='buses')
#now suppose we can book a % of buses not only complete buses
scale=100
scalenbbus40 = mdl.integer_var(0,1000,name='scalenbBus40')
scalenbbus30 = mdl.integer_var(0,1000,name='scalenbBus30')
nbbus40= scalenbbus40 / scale
nbbus30= scalenbbus30 / scale
mdl.add(nbbus40*40 + nbbus30*30 >= 310)
mdl.minimize(nbbus40*500 + nbbus30*400)
msol=mdl.solve()
print(msol[scalenbbus40]/scale," buses 40 seats")
print(msol[scalenbbus30]/scale," buses 30 seats")

Non Linear MPC optimization of a 2 dimensional drone

I am trying to simulate a drone on a 2-dimensional lunar surface. The drone can apply thrust the z-axis of the body, and the drone can change the angle of its body from -90 degrees to +90 degrees.
The first planned acceleration in the y direction that the MPC function gives is a negative value that exceeds the the lunar accel_g, which I set to be 1.635 m/s^2; thus, the drone cancels out the initial velocity really quickly. This should not happen since I set the constraints of body angle in such that the thrust will never be able to reduce the vertical velocity: vertical velocity of the drone should be reduced only by the lunar gravity. I can not find what is wrong with the code.
** is there a way I can apply rotation to the marker of the plot? I want to change the cross marker so that it can represent the changes in attitude. **
function run_mpc(initial_position, initial_velocity, initial_angle)
model = Model(Ipopt.Optimizer)
Δt = 0.1
num_time_steps = 20 # Change this -> Affects Optimization
max_acceleration_Thr = 3 # Max Thrust / Mass
max_pitch_angle = 90
accel_g = 1.635 # 1/6 of Earth G
des_pos = [-1,0]
#variables model begin
position[1:2, 1:num_time_steps]
velocity[1:2, 1:num_time_steps]
acceleration[1:2, 1:num_time_steps]
-max_pitch_angle <= angle[1:num_time_steps] <= max_pitch_angle
0 <= accel_Thr[1:num_time_steps] <= max_acceleration_Thr
end
# Dynamics constraints
#NLconstraint(model, [i=2:num_time_steps, j=[1]], acceleration[j, i] == accel_Thr[i-1]*sind(angle[i-1]))
#NLconstraint(model, [i=2:num_time_steps, j=[2]], acceleration[j, i] == (accel_Thr[i-1]*cosd(angle[i-1]))-accel_g)
#NLconstraint(model, [i=2:num_time_steps, j=1:2],
velocity[j, i] == velocity[j, i - 1] + (acceleration[j, i - 1]) * Δt)
#NLconstraint(model, [i=2:num_time_steps, j=1:2],
position[j, i] == position[j, i - 1] + velocity[j, i - 1] * Δt)
# Cost function: minimize final position and final velocity
# For Moving to [-2,0] with min. vertical velocity,
# sum(([-2,0]-position[:, end]).^2)+ sum(velocity[[2], end].^2)
#NLobjective(model, Min,
100 * sum((des_pos[i]-position[i, num_time_steps])^2 for i in 1:2)+ sum(velocity[i, num_time_steps]^2 for i in 1:2))
# Initial conditions:
#NLconstraint(model, [i=1:2], position[i, 1] == initial_position[i])
#NLconstraint(model, [i=1:2], velocity[i, 1] == initial_velocity[i])
#NLconstraint(model, angle[1] == initial_angle)
optimize!(model)
return value.(position), value.(velocity), value.(acceleration), value.(angle[2:end])
end;
begin
# The robot's starting position and velocity
q = [1.0, 0.0]
v = [-2.0, 2.0]
ang = 45
Δt = 0.1
# Recording Position, Acceleration, Attitude, Planned Positions
qs_x = []
qs_y = []
as_x = []
as_y = []
angs = []
q_plans = []
u_plans = []
anim = #animate for i in 1:90 # This determies the number of MPC to be run
# Plot the current position & Attitude
plot(label = "Drone",[q[1]], [q[2]], marker=(:rect, 10), xlim=(-2, 2), ylim=(-2, 2))
plot!(label = "Body Axis",[q[1]], [q[2]], marker=(:cross, 18, :grey))
push!(qs_x,q[1])
push!(qs_y,q[2])
# Run the MPC control optimization
q_plan, v_plan, u_plan, ang_plan = run_mpc(q, v, ang)
# Draw the planned future states from the MPC optimization
plot!(label = "Opt. Path", q_plan[1, :], q_plan[2, :], linewidth=5, arrow=true, c=:orange)
# Draw the planned acceleration
plot!(label = "Opt. Accel",u_plan[1, 1:2], u_plan[2, 1:2], linewidth=3, arrow=true, c=:red)
# Save Acceleration & Angle Data to csv
u = u_plan[:, 1]
push!(as_x, u[1])
push!(as_y, u[2])
push!(angs, ang)
push!(u_plans, u_plan)
# Apply the planned acceleration&Attitude and simulate one step in time
global ang = ang_plan[1]
global v += u * Δt
global q += v * Δt
end
gif(anim, "~/Downloads/NLmpc_angle.gif", fps=60)
end

Drawing segments (tangents) of fixed lengths preserving the aspect angles with matplotlib

Context: I'm trying to display the gradients as fixed-length lines on a plot of gradient noise. Each "gradient" can be seen as a tangent on a given point. The issue is, even if I make sure the lines have the same length, the aspect ratio stretches them:
The complete code to generate this:
from math import sqrt, floor, modf, sin
import matplotlib.pyplot as plt
mix = lambda a, b, x: a*(1-x) + b*x
interpolant = lambda t: ((6*t - 15)*t + 10)*t*t*t
rng01 = lambda x: modf(sin(x) * 43758.5453123)[0]
def _gradient_noise(t):
i = floor(t)
f = t - i
s0 = rng01(i) * 2 - 1
s1 = rng01(i + 1) * 2 - 1
v0 = s0 * f;
v1 = s1 * (f - 1);
return mix(v0, v1, interpolant(f))
def _plot_noise(n, interp_npoints=100):
xdata = [i/interp_npoints for i in range(n * interp_npoints)]
gnoise = [_gradient_noise(x) for x in xdata]
plt.plot(xdata, gnoise, label='gradient noise')
plt.xlabel('t')
plt.ylabel('amplitude')
plt.grid(linestyle=':')
plt.legend()
for i in range(n + 1):
a = rng01(i) * 2 - 1 # gradient slope
norm = sqrt(1 + a**2)
norm *= 4 # 1/4 length
vnx, vny = 1/norm, a/norm
x = (i-vnx/2, i+vnx/2)
y = (-vny/2, vny/2)
plt.plot(x, y, 'r-')
plt.show()
if __name__ == '__main__':
_plot_noise(15)
The red-lines drawing is located in the for-loop.
hypot(x[1]-x[0], y[1]-y[0]) gives me a constant .25 for every vector, which corresponds to my target length (¼). Which means my segments are actually in the correct length for the given aspect. This can also be "verified" with .set_aspect(1):
I've tried several things, such as translating the coordinates into display coordinates (plt.gca().transData.transform(...)), scale them, then back again (plt.gca().transData.inverted().transform(...)), without success (as if the aspect was applied on top of the display coordinates). Doing that would probably also actually change the angles as well anyway.
So to sum up: I'm looking for a way to display lines with a fixed length (expressed in the x data coordinates system), and oriented (rotated) in the xy data coordinates system.
Welcome to SO. What a well asked first question. It made me question my sanity for a hot second once I reproduced the plot and the math checked out...
However, you identified the core problem yourself: the issue is that in your code the length of the gradient lines is determined in data coordinates, when it should be dependent on the aspect ratio of the plot.
So, if you want the gradient lines to be of uniform length in display space then you need to rescale the either the dx or the dy component by the aspect ratio of the plot (or its inverse, respectively) when computing then norm:
import matplotlib.pyplot as plt
from math import sqrt, floor
mix = lambda a, b, x: a*(1-x) + b*x
interpolant = lambda t: ((6*t - 15)*t + 10)*t*t*t
rng01 = lambda x: ((1103515245*x + 12345) % 2**32) / 2**32
def _gradient_noise(t):
i = floor(t)
f = t - i
s0 = rng01(i) * 2 - 1
s1 = rng01(i + 1) * 2 - 1
v0 = s0 * f;
v1 = s1 * (f - 1);
return mix(v0, v1, interpolant(f))
def _plot_noise(n, interp_npoints=100):
xdata = [i/interp_npoints for i in range(n * interp_npoints)]
gnoise = [_gradient_noise(x) for x in xdata]
fig, ax = plt.subplots()
ax.plot(xdata, gnoise, label='gradient noise')
ax.set_xlabel('t')
ax.set_ylabel('amplitude')
ax.grid(linestyle=':')
ax.legend(loc=1)
x0, x1, y0, y1 = ax.axis()
aspect = (y1 - y0) / (x1 - x0)
for i in range(n + 1):
dy = rng01(i) * 2 - 1 # gradient slope
dx = 1
norm = sqrt(dx**2 + (dy / aspect)**2)
# norm *= 4 # 1/4 length
vnx, vny = dx/norm, dy/norm
x = (i-vnx/2, i+vnx/2)
y = (-vny/2, vny/2)
ax.plot(x, y, 'r-')
plt.show()
if __name__ == '__main__':
_plot_noise(15)
Final code with proper aspect ratio and resize event handled:
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
from math import hypot, floor, modf, sin
mix = lambda a, b, x: a*(1-x) + b*x
interpolant = lambda t: ((6*t - 15)*t + 10)*t*t*t
rng01 = lambda x: modf(sin(x) * 43758.5453123)[0]
def _gradient_noise(t):
i = floor(t)
f = t - i
s0 = rng01(i) * 2 - 1
s1 = rng01(i + 1) * 2 - 1
v0 = s0 * f;
v1 = s1 * (f - 1);
return mix(v0, v1, interpolant(f))
def _get_ar(ax):
fs = ax.figure.get_size_inches()
pos = ax.get_position(original=False)
return 1 / (ax.get_data_ratio() * (fs[0] * pos.width) / (fs[1] * pos.height))
def _get_line_coords(aspect, i):
dx, dy = 1, rng01(i) * 2 - 1 # gradient slope
norm = hypot(dx, dy * aspect)
vnx, vny = dx/norm, dy/norm
x = (i-vnx/2, i+vnx/2)
y = (-vny/2, vny/2)
return x, y
def _plot_noise(n, interp_npoints=100):
xdata = [i/interp_npoints for i in range(n * interp_npoints)]
gnoise = [_gradient_noise(x) for x in xdata]
fig, ax = plt.subplots()
ax.plot(xdata, gnoise, label='gradient noise')
ax.set_xlabel('t')
ax.set_ylabel('amplitude')
ax.grid(linestyle=':')
ax.legend(loc=1)
xlim = ax.get_xlim()
ylim = ax.get_ylim()
aspect = _get_ar(ax)
resize_objects = []
for i in range(n + 1):
lx, ly = _get_line_coords(aspect, i)
line = ax.plot(lx, ly, 'r-')[0]
ellipse = Ellipse(xy=(i, 0), width=1, height=1/aspect, fill=False, linestyle=':')
ax.add_patch(ellipse)
resize_objects.append((line, ellipse))
def _onresize(event):
ar = _get_ar(ax)
for i, (line, ellipse) in enumerate(resize_objects):
ellipse.set_height(1 / ar)
lx, ly = _get_line_coords(ar, i)
line.set_xdata(lx)
line.set_ydata(ly)
ax.figure.canvas.mpl_connect('resize_event', _onresize)
ax.set_xlim(xlim)
ax.set_ylim(ylim)
plt.show()
if __name__ == '__main__':
_plot_noise(10)
Some notes:
the same question was asked on matplotlib discourse, where jklymak provided the correct answer for the ratio computation: https://discourse.matplotlib.org/t/drawing-segments-tangents-of-fixed-lengths-preserving-the-aspect-angles-with-matplotlib/21844/14
the ax.get_{x,y}lim() → ax.set_{x,y}lim() roundtrip seems necessary because the aspect is computed based on the initial axis, which changes when plotting the lines/ellipses
the resize events is not necessary in case of export

Find 7 vertices of a box using openCV

I don't know if this question have been repeating in here. If yes then i'm sorry..
I have a box that positioned to see H,W,L view. I understand steps to get vertices however most of the examples in the net only describes how to get 4 vertices from 2D plane. So my question is, how if we want to get 7 vertices (like the pic above) and handle it in numpy? How to differentiate between upper points and lower points?
I will be using Python to determine this.
Here's my attempt to get the 8 corners of the 3d rectangle. I masked on the saturation channel of the HSV color space since that separates out white.
I used findContours to get the contour of the box and then used approxPolyDP to get a six-point approximation (the six visible corners).
From there I approximated the two "hidden" corners via a parallelogram approximation. For each point I looked two points behind and created a fourth point that would make a parallelogram with that side. I then took the centroid of these parallelogram points to guess the corner. I hoped that taking the centroid of the points would help even out the error between the parallelogram assumption and the perspective warping, but it did a poor job.
If you need a better approximation there are probably ways to estimate the perspective warping to get the corners.
import cv2
import numpy as np
import random
def tup(point):
return (int(point[0]), int(point[1]));
# load image
img = cv2.imread("box.jpg");
# reduce size to fit on screen
scale = 0.25;
h,w = img.shape[:2];
h = int(scale*h);
w = int(scale*w);
img = cv2.resize(img, (w,h));
copy = np.copy(img);
# convert to hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV);
h,s,v = cv2.split(hsv);
# make mask
mask = cv2.inRange(s, 30, 255);
# dilate and erode to get rid of small holes
kernel = np.ones((5,5), np.uint8);
mask = cv2.dilate(mask, kernel, iterations = 1);
mask = cv2.erode(mask, kernel, iterations = 1);
# contours # OpenCV 3.4, in OpenCV 2 or 4 it returns (contours, _)
_, contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
contour = contours[0]; # just take the first one
# approx until 6 points
num_points = 999999;
step_size = 0.01;
percent = step_size;
while num_points >= 6:
# get number of points
epsilon = percent * cv2.arcLength(contour, True);
approx = cv2.approxPolyDP(contour, epsilon, True);
num_points = len(approx);
# increment
percent += step_size;
# step back and get the points
# there could be more than 6 points if our step size misses it
percent -= step_size * 2;
epsilon = percent * cv2.arcLength(contour, True);
approx = cv2.approxPolyDP(contour, epsilon, True);
# draw contour
cv2.drawContours(img, [approx], -1, (0,0,200), 2);
# draw points
for point in approx:
point = point[0]; # drop extra layer of brackets
center = (int(point[0]), int(point[1]));
cv2.circle(img, center, 4, (150, 200, 0), -1);
# do parallelogram approx to get the two "hidden" corners to complete our 3d rectangle
proposals = [];
size = len(approx);
for a in range(size):
# get points backwards
two = approx[a - 2][0];
one = approx[a - 1][0];
curr = approx[a][0];
# get vector from one -> two
dx = two[0] - one[0];
dy = two[1] - one[1];
hidden = [curr[0] + dx, curr[1] + dy];
proposals.append([hidden, curr, a, two]);
# debug draw
c = np.copy(copy);
cv2.circle(c, tup(two), 4, (255, 0, 0), -1);
cv2.circle(c, tup(one), 4, (0,255,0), -1);
cv2.circle(c, tup(curr), 4, (0,0,255), -1);
cv2.circle(c, tup(hidden), 4, (255,255,0), -1);
cv2.line(c, tup(two), tup(one), (0,0,200), 1);
cv2.line(c, tup(curr), tup(hidden), (0,0,200), 1);
cv2.imshow("Mark", c);
cv2.waitKey(0);
# draw proposals
for point in proposals:
point = point[0];
center = (point[0], point[1]);
cv2.circle(img, center, 4, (200, 100, 0), -1);
# group points and sum up points
hidden_corners = [[0,0], [0,0]];
for point in proposals:
# get index and update hidden corners
index = point[2] % 2;
pos = point[0];
hidden_corners[index][0] += pos[0];
hidden_corners[index][1] += pos[1];
# divide to get centroid
hidden_corners[0][0] /= 3.0;
hidden_corners[0][1] /= 3.0;
hidden_corners[1][0] /= 3.0;
hidden_corners[1][1] /= 3.0;
# draw new points
for point in proposals:
# unpack
pos = point[0];
parent = point[1];
index = point[2] % 2;
source = point[3];
# draw
color = [random.randint(0, 150) for a in range(3)];
cv2.line(img, tup(hidden_corners[index]), tup(parent), (0,0,200), 2);
cv2.line(img, tup(pos), tup(parent), color, 1);
cv2.line(img, tup(pos), tup(source), color, 1);
cv2.circle(img, tup(hidden_corners[index]), 4, (200, 200, 0), -1);
# show
cv2.imshow("Image", img);
cv2.imshow("Mask", mask);
cv2.waitKey(0);

Is there any way to use bivariate colormaps in matplotlib?

In other words, I want to make a heatmap (or surface plot) where the color varies as a function of 2 variables. (Specifically, luminance = magnitude and hue = phase.) Is there any native way to do this?
Some examples of similar plots:
Several good examples of exactly(?) what I want to do.
More examples from astronomy, but with non-perceptual hue
Edit: This is what I did with it: https://github.com/endolith/complex_colormap
imshow can take an array of [r, g, b] entries. So you can convert the absolute values to intensities and phases - to hues.
I will use as an example complex numbers, because for it it makes the most sense. If needed, you can always add numpy arrays Z = X + 1j * Y.
So for your data Z you can use e.g.
imshow(complex_array_to_rgb(Z))
where (EDIT: made it quicker and nicer thanks to this suggestion)
def complex_array_to_rgb(X, theme='dark', rmax=None):
'''Takes an array of complex number and converts it to an array of [r, g, b],
where phase gives hue and saturaton/value are given by the absolute value.
Especially for use with imshow for complex plots.'''
absmax = rmax or np.abs(X).max()
Y = np.zeros(X.shape + (3,), dtype='float')
Y[..., 0] = np.angle(X) / (2 * pi) % 1
if theme == 'light':
Y[..., 1] = np.clip(np.abs(X) / absmax, 0, 1)
Y[..., 2] = 1
elif theme == 'dark':
Y[..., 1] = 1
Y[..., 2] = np.clip(np.abs(X) / absmax, 0, 1)
Y = matplotlib.colors.hsv_to_rgb(Y)
return Y
So, for example:
Z = np.array([[3*(x + 1j*y)**3 + 1/(x + 1j*y)**2
for x in arange(-1,1,0.05)] for y in arange(-1,1,0.05)])
imshow(complex_array_to_rgb(Z, rmax=5), extent=(-1,1,-1,1))
imshow(complex_array_to_rgb(Z, rmax=5, theme='light'), extent=(-1,1,-1,1))
imshow will take an NxMx3 (rbg) or NxMx4 (grba) array so you can do your color mapping 'by hand'.
You might be able to get a bit of traction by sub-classing Normalize to map your vector to a scaler and laying out a custom color map very cleverly (but I think this will end up having to bin one of your dimensions).
I have done something like this (pdf link, see figure on page 24), but the code is in MATLAB (and buried someplace in my archives).
I agree a bi-variate color map would be useful (primarily for representing very dense vector fields where your kinda up the creek no matter what you do).
I think the obvious extension is to let color maps take complex arguments. It would require specialized sub-classes of Normalize and Colormap and I am going back and forth on if I think it would be a lot of work to implement. I suspect if you get it working by hand it will just be a matter of api wrangling.
I created an easy to use 2D colormap class, that takes 2 NumPy arrays and maps them to an RGB image, based on a reference image.
I used #GjjvdBurg's answer as a starting point. With a bit of work, this could still be improved, and possibly turned into a proper Python module - if you want, feel free to do so, I grant you all credits.
TL;DR:
# read reference image
cmap_2d = ColorMap2D('const_chroma.jpeg', reverse_x=True) # , xclip=(0,0.9))
# map the data x and y to the RGB space, defined by the image
rgb = cmap_2d(data_x, data_y)
# generate a colorbar image
cbar_rgb = cmap_2d.generate_cbar()
The ColorMap2D class:
class ColorMap2D:
def __init__(self, filename: str, transpose=False, reverse_x=False, reverse_y=False, xclip=None, yclip=None):
"""
Maps two 2D array to an RGB color space based on a given reference image.
Args:
filename (str): reference image to read the x-y colors from
rotate (bool): if True, transpose the reference image (swap x and y axes)
reverse_x (bool): if True, reverse the x scale on the reference
reverse_y (bool): if True, reverse the y scale on the reference
xclip (tuple): clip the image to this portion on the x scale; (0,1) is the whole image
yclip (tuple): clip the image to this portion on the y scale; (0,1) is the whole image
"""
self._colormap_file = filename or COLORMAP_FILE
self._img = plt.imread(self._colormap_file)
if transpose:
self._img = self._img.transpose()
if reverse_x:
self._img = self._img[::-1,:,:]
if reverse_y:
self._img = self._img[:,::-1,:]
if xclip is not None:
imin, imax = map(lambda x: int(self._img.shape[0] * x), xclip)
self._img = self._img[imin:imax,:,:]
if yclip is not None:
imin, imax = map(lambda x: int(self._img.shape[1] * x), yclip)
self._img = self._img[:,imin:imax,:]
if issubclass(self._img.dtype.type, np.integer):
self._img = self._img / 255.0
self._width = len(self._img)
self._height = len(self._img[0])
self._range_x = (0, 1)
self._range_y = (0, 1)
#staticmethod
def _scale_to_range(u: np.ndarray, u_min: float, u_max: float) -> np.ndarray:
return (u - u_min) / (u_max - u_min)
def _map_to_x(self, val: np.ndarray) -> np.ndarray:
xmin, xmax = self._range_x
val = self._scale_to_range(val, xmin, xmax)
rescaled = (val * (self._width - 1))
return rescaled.astype(int)
def _map_to_y(self, val: np.ndarray) -> np.ndarray:
ymin, ymax = self._range_y
val = self._scale_to_range(val, ymin, ymax)
rescaled = (val * (self._height - 1))
return rescaled.astype(int)
def __call__(self, val_x, val_y):
"""
Take val_x and val_y, and associate the RGB values
from the reference picture to each item. val_x and val_y
must have the same shape.
"""
if val_x.shape != val_y.shape:
raise ValueError(f'x and y array must have the same shape, but have {val_x.shape} and {val_y.shape}.')
self._range_x = (np.amin(val_x), np.amax(val_x))
self._range_y = (np.amin(val_y), np.amax(val_y))
x_indices = self._map_to_x(val_x)
y_indices = self._map_to_y(val_y)
i_xy = np.stack((x_indices, y_indices), axis=-1)
rgb = np.zeros((*val_x.shape, 3))
for indices in np.ndindex(val_x.shape):
img_indices = tuple(i_xy[indices])
rgb[indices] = self._img[img_indices]
return rgb
def generate_cbar(self, nx=100, ny=100):
"generate an image that can be used as a 2D colorbar"
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
return self.__call__(*np.meshgrid(x, y))
Usage:
Full example, using the constant chroma reference taken from here as a screenshot:
# generate data
x = y = np.linspace(-2, 2, 300)
xx, yy = np.meshgrid(x, y)
ampl = np.exp(-(xx ** 2 + yy ** 2))
phase = (xx ** 2 - yy ** 2) * 6 * np.pi
data = ampl * np.exp(1j * phase)
data_x, data_y = np.abs(data), np.angle(data)
# Here is the 2D colormap part
cmap_2d = ColorMap2D('const_chroma.jpeg', reverse_x=True) # , xclip=(0,0.9))
rgb = cmap_2d(data_x, data_y)
cbar_rgb = cmap_2d.generate_cbar()
# plot the data
fig, plot_ax = plt.subplots(figsize=(8, 6))
plot_extent = (x.min(), x.max(), y.min(), y.max())
plot_ax.imshow(rgb, aspect='auto', extent=plot_extent, origin='lower')
plot_ax.set_xlabel('x')
plot_ax.set_ylabel('y')
plot_ax.set_title('data')
# create a 2D colorbar and make it fancy
plt.subplots_adjust(left=0.1, right=0.65)
bar_ax = fig.add_axes([0.68, 0.15, 0.15, 0.3])
cmap_extent = (data_x.min(), data_x.max(), data_y.min(), data_y.max())
bar_ax.imshow(cbar_rgb, extent=cmap_extent, aspect='auto', origin='lower',)
bar_ax.set_xlabel('amplitude')
bar_ax.set_ylabel('phase')
bar_ax.yaxis.tick_right()
bar_ax.yaxis.set_label_position('right')
for item in ([bar_ax.title, bar_ax.xaxis.label, bar_ax.yaxis.label] +
bar_ax.get_xticklabels() + bar_ax.get_yticklabels()):
item.set_fontsize(7)
plt.show()
I know this is an old post, but want to help out others that may arrive late. Below is a python function to implement complex_to_rgb from sage. Note: This implementation isn't optimal, but it is readable. See links: (examples)(source code)
Code:
import numpy as np
def complex_to_rgb(z_values):
width = z_values.shape[0]
height = z_values.shape[1]
rgb = np.zeros(shape=(width, height, 3))
for i in range(width):
row = z_values[i]
for j in range(height):
# define value, real(value), imag(value)
zz = row[j]
x = np.real(zz)
y = np.imag(zz)
# define magnitued and argument
magnitude = np.hypot(x, y)
arg = np.arctan2(y, x)
# define lighness
lightness = np.arctan(np.log(np.sqrt(magnitude) + 1)) * (4 / np.pi) - 1
if lightness < 0:
bot = 0
top = 1 + lightness
else:
bot = lightness
top = 1
# define hue
hue = 3 * arg / np.pi
if hue < 0:
hue += 6
# set ihue and use it to define rgb values based on cases
ihue = int(hue)
# case 1
if ihue == 0:
r = top
g = bot + hue * (top - bot)
b = bot
# case 2
elif ihue == 1:
r = bot + (2 - hue) * (top - bot)
g = top
b = bot
# case 3
elif ihue == 2:
r = bot
g = top
b = bot + (hue - 2) * (top - bot)
# case 4
elif ihue == 3:
r = bot
g = bot + (4 - hue) * (top - bot)
b = top
# case 5
elif ihue == 4:
r = bot + (hue - 4) * (top - bot)
g = bot
b = top
# case 6
else:
r = top
g = bot
b = bot + (6 - hue) * (top - bot)
# set rgb array values
rgb[i, j, 0] = r
rgb[i, j, 1] = g
rgb[i, j, 2] = b
return rgb