3D surface plot of colorspace in python - python

My computer setup is Mac Mojave 10.14.4.
I am new to Python so I am using Jupyter Lab so that I can understand what each part is producing so please can you respond similarly.
I want to produce a 3d surface plot of a digitally printed fabric sample with z-axis plotting the color space.
Here is the hardcopy file.
[]
Here is the 3dContour plot of the same test fabric
img = cv2.imread(testROYGBIVB.jpg) img - cv2.cvtColor(img,
cv2.COLOR_BGR2HSV)
plt.imshow(img)
img0 = img
img0.shape
(70, 90,3)
x, y, z = img0.T
x = np.linspace(0, 7, 70) #start, step, total
y = np.linspace(0, 9, 90)
X, Y = np.meshgrid(x, y)
Z = np.invert(z) #makes it easier to view
font = {'family': 'sans-serif',
'color': 'black',
'weight': 'normal',
'size': 16,
}
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.contour3D(X, Y, Z, 256, cmap='cubehelix_r')
ax.set_xlabel('x',fontdict=font)
ax.set_ylabel('y',fontdict=font)
ax.set_zlabel('z',fontdict=font); #RGB values
ax.set_title('Ultra Cotton',fontdict=font);
plt.tight_layout()
plt.savefig('UltaCotton.png')
ax.view_init(60, 35)
fig
[]
My question is this - the color space values of my plot are HSV.
I can split these values as seen below to create a scatter.
But I would like to maintain the rod structure from the contour but with the color of the rods matching the defined color space HSV as seen in the scatter.
I would like my contour plot and my scatter plot to have a hybrid baby.
FYI - the z values were inverted so that the top surface would be easily visible.
Can this be done? Thanks
flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
len(flags)
258
flags[40]
'COLOR_BGR2RGB'
hsv_img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
pixel_colors = img.reshape((np.shape(img)[0]*np.shape(img)[1], 3))
norm = colors.Normalize(vmin=-1.,vmax=1.)
norm.autoscale(pixel_colors)
pixel_colors = norm(pixel_colors).tolist()
h, s, v = cv2.split(hsv_img)
fig = plt.figure()
axis = fig.add_subplot(1, 1, 1, projection="3d")
axis.scatter(h.flatten(), s.flatten(), v.flatten(), facecolors=pixel_colors, marker=".")
axis.set_xlabel("Hue")
axis.set_ylabel("Saturation")
axis.set_zlabel("Value")
plt.show()
]4
plt.tight_layout()
plt.savefig('filename.png')
axis.view_init(45, 35)
#ax.set_title('Ultra Cotton');
plt.tight_layout()
plt.savefig('filenameView.png')
fig
[]

Like the others, I'm confused by what you are trying to achieve.
Is this anything like what you had in mind?
img = plt.imread('Jb2Y5.jpg')
nx,ny,_ = img.shape
X, Y = np.meshgrid(np.linspace(0,ny,ny),np.linspace(0,nx,nx))
fig, (ax1, ax2, ax3) = plt.subplots(1,3,subplot_kw=dict(projection='3d'), figsize=(10,3))
ax1.plot_surface(X,Y, img[:,:,0], cmap="Reds", alpha=0.5)
ax1.set_title('RED')
ax2.plot_surface(X,Y, img[:,:,1], cmap='Greens', alpha=0.5)
ax2.set_title('GREEN')
ax3.plot_surface(X,Y, img[:,:,2], cmap='Blues', alpha=0.5)
ax3.set_title('BLUE')

Related

How to plot 2 maps in 1 plot using Matplotlib? (Python)

I was trying to overlay 1 map on another, but I could not.
input_file = "slicedSmoothedStokesPlanck/Smoothed_Sliced_PSI_MAP.fits"
i_file = "slicedSmoothedStokesPlanck/Smoothed_Sliced_I_MAP.fits"
pl_b = fits.getdata(input_file, ext=0)
i_file_data = fits.getdata(i_file, ext=0)
fig = plt.figure()
ax = fig.add_subplot(111) #, projection=wcs
im = ax.imshow(texture, alpha=0.5) #, cmap='RdYlBu_r'
ax.imshow(i_file_data)
plt.title("TEST")
plt.show()
The above code, only shows last ax.imshow(i_file_data)
The idea is that I have a map1, and map2. I want to overlay map2 with alpha = 0.5 on map1, and plot it.
If I am getting you correctly then this should work. I assume your bottom layer has color and top layer has just boundaries.
fig, ax = plt.subplots(1,1, figsize(8, 8))
pl_b.plot(ax = ax, cmap = "RdYlBu_r", edgecolor = "black", lw = 0.1) #, this is your map 1
i_file_data.plot(ax = ax, facecolor = "none", edgecolor = "black", lw = 0.1, alpha = 0.5) #, this is your map 2
plt.title("TEST")
plt.show()
You can tweak the top layer and change the opacity accordingly.
If you want your map 2 be just the outline on top of map 1 then set facecolor = "none" in the second layer

Saving Pyplot 3d plot to image

I've been trying to convert a 3d plot to a png image, but I keep getting weird artifacts with the plots texts and ticks. I don't know what I'm doing wrong
Here is my 3d plot function :
def plot_3d(frame, fig, title, _max, _min =0):
ax = fig.add_subplot(111, projection='3d')
dz = frame.flatten()
wherepos = np.where(dz>0)
dz = dz[wherepos]
dz -= (_min)
ys = np.concatenate([[i]*4 for i in range(4)])[wherepos]
xs = np.mod(range(16),4)[wherepos]
dx = 1
dy = 1
zs = np.zeros(len(wherepos)) + _min
# creating the plot
ax.bar3d(xs, ys, zs, dx,
dy, dz, color='green')
# setting title and labels
ax.set_title("3D bar plot")
ax.set_xlabel('column')
ax.set_ylabel('row')
ax.set_zlabel('obj 1 distance')
ax.set_xlim([0, 4])
# ax.set_ylim([0, 4])
ax.set_ylim([4,0])
ax.set_zlim([_min,_max])
ax.set_xticks(list(range(5)))
ax.set_yticks(list(range(5)))
# Plot with angle (pixel (4,0) in front)
ax.view_init(elev = 50 ,azim=-135)
# Plot with no angle (row 0 on top)
# ax.view_init(elev = 50 ,azim=-90)
plt.title(title)
This function is called there :
plot_3d(frame, fig, plot_name, _max, _min)
fig.canvas.draw()
img = np.frombuffer(fig.canvas.tostring_rgb(), dtype="uint8")
img = img.reshape(frameSize[1], frameSize[0], 3)
plt.imsave("test.png",img)
The result is as follows:
[Plot with weird artifacts]

Why is my annotation causing my figure size to change in matplotlib?

I am quite new to matplotlib and I am trying to do something simple, but I can't quite get the hang of it.
So I'm trying to piece together a couple of png images and label them A, B, C etc... but I want the labels to be in terms of figure fractions, so I can scale them in the future.
Here's my code for plotting the images:
im, h, w = loadImList(imList)
total_height = h[0] + h[1]
total_width = max(w[0],w[1])
new_img = np.ones(shape=(total_height, total_width, 3))
hStart[0] = 0
hStart[1] = h[0]
wStart[0] = 0
wStart[1] = 0
nIm = len(im)
for iDx in np.arange(len(im)):
new_img[np.int32(hStart[iDx]):np.int32(hStart[iDx]+h[iDx]),np.int32(wStart[iDx]):np.int32(wStart[iDx]+w[iDx])]=im[iDx]
fig_w = 4.7
fig_h = total_height / total_width * fig_w
fig, ax = plt.subplots(figsize=(fig_w, fig_h), constrained_layout=True)
fig.set_facecolor("white")
plt.imshow(new_img)
ax.set_xticks([])
ax.set_yticks([])
And this is what I get:
And then I tried to write a function that adds in the annotations:
def addTextbox(textstr, x, y, ax):
offsetbox = TextArea(textstr)
ab = AnnotationBbox(offsetbox, (x,y),
xybox=(x, y),
xycoords='figure fraction',
boxcoords="figure fraction",
box_alignment=(0, 0),
frameon=False,
pad=0,
arrowprops=None,
fontsize=36,
clip_on = False)
ax.add_artist(ab)
return ax
Adding ax = addTextbox("B)", 0.05, 0.5, ax) gives the image below which is reasonable.
But, when I do
ax = addTextbox("A)", 0.01, 0.6, ax)
ax = addTextbox("B)", 0.05, 0.5, ax)
I get a white margin on the left side of the image, and the B) annotation shifts. I don't understand why this is happening, or how do I solve it. Any help will be appreciated. Thanks!

Ensure matplotlib colorbar looks the same across multiple figure

Consider the following code
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
def make_Z(X,Y, offset=0.):
return np.sin(X) + np.cos(Y) + offset
class MidpointNormalize(colors.Normalize):
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
self.midpoint = midpoint
colors.Normalize.__init__(self, vmin, vmax, clip)
def __call__(self, value, clip=None):
# I'm ignoring masked values and all kinds of edge cases to make a
# simple example...
x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
return np.ma.masked_array(np.interp(value, x, y), np.isnan(value))
################################################################################
x,y = np.linspace(-10, 10, 100), np.linspace(-10, 10, 100)
X, Y = np.meshgrid(x,y)
Z = make_Z(X,Y, offset=0.8)#; print(Z.shape)
tens = np.logspace(0., 3.0, num=4); print(tens)
Zs = [Z/i for i in tens]#; print(len(Zs))
min_Z = np.min(Zs)
max_Z = np.max(Zs)
norm = MidpointNormalize(vmin=min_Z, vmax=max_Z, midpoint=0.)
for Z in Zs:
print("min, max: %f, %f"%(np.min(Z), np.max(Z)))
fig, ax = plt.subplots()
Z_plot = ax.contourf(X,Y,Z, levels=100, norm=norm, cmap='seismic', vmin=min_Z, vmax=max_Z)
plt.colorbar(Z_plot, ax=ax)
plt.show()
print("-"*70)
This produces the following output:
Explanation: I make a Z value based on X and Y coordinates, then divide the Z value in each figure by powers of ten (100, 101, 102, 103), hence the plots becoming weaker and weaker further down.
Basically, everything is as expected. However, I would like the same colourbar to be applied to all figures. At the moment it looks like the same colormap is applied (which it should be), but I want the full "blue-on-bottom-red-above"- colorbar in all figures, even those that appear all white in colour. I am somewhat confident that I tried out all the arguments to contourf and colorbar, including the vmin and vmax arguments, but can not get the full range colorbar to be shown on all figures. Any hints?
EDIT:
I had the idea to maybe adjust the ylim property of the colorbar, since it is after all just a regular axis. Defining the colorbar as cbar and then doing cbar.ax.set_ylim(min_Z, max_Z) results in gibberish, however.
Option 1
I don't know if this is acceptable for you, but you can replace contourf with imshow:
Z_plot = ax.imshow(Z, norm=norm, cmap='seismic', vmin=min_Z, vmax=max_Z, interpolation = 'bilinear')
Option 2
matplotlib.pyplot.colorbar is linked to the mappable you pass it as the first argument. A trick can be to pass to colorbar the non scaled contour.
Try if this code fit for you:
for Z, ten in zip(Zs, tens):
print("min, max: %f, %f"%(np.min(Z), np.max(Z)))
fig, ax = plt.subplots()
plt.colorbar(ax.contourf(X,Y,Z*ten, levels=100, norm=norm, cmap='seismic', vmin=min_Z, vmax=max_Z), ax=ax)
Z_plot = ax.contourf(X, Y, Z, levels = 100, norm = norm, cmap = 'seismic', vmin = min_Z, vmax = max_Z)
plt.show()
print("-"*70)
Option 3
The same concept of above but this time I set up the mappable to pass to colorbar in a more clean way.
for Z in Zs:
print("min, max: %f, %f"%(np.min(Z), np.max(Z)))
fig, ax = plt.subplots()
Z_plot = ax.contourf(X, Y, Z, levels = 100, norm = norm, cmap = 'seismic', vmin = min_Z, vmax = max_Z)
m = cm.ScalarMappable(cmap = cm.seismic, norm = colors.TwoSlopeNorm(vmin = np.min(Z), vcenter = 0, vmax = np.max(Z)))
m.set_array(Z)
m.set_clim(np.min(Z), np.max(Z))
cbar = plt.colorbar(m, boundaries = np.linspace(np.min(Z), np.max(Z), 100))
ticks = np.append(0, np.linspace(np.min(Z), np.max(Z), 9))
ticks.sort()
cbar.set_ticks(ticks)
plt.show()
print("-"*70)
This answer is wholly thanks to Zephyr's answer above, which I accepted for the third code block, which I have adapted below to be used both in the MWE I supplied as well as my actual code:
x,y = np.linspace(-10, 10, 100), np.linspace(-10, 10, 100)
X, Y = np.meshgrid(x,y)
Z = make_Z(X,Y, offset=0.8)#; print(Z.shape)
tens = np.logspace(0., 3.0, num=4); print(tens)
Zs = [Z/i for i in tens]#; print(len(Zs))
min_Z = np.min(Zs)
max_Z = np.max(Zs)
for Z in Zs:
print("min, max: %f, %f"%(np.min(Z), np.max(Z)))
fig, ax = plt.subplots()
Z_plot = ax.contourf(X,Y,Z, levels=100, norm=norm, cmap='seismic', vmin=min_Z, vmax=max_Z)
m = cm.ScalarMappable(cmap = 'seismic', norm = norm)
cbar = plt.colorbar(m, boundaries = np.linspace(min_Z, max_Z, 100))
plt.show()
print("-"*70)
Differences to Zephyr's answer:
the only part I took over from his answer is the the line containing ScalarMappable, in which I have set the norm to be the same as the one used in contourf
verything regarding tickmarks I have eliminated because for me the automatic tickmarks sufficed.
the set_array and set_clim functions I have eliminated because they did not change anything for me, and frankly, I don't know what they are good for.

Setting color of area in Matplotlib

I'm creating a chart with matplotlib, here is my code:
fig = plt.figure(facecolor='#131722',dpi=155, figsize=(8, 4))
ax1 = plt.subplot2grid((1,2), (0,0), facecolor='#131722')
Colors = [['#0400ff', '#FF0000'], ['#09ff00', '#ff8c00']]
for x in List:
Index = List.index(x)
rate_buy = []
total_buy = []
for y in x['data']['bids']:
rate_buy.append(y[0])
total_buy.append(y[1])
rBuys = pd.DataFrame({'buy': rate_buy})
tBuys = pd.DataFrame({'total': total_buy})
ax1.plot(rBuys.buy, tBuys.total, color=Colors[Index][0], linewidth=0.5, alpha=0.8)
ax1.fill_between(rBuys.buy, 0, tBuys.total, facecolor=Colors[Index][0], alpha=1)
And here is the output:
The problem with the current output is that the colors of the two areas are "merging": basically the area BELOW the blue line should be blue, but instead it's green. How can i set it to be blue, for example, like in my example?
Example List data:
[[9665, 0.07062500000000001], [9666, 0.943708], [9667, 5.683787000000001], [9668, 9.802289], [9669, 11.763305], [9670, 14.286004], [9671, 16.180122], [9672, 23.316723000000003], [9673, 30.915156000000003], [9674, 33.44226200000001], [9675, 36.14526200000001], [9676, 45.76024100000001], [9677, 51.85294700000001], [9678, 58.79529300000001], [9679, 59.05322900000001], [9680, 60.27704500000001], [9681, 60.743885000000006], [9682, 66.75103700000001], [9683, 71.86412600000001], [9684, 73.659636], [9685, 78.08502800000001], [9686, 78.19614200000001], [9687, 79.98396400000001], [9688, 90.55855800000002]]
I guess the hint of #JohanC is correct, you are plotting in the wrong order and overlay your previous plots with new ones.
I tried to recreate a small example where total_buy1 > total_buy0, so in order to get the desired result you first have to plot total_buy1
and then total_buy0:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
Colors = [['#0400ff', '#FF0000'],
['#09ff00', '#ff8c00']]
n = 100
rate_buy = np.linspace(0, 1000, 100)
total_buy0 = np.linspace(0, 300, n)[::-1] + np.random.normal(scale=10, size=n)
total_buy1 = np.linspace(0, 600, n)[::-1] + np.random.normal(scale=10, size=n)
ax.plot(rate_buy, total_buy1, color=Colors[1][1], linewidth=0.5, alpha=0.8)
ax.fill_between(rate_buy, 0, total_buy1, facecolor=Colors[1][0], alpha=1)
ax.plot(rate_buy, total_buy0, color=Colors[0][1], linewidth=0.5, alpha=0.8)
ax.fill_between(rate_buy, 0, total_buy0, facecolor=Colors[0][0], alpha=1)
I noticed that you use Colors[Index][0] for both plotting calls, so the line and the area will not have different colors.

Categories