I would like to use nested circles as legend in Python in a seaborn scatterplot where I used their size to indicate a quantity.
For now I managed, using the legend's labels and handles, to get two circles for the two extremes. Would you know how I can nest them?
I have in mind something like this:
I tried to draw stacked circles in legend by writing Handlers. Here is the implementation.
This will give the figure like output image and would be modified to what you need.
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerLine2D, HandlerPathCollection
class HandlerPath(HandlerPathCollection):
"""
Custom Handler for HandlerPathCollection instances.
"""
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize, trans):
xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
width, height, fontsize)
leglines = []
legline_1 = Line2D([10], [5], marker='o', markerfacecolor='darkred', markersize=10, alpha=0.3)
legline_2 = Line2D([10], [5], marker='o', markerfacecolor='darkred', markersize=20, alpha=0.3)
legline_1.set_color("darkred")
legline_2.set_color("darkred")
leglines.append(legline_1)
leglines.append(legline_2)
return leglines
fig, ax = plt.subplots()
l = ax.plot([0.5, 0, -0.5], [0.5, 0, -0.5], linestyle = '--', color='darkred', marker='o', label="TinyTL")
c = ax.scatter([0.5, 0, -0.5], [0.5, 0, -0.5] , s = 1e3, alpha=0.3, color="darkred")
ax.legend(
l+[c], ["line", "circles"],
handler_map={
Line2D: HandlerLine2D(),
PathCollection: HandlerPath()
},
handlelength=2.5, handleheight=3
)
plt.savefig('test.png')
Hope this helps.
Recently, I want to achieve the same legend of the nested circles. The following is my implementation:
import matplotlib.pyplot as plt
import matplotlib.legend_handler as mhandler
import pandas as pd
# Sample data
data = [[ 0, 2, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 2, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 2, 0],
[ 2, 0, 6, 0, 0, 6, 0, 0],
[ 2, 2, 2, 2, 3, 0, 0, 2],
[ 8, 0, 8, 0, 0, 0, 0, 0],
[ 6, 6, 6, 2, 0, 0, 0, 0],
[10, 6, 2, 0, 0, 2, 0, 0],
[10, 10, 2, 0, 4, 2, 0, 0],
[ 8, 9, 8, 20, 10, 0, 8, 2]]
df = pd.DataFrame(data).reset_index().melt(id_vars=['index'])
fig, ax = plt.subplots(figsize=(7, 7), dpi=100)
bubbles = ax.scatter(
df['index'], df['variable'],
s=df['value']*70, # So that the marker is larger
marker='o',
c='#FFB24D', edgecolor='#FF6A1C', lw=1.5,
)
# Set markers' properties before they are used as the legend handles.
def set_marker_color(handle, orig_handle):
handle.update_from(orig_handle)
handle.set_facecolor('w')
handle.set_edgecolor('k')
legend = ax.legend([bubbles], ['value'], handletextpad=2,
scatterpoints=3, # Choose three marker points for a legend entry
handler_map={
type(bubbles):
mhandler.HandlerPathCollection(
sizes=[30*70, 10*70, 2*70], # Choose the corresponding size.
marker_pad=1, # So that all points have the same x coordinate.
yoffsets=[0, -1.2, -2.1], # Offset in the y direction to get the expected layout.
update_func=set_marker_color)}, # If not, the legend handles will be same as the original handles.
frameon=False,
loc='lower left',
bbox_to_anchor=(0.01, 1.05))
After that, you can use ax.text or ax.annotate to add some labels in the legend handles.
However, I haven't figured out any way to add the number labels automatically, or to get the marker_pad and yoffsets automatically.
I hope this can be a start, and someone can find a more generic way to achieve this kind of legend.
Related
I have the following script:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
nn = 400 # number of points along circle's perimeter
theta = np.linspace(0, 2*np.pi, nn)
rho = np.ones(nn)
# (x,y) represents points on circle's perimeter
x = np.ravel(rho*np.cos(theta))
y = np.ravel(rho*np.sin(theta))
fig, ax = plt.subplots()
plt.rcParams["figure.figsize"] = [6, 10]
ax = plt.axes(projection='3d') # set the axes for 3D plot
ax.azim = -90 # y rotation (default=270)
ax.elev = 21 # x rotation (default=0)
# low, high values of z for plotting 2 circles at different elev.
loz, hiz = -15, 15
# Plot two circles
ax.plot(x, y, hiz)
ax.plot(x, y, loz)
# set some indices to get proper (x,y) for line plotting
lo1,hi1 = 15, 15+nn//2
lo2,hi2 = lo1+nn//2-27, hi1-nn//2-27
# plot 3d lines using coordinates of selected points
ax.plot([x[lo1], x[hi1]], [y[lo1], y[hi1]], [loz, hiz])
ax.plot([x[lo2], x[hi2]], [y[lo2], y[hi2]], [loz, hiz])
ax.plot([0, 0, 0], [0, 0, 10])
ax.plot([0, 0, 0], [9, 0, 0])
ax.plot([0, 0, 0], [0, 8, 0])
plt.show()
At the end of the script, I would like to plot three lines in three directions. How to do that? Why this:
ax.plot([0, 0, 0], [0, 0, 10])
ax.plot([0, 0, 0], [9, 0, 0])
ax.plot([0, 0, 0], [0, 8, 0])
gives the line in same direction?
And I have a second question, please. How to make the cone more narrower (the base more similar to circle)?
Output now:
ax.plot([0, 0, 0], [0, 0, 10]) is giving plot the x and y coordinates of 3 points, but you haven't given any coordinates in the z direction. Remember the inputs to plot are x, y, z, not, as you seem to have assumed, (x0,y0,z0), (x1,y1,z1)
So this is drawing 3 "lines" where two of them start and end at x=y=z=0, and one of them extends to y=10. The other two ax.plot calls you have are doing similar things.
To draw three lines that start at the origin and each extend along one of the x, y, or z directions, you perhaps meant to use:
ax.plot([0, 0], [0, 0], [0, 10]) # extend in z direction
ax.plot([0, 0], [0, 8], [0, 0]) # extend in y direction
ax.plot([0, 9], [0, 0], [0, 0]) # extend in x direction
Note that this also makes your circles look more like circles
After commenting the last 3 lines of your code, the image is the output I am getting
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
nn = 400 # number of points along circle's perimeter
theta = np.linspace(0, 2*np.pi, nn)
rho = np.ones(nn)
# (x,y) represents points on circle's perimeter
x = np.ravel(rho*np.cos(theta))
y = np.ravel(rho*np.sin(theta))
fig, ax = plt.subplots()
plt.rcParams["figure.figsize"] = [6, 10]
ax = plt.axes(projection='3d') # set the axes for 3D plot
ax.azim = -90 # y rotation (default=270)
ax.elev = 21 # x rotation (default=0)
# low, high values of z for plotting 2 circles at different elev.
loz, hiz = -15, 15
# Plot two circles
ax.plot(x, y, hiz)
ax.plot(x, y, loz)
# set some indices to get proper (x,y) for line plotting
lo1,hi1 = 15, 15+nn//2
lo2,hi2 = lo1+nn//2-27, hi1-nn//2-27
# plot 3d lines using coordinates of selected points
ax.plot([x[lo1], x[hi1]], [y[lo1], y[hi1]], [loz, hiz])
ax.plot([x[lo2], x[hi2]], [y[lo2], y[hi2]], [loz, hiz])
#ax.plot([0, 0, 0], [0, 0, 10])
#ax.plot([0, 0, 0], [9, 0, 0])
#ax.plot([0, 0, 0], [0, 8, 0])
plt.show()
You can see that the base is almost a perfect circle. Because you are also plotting lines in your figure, it is giving an illusion that the base in not a circle.
And regarding the lines in 3 different directions. Since this part of code
ax.plot([0, 0, 0], [0, 0, 10])
ax.plot([0, 0, 0], [9, 0, 0])
ax.plot([0, 0, 0], [0, 8, 0])
has all zeroes in X-Axis, it is essentially plotting the lines on Y-Axis only.
When I give some values in the X-Axis part, like this
ax.plot([1, 0, 0], [0, 0, 10])
ax.plot([0, 0, 5], [9, 0, 0])
ax.plot([0, 8, 0], [0, 8, 0])
The output is
I hope this is what you were asking.
I'd like to label non-contiguous regions in an image with different labels.
I guess this should be possible with scikit-learn.
The image is stored for example in a 2D numpy.ndarray with zeroes and ones, the ones beeing different contiguous regions.
Let's have a look at the following very simple array where we have two contiguous regions of ones but these to regions are separated from each other by zeroes.
np.array([
[1, 1, 1, 0, 0, 0],
[1, 1, 0, 0, 0, 1],
[0, 1, 0, 1, 0, 1],
[1, 1, 0, 1, 1, 1]
], dtype = int)
the algorithm should label the top-left contiguous region with a label like '1' and the right bottom contiguous region with a second label '2'.
np.array([
[1, 1, 1, 0, 0, 0],
[1, 1, 0, 0, 0, 2],
[0, 1, 0, 2, 0, 2],
[1, 1, 0, 2, 2, 2]
], dtype = int)
Any hints how to do this?
import numpy as np
from skimage.measure import label
a = np.array([[1, 1, 1, 0, 0, 0],
[1, 1, 0, 0, 0, 1],
[0, 1, 0, 1, 0, 1],
[1, 1, 0, 1, 1, 1]], dtype = int)
label(a)
I would like to have names of axes as in the figure.
This could be a good starter. Try experiment with it.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
fig = plt.figure(figsize=[8,8])
ax = fig.gca(projection = '3d')
# some settings
vleng = 4
aleng = vleng/3.
p = np.array([vleng, 0, 0])
q = np.array([0, vleng, 0])
r = np.array([0, 0, vleng])
ax.plot(*np.vstack([[0,0,0], p]).T, color='b')
ax.plot(*np.vstack([[0,0,0], q]).T, color='g')
ax.plot(*np.vstack([[0,0,0], r]).T, color='r')
# plotting arrow at terminal of the lines
ax.quiver(vleng, 0, 0, aleng, 0, 0, \
length=0.5, arrow_length_ratio=0.5, color='r')
ax.quiver(0, vleng, 0, 0, aleng, 0, \
length=0.5, arrow_length_ratio=0.5, color='m')
ax.quiver(0, 0, vleng, 0, 0, aleng, \
length=0.5, arrow_length_ratio=0.5, color='k')
ax.text3D(vleng+1.5, 0, 0, 'X')
ax.text3D(0, vleng+1.0, 0, 'y')
ax.text3D(0, 0, vleng+1.0, 'z')
ax.azim = 35 # y rotation (default=270)
ax.elev = 20 # x rotation (default=0)
ax.dist = 15 # zoom (define perspective)
ax.set_axis_off( ) # hide all grid
ax.set_aspect('equal')
# plot poly1
ax.plot3D( [3.5, 0.25, 2, 3.5], [1, 0.25, 2.5, 1], [1.9, 3.2, 3.8, 1.9], label = 'one line', color='pink' )
# projection of poly1 on xy-plane
ax.plot3D( [3.5, 0.25, 2, 3.5], [1, 0.25, 2.5, 1], [0, 0, 0, 0], label = 'one line', color='gray' )
#ax.legend()
plt.show()
I got confused for the code I have here, when I develop my code into different cells in Jupyter Notebook, I gradually notice that the matplotlib graph shows up without calling plt.show(), which is anti-intuitive to me.
So if you implement the script below, the graph will pop up without plt.show():
data = np.array([
[5, 3, 2 ],
[2, -3, 5 ],
[ -4, 4, -6],
[-5, -3, -1],
[2, 6, 6]
])
bar_markers = np.array([4, 3, -2, 2, -1])
index = np.arange(len(data[:, 0]))
width = 0.15
fig, ax = plt.subplots()
ax.bar(index, data[:,0], width, bottom = 0, color = 'yellowgreen')
ax.bar(index+width, data[:, 1], width, bottom = 0, color = 'purple')
ax.bar(index+2*width, data[:, 2], width, bottom = 0, color = np.random.rand(3,))
ax.bar(index+width, [0]*len(bar_markers), width*3, bottom=bar_markers, edgecolor='k')
In a different word, this will cause confusion when you develop your code cell by cell in Jupyter Notebook, because 1)when you actually try to display graph using plt.show() in a different cell, it doesn't show any graph at all; 2) whenever you initialize graph using fig, ax = plt.subplots() it will display an empty graph right away. What I mean is shown below:
# in the first cell in Jupyter Notebook
data = np.array([
[5, 3, 2 ],
[2, -3, 5 ],
[ -4, 4, -6],
[-5, -3, -1],
[2, 6, 6]
])
bar_markers = np.array([4, 3, -2, 2, -1])
index = np.arange(len(data[:, 0]))
width = 0.15
fig, ax = plt.subplots()
Once implement it, the empty graph pops up without a reason.
# Then when you keep coding in a different cell
ax.bar(index, data[:,0], width, bottom = 0, color = 'yellowgreen')
ax.bar(index+width, data[:, 1], width, bottom = 0, color = 'purple')
ax.bar(index+2*width, data[:, 2], width, bottom = 0, color = np.random.rand(3,))
ax.bar(index+width, [0]*len(bar_markers), width*3, bottom=bar_markers, edgecolor='k')
plt.show()
It doesn't show anything at all.
If you know the reason why, please let us know, thank you in advance.
I'm running LBP algorithm to classify images by their texture features. Classifying method is LinearSVC in sklearn.svm package.
Getting histogram and fitting by SVM is done, but sometimes length of histogram varies depending on image.
Example is below:
from skimage import feature
from scipy.stats import itemfreq
from sklearn.svm import LinearSVC
import numpy as np
import cv2
import cvutils
import csv
import os
def __get_hist(image, radius):
NumPoint = radius*8
lbp = feature.local_binary_pattern(image, NumPoint, radius, method="uniform")
x = itemfreq(lbp.ravel())
hist = x[:,1]/sum(x[:,1])
return hist
def get_trainHist_list(train_txt):
train_dic = {}
with open(train_txt, 'r') as csvfile:
reader = csv.reader(csvfile, delimiter = ' ')
for row in reader:
train_dic[row[0]] = int(row[1])
hist_list=[]
key_list=[]
label_list=[]
for key, label in train_dic.items():
img = cv2.imread("D:/Python36/images/texture/%s" %key, cv2.IMREAD_GRAYSCALE)
key_list.append(key)
label_list.append(label)
hist_list.append(__get_hist(img,3))
bundle = [np.array(key_list), np.array(label_list), np.array(hist_list)]
return bundle
train_txt = 'D:/Python36/images/class_train.txt'
train_hist = get_trainHist_list(train_txt)
model = LinearSVC(C=100.0, random_state=42)
model.fit(train_hist[2], train_hist[1])
for i in train_hist[2]:
print(len(i))
test_img = cv2.imread("D:/Python36/images/texture_test/flat-3.png", cv2.IMREAD_GRAYSCALE)
hist= np.array(__get_hist(test_img, 3))
print(len(hist))
prediction = model.predict([hist])
print(prediction)
result
26
26
26
26
26
26
25
Traceback (most recent call last):
File "D:\Python36\texture.py", line 44, in <module>
prediction = model.predict([hist])
File "D:\Python36\lib\site-packages\sklearn\linear_model\base.py", line 324, in predict
scores = self.decision_function(X)
File "D:\Python36\lib\site-packages\sklearn\linear_model\base.py", line 305, in decision_function
% (X.shape[1], n_features))
ValueError: X has 25 features per sample; expecting 26
As you can see, length of histogram for training images is all 26, but test_img's is 25. For this reason, predict in SVM doesn't work.
I guess test_img has empty parts in the histogram, and that empty parts could have skipped. (I'm not sure)
Someone have idea to fix it?
There are 59 different uniform LBPs for a neighbourhood of 8 points. This should be the dimension of your feature vectors, but it is not because you used itemfreq to compute the histograms (as a side note, itemfreq is deprecated). The length of the histograms obtained throug itemfreq is the number of different uniform LBPs in the image. If some uniform LBPs are not present in the image the number of bins of the resulting histogram will be lower than 59. This issue can be easily fixed by utilizing bincount as demonstrated in the toy example below:
import numpy as np
from skimage import feature
from scipy.stats import itemfreq
lbp = np.array([[0, 0, 0, 0],
[1, 1, 1, 1],
[8, 8, 9, 9]])
hi = itemfreq(lbp.ravel())[:, 1] # wrong approach
hb = np.bincount(lbp.ravel(), minlength=59) # proposed method
The output looks like this:
In [815]: hi
Out[815]: array([4, 4, 2, 2], dtype=int64)
In [816]: hb
Out[816]:
array([4, 4, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0], dtype=int64)