Façon de contourner le bord extérieur de la région de grille sélectionnée en Python
J'ai le code suivant:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-np.pi/2, np.pi/2, 30)
y = np.linspace(-np.pi/2, np.pi/2, 30)
x,y = np.meshgrid(x,y)
z = np.sin(x**2+y**2)[:-1,:-1]
fig,ax = plt.subplots()
ax.pcolormesh(x,y,z)
Ce qui donne cette image :
Maintenant, disons que je veux mettre en surbrillance le bord de certaines cases de la grille :
highlight = (z > 0.9)
Je pourrais utiliser la fonction de contour, mais cela donnerait un contour "lissé". Je veux juste mettre en évidence le bord d'une région, en suivant le bord des cases de la grille.
Le plus proche que je suis venu ajoute quelque chose comme ceci:
highlight = np.ma.masked_less(highlight, 1)
ax.pcolormesh(x, y, highlight, facecolor = 'None', edgecolors = 'w')
Ce qui donne ce tracé :
Ce qui est proche, mais ce que je veux vraiment, c'est que seuls les bords extérieurs et intérieurs de ce "beignet" soient mis en évidence.
Donc, essentiellement, je recherche un hybride des fonctions contour et pcolormesh - quelque chose qui suit le contour d'une certaine valeur, mais suit les bacs de la grille par "étapes" plutôt que de se connecter point à point. Cela a-t-il du sens?
Remarque : dans les arguments pcolormesh, j'ai edgecolors = 'w'
, mais les bords sont toujours bleus. Que se passe-t-il ici?
EDIT : la réponse initiale de JohanC à l'aide de add_iso_line() fonctionne pour la question telle qu'elle est posée. Cependant, les données réelles que j'utilise sont une grille x,y très irrégulière, qui ne peut pas être convertie en 1D (comme requis pour add_iso_line()
.
J'utilise des données qui ont été converties de coordonnées polaires (rho, phi) en cartésiennes (x, y). La solution 2D posée par JohanC ne semble pas fonctionner pour le cas suivant :
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
def pol2cart(rho, phi):
x = rho * np.cos(phi)
y = rho * np.sin(phi)
return(x, y)
phi = np.linspace(0,2*np.pi,30)
rho = np.linspace(0,2,30)
pp, rr = np.meshgrid(phi,rho)
xx,yy = pol2cart(rr, pp)
z = np.sin(xx**2 + yy**2)
scale = 5
zz = ndimage.zoom(z, scale, order=0)
fig,ax = plt.subplots()
ax.pcolormesh(xx,yy,z[:-1, :-1])
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xmin, xmax = xx.min(), xx.max()
ymin, ymax = yy.min(), yy.max()
ax.contour(np.linspace(xmin,xmax, zz.shape[1]) + (xmax-xmin)/z.shape[1]/2,
np.linspace(ymin,ymax, zz.shape[0]) + (ymax-ymin)/z.shape[0]/2,
np.where(zz < 0.9, 0, 1), levels=[0.5], colors='red')
ax.set_xlim(*xlim)
ax.set_ylim(*ylim)
Réponses
Cet article montre un moyen de tracer de telles lignes. Comme il n'est pas simple de s'adapter au courant pcolormesh
, le code suivant illustre une adaptation possible. Notez que les versions 2d de x et y ont été renommées, car les versions 1d sont nécessaires pour les segments de ligne.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(-np.pi / 2, np.pi / 2, 30)
y = np.linspace(-np.pi / 2, np.pi / 2, 30)
xx, yy = np.meshgrid(x, y)
z = np.sin(xx ** 2 + yy ** 2)[:-1, :-1]
fig, ax = plt.subplots()
ax.pcolormesh(x, y, z)
def add_iso_line(ax, value, color):
v = np.diff(z > value, axis=1)
h = np.diff(z > value, axis=0)
l = np.argwhere(v.T)
vlines = np.array(list(zip(np.stack((x[l[:, 0] + 1], y[l[:, 1]])).T,
np.stack((x[l[:, 0] + 1], y[l[:, 1] + 1])).T)))
l = np.argwhere(h.T)
hlines = np.array(list(zip(np.stack((x[l[:, 0]], y[l[:, 1] + 1])).T,
np.stack((x[l[:, 0] + 1], y[l[:, 1] + 1])).T)))
lines = np.vstack((vlines, hlines))
ax.add_collection(LineCollection(lines, lw=1, colors=color))
add_iso_line(ax, 0.9, 'r')
plt.show()
Voici une adaptation de la deuxième réponse, qui ne peut fonctionner qu'avec des tableaux 2d :
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from scipy import ndimage
x = np.linspace(-np.pi / 2, np.pi / 2, 30)
y = np.linspace(-np.pi / 2, np.pi / 2, 30)
x, y = np.meshgrid(x, y)
z = np.sin(x ** 2 + y ** 2)
scale = 5
zz = ndimage.zoom(z, scale, order=0)
fig, ax = plt.subplots()
ax.pcolormesh(x, y, z[:-1, :-1] )
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xmin, xmax = x.min(), x.max()
ymin, ymax = y.min(), y.max()
ax.contour(np.linspace(xmin,xmax, zz.shape[1]) + (xmax-xmin)/z.shape[1]/2,
np.linspace(ymin,ymax, zz.shape[0]) + (ymax-ymin)/z.shape[0]/2,
np.where(zz < 0.9, 0, 1), levels=[0.5], colors='red')
ax.set_xlim(*xlim)
ax.set_ylim(*ylim)
plt.show()
Je vais essayer de refactoriser la add_iso_line
méthode afin de la rendre plus claire et ouverte aux optimisations. Donc, dans un premier temps, il y a une partie incontournable :
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(-np.pi/2, np.pi/2, 30)
y = np.linspace(-np.pi/2, np.pi/2, 30)
x, y = np.meshgrid(x,y)
z = np.sin(x**2+y**2)[:-1,:-1]
fig, ax = plt.subplots()
ax.pcolormesh(x,y,z)
xlim, ylim = ax.get_xlim(), ax.get_ylim()
highlight = (z > 0.9)
Voici maintenant highlight
un tableau binaire qui ressemble à ceci :
add_iso_line
la méthode) donc en utilisant simplement une boucle simple:
lines = []
cells = zip(*np.where(highlight))
for x, y in cells:
if x == 0 or highlight[x - 1, y] == 0: lines.append(([x, y], [x, y + 1]))
if x == highlight.shape[0] or highlight[x + 1, y] == 0: lines.append(([x + 1, y], [x + 1, y + 1]))
if y == 0 or highlight[x, y - 1] == 0: lines.append(([x, y], [x + 1, y]))
if y == highlight.shape[1] or highlight[x, y + 1] == 0: lines.append(([x, y + 1], [x + 1, y + 1]))
Et, enfin, je redimensionne et centre les coordonnées des lignes afin de les adapter à pcolormesh :
lines = (np.array(lines) / highlight.shape - [0.5, 0.5]) * [xlim[1] - xlim[0], ylim[1] - ylim[0]]
ax.add_collection(LineCollection(lines, colors='r'))
plt.show()
En conclusion, ceci est très similaire à la solution de JohanC et, en général, plus lent. Heureusement, nous pouvons réduire considérablement la quantité de cells
contours en extrayant uniquement en utilisant python-opencv
package :
import cv2
highlight = highlight.astype(np.uint8)
contours, hierarchy = cv2.findContours(highlight, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cells = np.vstack(contours).squeeze()
Voici une illustration des cellules en cours de vérification :