Regionprops finds always one region - python - python

I have an image with multiple globs, and when I am using regionprops to find the centroid and BB for each. However, it always finds only 1 for all of them.
More specifically, I get the results on the left image, but I want the one on the right image:
regions_problem
My code:
regions = regionprops(itk_label)
fig, ax = plt.subplots()
ax.imshow(itk_label, cmap=plt.cm.gray)
i=0
for props in regions:
i=i+1
print(str(i))
y0, x0 = props.centroid
ax.plot(x0, y0, '.r', markersize=5)
minr, minc, maxr, maxc = props.bbox
bx = (minc, maxc, maxc, minc, minc)
by = (minr, minr, maxr, maxr, minr)
ax.plot(bx, by, '-b', linewidth=1.0)
ax.axis((0, 600, 600, 0))
plt.show()
Can anyone tell me what I am doing wrong?
Funny thing is that the image on the right was also created by me in the past. However, I cannot do it again.

It's likely that your features have the same label value. regionprops requires that your features are labelled independently, ie. 1, 2, 3, .... Here is an example:
In this case both features have label value 1, so they are measured as if they are one object:
import numpy as np
from skimage import measure
from matplotlib import pyplot as plt
im = np.zeros((600, 600), dtype=int)
im[350:400, 350:400] = 1
im[510:530, 200:240] = 1
np.unique(im)
>>> array([0, 1])
plt.matshow(im)
regions = measure.regionprops(im)
for r in regions:
plt.plot(*r.centroid[::-1], marker='x', color='r')
If you use the measure.label function you can label the separated features independently, ie. label values 1, 2:
plt.matshow(im)
labelled = measure.label(im)
np.unique(labelled)
>>> array([0, 1, 2])
regions = measure.regionprops(labelled)
for r in regions:
plt.plot(*r.centroid[::-1], marker='x', color='r')
and this gives you your desired result.

Related

What's the meaning of clip_box in Matplotlib Artist?

I am testing the clip_box feature of Artist using the code snippet below:
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
import numpy as np
fig = plt.figure()
ax = fig.subplots(1, 2)
x = [1, 2, 3, 4]
y = [3, 8, 5, 2]
line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)
boundingbox = Bbox(np.array([[0, 0], [3, 9]]))
line_b.set_clip_box(boundingbox)
line_b.set_clip_on(True)
plt.show()
What I expect is the last part of line_b will be cut out by the clip box, and line_b will be a bit shorter than line_a.
It turns out that there's nothing left on the second subplot. It's totally empty. Is my understanding of the clip_box wrong or are there some issues in the code snippet?
The "natural" clip box for the right hand side plot is ax[1].bbox. Finding its extent tells us what units should be used to specify the clip box Bbox.
Since we don't add the Bbox instance to any axes when we create, it could only be relative to the figure. When we print ax[1].bbox, we can see that its size is to be specified in pixels.
It's indeed much simpler to use a Rectangle or Polygon to specify the clip box because they can be added to axes. Using 'none' color for its facecolor could be more convenient because it's figure style-independent.
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
fig = plt.figure(dpi=89)
ax = fig.subplots(1, 2)
x = [1, 2, 3, 4]
y = [3, 8, 5, 2]
line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)
print(ax[1].bbox, '\n', ax[1].bbox.extents)
# the line above prints
# TransformedBbox(
# Bbox(x0=0.5477272727272726, y0=0.10999999999999999, x1=0.8999999999999999, y1=0.88),
# BboxTransformTo(
# TransformedBbox(
# Bbox(x0=0.0, y0=0.0, x1=6.393258426966292, y1=4.797752808988764),
# Affine2D().scale(178.0))))
# [ 623.31363636 93.94 1024.2 751.52 ]
# 178.0 is 2 * dpi, I believe the doubling happens because of what screen I have got
boundingbox = Bbox.from_extents([623.31363636, 93.94, 900.2, 751.52])
print(boundingbox, '\n', boundingbox.extents)
# the line above prints
# Bbox(x0=623.31363636, y0=93.94, x1=900.2, y1=751.52)
# [623.31363636 93.94 900.2 751.52 ]
line_b.set_clip_box(boundingbox)
line_b.set_clip_on(True)
plt.show()
I've spent some time reading about Bboxes in Matplotlib and they are pretty complicated. The set_clip_box method you refer to has not got very helpful documentation, and the examples of its use both use the bbox of an Axes, which is a nested transformation; ie _, ax = plt.subplots(); ax.bbox is a TransformedBbox based on a linear transform of another TransformedBbox based on an Affine2D transform of a plain Bbox! (All of this explained in more detail here.)
It seems that these involve transformations between different sets of co-ordinates; in the case of a regular Axes it is between x- and y-values, pixels, and the specific adaptations to screen size. I would be happy to hear from someone who knows more about Bboxes why your Bbox acts the way it does. But what you want to achieve can be done much more easily, using a FancyBboxPatch (a Rectangle patch would work just as well):
from matplotlib.patches import FancyBboxPatch
f, ax = plt.subplots(1, 2)
x = [1, 2, 3, 4]
y = [3, 8, 5, 2]
line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)
bb = Bbox([[0, 0], [3, 9]])
ax[1].add_patch(FancyBboxPatch((bb.xmin, bb.ymin), bb.width, bb.height, boxstyle="square",
ec='white', fc='white', zorder=2.1))
(ec and fc are edge colour and fill colour; zorder determines the artist order. Lines are 2, so we just need out Bbox patch to be slightly higher.)

Not getting the heatmap in the background using Matplotlib Python

I have tried this and got the result as in the image:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list("", ["red","grey","green"])
df = pd.read_csv('t.csv', header=0)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax = ax1.twiny()
# Scatter plot of positive points, coloured blue (C0)
ax.scatter(np.argwhere(df['real'] > 0), df.loc[df['real'] > 0, 'real'], color='C2')
# Scatter plot of negative points, coloured red (C3)
ax.scatter(np.argwhere(df['real'] < 0), df.loc[df['real'] < 0, 'real'], color='C3')
# Scatter neutral values in grey (C7)
ax.scatter(np.argwhere(df['real'] == 0), df.loc[df['real'] == 0, 'real'], color='C7')
ax.set_ylim([df['real'].min(), df['real'].max()])
index = len(df.index)
ymin = df['prediction'].min()
ymax= df['prediction'].max()
ax1.imshow([np.arange(index),df['prediction']],cmap=cmap,
extent=(0,index-1,ymin, ymax), alpha=0.8)
plt.show()
Image:
I was expecting one output where the color is placed according to the figure. I am getting green color and no reds or greys.
I want to get the image or contours spread as the values are. How I can do that? See the following image, something similar:
Please let me know how I can achieve this. The data I used is here: t.csv
For a live version, have a look at Tensorflow Playground
There are essentially 2 tasks required in a solution like this:
Plot the heatmap as the background;
Plot the scatter data;
Output:
Source code:
import numpy as np
import matplotlib.pyplot as plt
###
# Plot heatmap in the background
###
# Setting up input values
x = np.arange(-6.0, 6.0, 0.1)
y = np.arange(-6.0, 6.0, 0.1)
X, Y = np.meshgrid(x, y)
# plot heatmap colorspace in the background
fig, ax = plt.subplots(nrows=1)
im = ax.imshow(X, cmap=plt.cm.get_cmap('RdBu'), extent=(-6, 6, -6, 6), interpolation='bilinear')
cax = fig.add_axes([0.21, 0.95, 0.6, 0.03]) # [left, bottom, width, height]
fig.colorbar(im, cax=cax, orientation='horizontal') # add colorbar at the top
###
# Plot data as scatter
###
# generate the points
num_samples = 150
theta = np.linspace(0, 2 * np.pi, num_samples)
# generate inner points
circle_r = 2
r = circle_r * np.random.rand(num_samples)
inner_x, inner_y = r * np.cos(theta), r * np.sin(theta)
# generate outter points
circle_r = 4
r = circle_r + np.random.rand(num_samples)
outter_x, outter_y = r * np.cos(theta), r * np.sin(theta)
# plot data
ax.scatter(inner_x, inner_y, s=30, marker='o', color='royalblue', edgecolors='white', linewidths=0.8)
ax.scatter(outter_x, outter_y, s=30, marker='o', color='crimson', edgecolors='white', linewidths=0.8)
ax.set_ylim([-6,6])
ax.set_xlim([-6,6])
plt.show()
To keep things simple, I kept the colorbar range (-6, 6) to match the data range.
I'm sure this code can be changed to suit your specific needs. Good luck!
Here is a possible solution.
A few notes and questions:
What are the 'prediction' values in your data file? They do not seem to correlate with the values in the 'real' column.
Why do you create a second axis? What is represented on the bottom X-axis in your plot? I removed the second axis and labelled the remaining axes (index and real).
When you slice a pandas DataFrame, the index comes with it. You don't need to create a separate index (argwhere and arange(index) in your code). I simplified the first part of the code, where scatterplots are produced.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list("", ["red","grey","green"])
df = pd.read_csv('t.csv', header=0)
print(df)
fig = plt.figure()
ax = fig.add_subplot(111)
# Data limits
xmin = 0
xmax = df.shape[0]
ymin = df['real'].min()
ymax = df['real'].max()
# Scatter plots
gt0 = df.loc[df['real'] > 0, 'real']
lt0 = df.loc[df['real'] < 0, 'real']
eq0 = df.loc[df['real'] == 0, 'real']
ax.scatter(gt0.index, gt0.values, edgecolor='white', color='C2')
ax.scatter(lt0.index, lt0.values, edgecolor='white', color='C3')
ax.scatter(eq0.index, eq0.values, edgecolor='white', color='C7')
ax.set_ylim((ymin, ymax))
ax.set_xlabel('index')
ax.set_ylabel('real')
# We want 0 to be in the middle of the colourbar,
# because gray is defined as df['real'] == 0
if abs(ymax) > abs(ymin):
lim = abs(ymax)
else:
lim = abs(ymin)
# Create a gradient that runs from -lim to lim in N number of steps,
# where N is the number of colour steps in the cmap.
grad = np.arange(-lim, lim, 2*lim/cmap.N)
# Arrays plotted with imshow must be 2D arrays. In this case it will be
# 1 pixel wide and N pixels tall. Set the aspect ratio to auto so that
# each pixel is stretched out to the full width of the frame.
grad = np.expand_dims(grad, axis=1)
im = ax.imshow(grad, cmap=cmap, aspect='auto', alpha=1, origin='bottom',
extent=(xmin, xmax, -lim, lim))
fig.colorbar(im, label='real')
plt.show()
This gives the following result:

Overlaying plots on a single graph

I have two heatmaps which are based on 2d histograms that I am trying to overlay on a single graph. The limits of their axes (extent_L and extent_H) do not necessarily coincide exactly. I can make the individual plots satisfactorily if needed, but when trying to show both heatmaps on a single graph nicely, only the most recent one is displayed.
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
# Generate some test data
x_L = np.random.randn(8873)
y_L = np.random.randn(8873)
x_H = np.random.randn(1000)
y_H = np.random.randn(1000)
heatmap_L, xedges_L, yedges_L = np.histogram2d(x_L, y_L, bins=50)
extent_L = [xedges_L[0], xedges_L[-1], yedges_L[0], yedges_L[-1]]
heatmap_H, xedges_H, yedges_H = np.histogram2d(x_H, y_H, bins=50)
extent_H = [xedges_H[0], xedges_H[-1], yedges_H[0], yedges_H[-1]]
plt.clf()
im1 = plt.imshow(heatmap_L.T, extent=extent_L, origin='lower', cmap='Blues')
im2 = plt.imshow(heatmap_H.T, extent=extent_H, origin='lower', cmap='Greens')
plt.show()
Edit: If I'm not mistaken, all points are not in exactly the proper location
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
# Generate some test data
x_L = np.random.randn(8873)
y_L = np.random.randn(8873)
x_H = np.random.randn(1000)
y_H = np.random.randn(1000)
heatmap_L, xedges_L, yedges_L = np.histogram2d(x_L, y_L, bins=50)
extent_L = np.array([xedges_L[0], xedges_L[-1], yedges_L[0], yedges_L[-1]])
heatmap_H, xedges_H, yedges_H = np.histogram2d(x_H, y_H, bins=50)
extent_H = np.array([xedges_H[0], xedges_H[-1], yedges_H[0], yedges_H[-1]])
plt.clf()
im1 = plt.imshow(heatmap_L.T, extent=extent_L, origin='lower', cmap='Blues')
im2 = plt.imshow(heatmap_H.T, extent=extent_H, origin='lower', cmap='Greens')
plt.autoscale()
plt.show()
flatHMH = np.reshape(heatmap_H, 2500) # flatten the 2D arrays
flatHML = np.reshape(heatmap_L, 2500)
maxHMH = flatHMH.max() # Find the maximum in each
maxHML = flatHML.max()
# Now for each value in the flat array build an RGBA tuple using
# 1 for the colour we want - either green or blue, and then scaling
# the value by the maximum, finally reshaping back to a 50x50 array
augHMH = np.array([(0, 1, 0, x/maxHMH) for x in flatHMH]).reshape((50, 50, 4))
augHML = np.array([(0, 0, 1, x/maxHML) for x in flatHML]).reshape((50, 50, 4))
plt.clf()
# Plot without cmap as colours are now part of the data array passed.
im1 = plt.imshow(augHML, extent=extent_L, origin='lower')
im2 = plt.imshow(augHMH, extent=extent_H, origin='lower')
plt.autoscale()
plt.show()
If you look closely at the points in the last plot, for example the clustering of points at the edge, you'll notice they are not the same as in the plot above.
You are displaying both plots, the problem is that you are drawing one on top of the other. To see this in action you can shift one of the plots as in:
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
# Generate some test data
x_L = np.random.randn(8873)
y_L = np.random.randn(8873)
x_H = np.random.randn(1000)
y_H = np.random.randn(1000)
heatmap_L, xedges_L, yedges_L = np.histogram2d(x_L, y_L, bins=50)
extent_L = np.array([xedges_L[0], xedges_L[-1], yedges_L[0], yedges_L[-1]])
heatmap_H, xedges_H, yedges_H = np.histogram2d(x_H, y_H, bins=50)
extent_H = np.array([xedges_H[0], xedges_H[-1], yedges_H[0], yedges_H[-1]])
plt.clf()
im1 = plt.imshow(heatmap_L.T, extent=extent_L, origin='lower', cmap='Blues')
im2 = plt.imshow(heatmap_H.T+2, extent=extent_H+2, origin='lower', cmap='Greens')
plt.autoscale()
plt.show()
You also need the plt.autoscale() call in there as otherwise the limits are not adjusted correctly.
One way to show the two plots on top of each other is to use the argument alpha=X to the imshow call (where 0 < X < 1) in order to set transparency on the plot call. Another, possibly clearer way is to transform each value from the histogram2D to an RGBA value. See the imshow docs for both alternatives to displaying the plots on top of each other.
One way of transforming the values would be to flatten the data, and augment it with the colours you want.
# imports and test data generation as before, removed for clarity...
flatHMH = np.reshape(heatmap_H, 2500) # flatten the 2D arrays
flatHML = np.reshape(heatmap_L, 2500)
maxHMH = flatHMH.max() # Find the maximum in each
maxHML = flatHML.max()
# Now for each value in the flat array build an RGBA tuple using
# 1 for the colour we want - either green or blue, and then scaling
# the value by the maximum, finally reshaping back to a 50x50 array
augHMH = np.array([(0, 1, 0, x/maxHMH) for x in flatHMH]).reshape((50, 50, 4))
augHML = np.array([(0, 0, 1, x/maxHML) for x in flatHML]).reshape((50, 50, 4))
plt.clf()
# Plot without cmap as colours are now part of the data array passed.
im1 = plt.imshow(augHML, extent=extent_L, origin='lower')
im2 = plt.imshow(augHMH, extent=extent_H, origin='lower')
plt.autoscale()
plt.show()
You can call
plt.autoscale()
such that the limits are adjusted to the content of the axes.
Example:
import numpy as np
import matplotlib.pyplot as plt
def get(offs=0):
# Generate some test data
x = np.random.randn(8873)+offs
y = np.random.randn(8873)+offs
heatmap, xedges, yedges = np.histogram2d(x, y, bins=50)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
heatmap, xedges, yedges = np.histogram2d(x, y, bins=50)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
return heatmap, extent
h1,e1 = get(-3)
h2,e2 = get(+3)
plt.imshow(h1, extent=e1, origin='lower', cmap="RdBu")
plt.imshow(h2, extent=e2, origin='lower', cmap="YlGnBu")
plt.autoscale()
plt.show()

How to plot vectors in python using matplotlib

I am taking a course on linear algebra and I want to visualize the vectors in action, such as vector addition, normal vector, so on.
For instance:
V = np.array([[1,1],[-2,2],[4,-7]])
In this case I want to plot 3 vectors V1 = (1,1), M2 = (-2,2), M3 = (4,-7).
Then I should be able to add V1,V2 to plot a new vector V12(all together in one figure).
when I use the following code, the plot is not as intended
import numpy as np
import matplotlib.pyplot as plt
M = np.array([[1,1],[-2,2],[4,-7]])
print("vector:1")
print(M[0,:])
# print("vector:2")
# print(M[1,:])
rows,cols = M.T.shape
print(cols)
for i,l in enumerate(range(0,cols)):
print("Iteration: {}-{}".format(i,l))
print("vector:{}".format(i))
print(M[i,:])
v1 = [0,0],[M[i,0],M[i,1]]
# v1 = [M[i,0]],[M[i,1]]
print(v1)
plt.figure(i)
plt.plot(v1)
plt.show()
How about something like
import numpy as np
import matplotlib.pyplot as plt
V = np.array([[1,1], [-2,2], [4,-7]])
origin = np.array([[0, 0, 0],[0, 0, 0]]) # origin point
plt.quiver(*origin, V[:,0], V[:,1], color=['r','b','g'], scale=21)
plt.show()
Then to add up any two vectors and plot them to the same figure, do so before you call plt.show(). Something like:
plt.quiver(*origin, V[:,0], V[:,1], color=['r','b','g'], scale=21)
v12 = V[0] + V[1] # adding up the 1st (red) and 2nd (blue) vectors
plt.quiver(*origin, v12[0], v12[1])
plt.show()
NOTE: in Python2 use origin[0], origin[1] instead of *origin
This may also be achieved using matplotlib.pyplot.quiver, as noted in the linked answer;
plt.quiver([0, 0, 0], [0, 0, 0], [1, -2, 4], [1, 2, -7], angles='xy', scale_units='xy', scale=1)
plt.xlim(-10, 10)
plt.ylim(-10, 10)
plt.show()
Your main problem is you create new figures in your loop, so each vector gets drawn on a different figure. Here's what I came up with, let me know if it's still not what you expect:
CODE:
import numpy as np
import matplotlib.pyplot as plt
M = np.array([[1,1],[-2,2],[4,-7]])
rows,cols = M.T.shape
#Get absolute maxes for axis ranges to center origin
#This is optional
maxes = 1.1*np.amax(abs(M), axis = 0)
for i,l in enumerate(range(0,cols)):
xs = [0,M[i,0]]
ys = [0,M[i,1]]
plt.plot(xs,ys)
plt.plot(0,0,'ok') #<-- plot a black point at the origin
plt.axis('equal') #<-- set the axes to the same scale
plt.xlim([-maxes[0],maxes[0]]) #<-- set the x axis limits
plt.ylim([-maxes[1],maxes[1]]) #<-- set the y axis limits
plt.legend(['V'+str(i+1) for i in range(cols)]) #<-- give a legend
plt.grid(b=True, which='major') #<-- plot grid lines
plt.show()
OUTPUT:
EDIT CODE:
import numpy as np
import matplotlib.pyplot as plt
M = np.array([[1,1],[-2,2],[4,-7]])
rows,cols = M.T.shape
#Get absolute maxes for axis ranges to center origin
#This is optional
maxes = 1.1*np.amax(abs(M), axis = 0)
colors = ['b','r','k']
for i,l in enumerate(range(0,cols)):
plt.axes().arrow(0,0,M[i,0],M[i,1],head_width=0.05,head_length=0.1,color = colors[i])
plt.plot(0,0,'ok') #<-- plot a black point at the origin
plt.axis('equal') #<-- set the axes to the same scale
plt.xlim([-maxes[0],maxes[0]]) #<-- set the x axis limits
plt.ylim([-maxes[1],maxes[1]]) #<-- set the y axis limits
plt.grid(b=True, which='major') #<-- plot grid lines
plt.show()
EDIT OUTPUT:
What did you expect the following to do?
v1 = [0,0],[M[i,0],M[i,1]]
v1 = [M[i,0]],[M[i,1]]
This is making two different tuples, and you overwrite what you did the first time... Anyway, matplotlib does not understand what a "vector" is in the sense you are using. You have to be explicit, and plot "arrows":
In [5]: ax = plt.axes()
In [6]: ax.arrow(0, 0, *v1, head_width=0.05, head_length=0.1)
Out[6]: <matplotlib.patches.FancyArrow at 0x114fc8358>
In [7]: ax.arrow(0, 0, *v2, head_width=0.05, head_length=0.1)
Out[7]: <matplotlib.patches.FancyArrow at 0x115bb1470>
In [8]: plt.ylim(-5,5)
Out[8]: (-5, 5)
In [9]: plt.xlim(-5,5)
Out[9]: (-5, 5)
In [10]: plt.show()
Result:
Thanks to everyone, each of your posts helped me a lot.
rbierman code was pretty straight for my question, I have modified a bit and created a function to plot vectors from given arrays. I'd love to see any suggestions to improve it further.
import numpy as np
import matplotlib.pyplot as plt
def plotv(M):
rows,cols = M.T.shape
print(rows,cols)
#Get absolute maxes for axis ranges to center origin
#This is optional
maxes = 1.1*np.amax(abs(M), axis = 0)
colors = ['b','r','k']
fig = plt.figure()
fig.suptitle('Vectors', fontsize=10, fontweight='bold')
ax = fig.add_subplot(111)
fig.subplots_adjust(top=0.85)
ax.set_title('Vector operations')
ax.set_xlabel('x')
ax.set_ylabel('y')
for i,l in enumerate(range(0,cols)):
# print(i)
plt.axes().arrow(0,0,M[i,0],M[i,1],head_width=0.2,head_length=0.1,zorder=3)
ax.text(M[i,0],M[i,1], str(M[i]), style='italic',
bbox={'facecolor':'red', 'alpha':0.5, 'pad':0.5})
plt.plot(0,0,'ok') #<-- plot a black point at the origin
# plt.axis('equal') #<-- set the axes to the same scale
plt.xlim([-maxes[0],maxes[0]]) #<-- set the x axis limits
plt.ylim([-maxes[1],maxes[1]]) #<-- set the y axis limits
plt.grid(b=True, which='major') #<-- plot grid lines
plt.show()
r = np.random.randint(4,size=[2,2])
print(r[0,:])
print(r[1,:])
r12 = np.add(r[0,:],r[1,:])
print(r12)
plotv(np.vstack((r,r12)))
Vector addition performed on random vectors
All nice solutions, borrowing and improvising for special case -> If you want to add a label near the arrowhead:
arr = [2,3]
txt = “Vector X”
ax.annotate(txt, arr)
ax.arrow(0, 0, *arr, head_width=0.05, head_length=0.1)
In order to match the vector lenght and angle with the x,y coordinates of the plot, you can use to following options to plt.quiver:
plt.figure(figsize=(5,2), dpi=100)
plt.quiver(0,0,250,100, angles='xy', scale_units='xy', scale=1)
plt.xlim(0,250)
plt.ylim(0,100)
Quiver is a good method once you figure out its annoying nuances, like not plotting vectors in their original scales. To do as far as I can tell you must pass these params to quiver call as many have pointed out: angles='xy', scale_units='xy', scale=1 AND you should set your plt.xlim and plt.ylim such that you get a square or near square grid. That is the only way I have gotten it to consistently plot the way I want. For instance passing a origin as *[0,0] and U, V as *[5,3] means the resulting plot should be a vector centered at 0,0 origin that goes over 5 units to the right on the x-axis and 3 units up on the y-axis.

Is it possible to do additive blending with matplotlib?

When dealing with overlapping high density scatter or line plots of different colors it can be convenient to implement additive blending schemes, where the RGB colors of each marker add together to produce the final color in the canvas. This is a common operation in 2D and 3D render engines.
However, in Matplotlib I've only found support for alpha/opacity blending. Is there any roundabout way of doing it or am I stuck with rendering to bitmap and then blending them in some paint program?
Edit: Here's some example code and a manual solution.
This will produce two partially overlapping random distributions:
x1 = randn(1000)
y1 = randn(1000)
x2 = randn(1000) * 5
y2 = randn(1000)
scatter(x1,y1,c='b',edgecolors='none')
scatter(x2,y2,c='r',edgecolors='none')
This will produce in matplotlib the following:
As you can see, there are some overlapping blue points that are occluded by red points and we would like to see them. By using alpha/opacity blending in matplotlib, you can do:
scatter(x1,y1,c='b',edgecolors='none',alpha=0.5)
scatter(x2,y2,c='r',edgecolors='none',alpha=0.5)
Which will produce the following:
But what I really want is the following:
I can do it manually by rendering each plot independently to a bitmap:
xlim = plt.xlim()
ylim = plt.ylim()
scatter(x1,y1,c='b',edgecolors='none')
plt.xlim(xlim)
plt.ylim(ylim)
scatter(x2,y2,c='r',edgecolors='none')
plt.xlim(xlim)
plt.ylim(ylim)
plt.savefig(r'scatter_blue.png',transparent=True)
plt.savefig(r'scatter_red.png',transparent=True)
Which gives me the following images:
What you can do then is load them as independent layers in Paint.NET/PhotoShop/gimp and just additive blend them.
Now ideal would be to be able to do this programmatically in Matplotlib, since I'll be processing hundreds of these!
If you only need an image as the result, you can get the canvas buffer as a numpy array, and then do the blending, here is an example:
from matplotlib import pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.scatter(x1,y1,c='b',edgecolors='none')
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
ax.patch.set_facecolor("none")
ax.patch.set_edgecolor("none")
fig.canvas.draw()
w, h = fig.canvas.get_width_height()
img = np.frombuffer(fig.canvas.buffer_rgba(), np.uint8).reshape(h, w, -1).copy()
ax.clear()
ax.scatter(x2,y2,c='r',edgecolors='none')
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
ax.patch.set_facecolor("none")
ax.patch.set_edgecolor("none")
fig.canvas.draw()
img2 = np.frombuffer(fig.canvas.buffer_rgba(), np.uint8).reshape(h, w, -1).copy()
img[img[:, :, -1] == 0] = 0
img2[img2[:, :, -1] == 0] = 0
fig.clf()
plt.imshow(np.maximum(img, img2))
plt.subplots_adjust(0, 0, 1, 1)
plt.axis("off")
plt.show()
the result:
This feature is now supported by my matplotlib backend https://github.com/anntzer/mplcairo (master only):
import matplotlib; matplotlib.use("module://mplcairo.qt")
from matplotlib import pyplot as plt
from mplcairo import operator_t
import numpy as np
x1 = np.random.randn(1000)
y1 = np.random.randn(1000)
x2 = np.random.randn(1000) * 5
y2 = np.random.randn(1000)
fig, ax = plt.subplots()
# The figure and axes background must be made transparent.
fig.patch.set(alpha=0)
ax.patch.set(alpha=0)
pc1 = ax.scatter(x1, y1, c='b', edgecolors='none')
pc2 = ax.scatter(x2, y2, c='r', edgecolors='none')
operator_t.ADD.patch_artist(pc2) # Use additive blending.
plt.show()

Categories

Resources