Cara membuat kontur tepi luar dari wilayah grid yang dipilih dengan Python
Saya memiliki kode berikut:
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)
Yang memberikan gambar ini:

Sekarang katakanlah saya ingin menyorot tepi kotak kisi tertentu:
highlight = (z > 0.9)
Saya dapat menggunakan fungsi kontur, tetapi ini akan menghasilkan kontur yang "dihaluskan". Saya hanya ingin menyorot tepi suatu wilayah, mengikuti tepi kotak kisi.
Yang terdekat saya datang adalah menambahkan sesuatu seperti ini:
highlight = np.ma.masked_less(highlight, 1)
ax.pcolormesh(x, y, highlight, facecolor = 'None', edgecolors = 'w')
Yang memberi plot ini:

Yang mana dekat, tapi yang saya benar-benar inginkan adalah hanya bagian luar dan dalam dari "donat" yang disorot.
Jadi pada dasarnya saya mencari beberapa hibrida dari fungsi kontur dan pcolormesh - sesuatu yang mengikuti kontur beberapa nilai, tetapi mengikuti kotak kisi dalam "langkah-langkah" daripada menghubungkan titik-ke-titik. Apakah itu masuk akal?
Catatan sampingan: Dalam argumen pcolormesh, saya punya edgecolors = 'w'
, tapi ujung-ujungnya masih berwarna biru. Apa yang sedang terjadi di sana?
EDIT: Jawaban awal JohanC menggunakan add_iso_line () berfungsi untuk pertanyaan seperti yang diajukan. Namun, data sebenarnya yang saya gunakan adalah petak x, y yang sangat tidak beraturan, yang tidak dapat dikonversi ke 1D (seperti yang diperlukan untuk add_iso_line()
.
Saya menggunakan data yang telah diubah dari koordinat kutub (rho, phi) menjadi kartesian (x, y). Solusi 2D yang diajukan oleh JohanC tampaknya tidak berfungsi untuk kasus berikut:
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)

Jawaban
Posting ini menunjukkan cara menggambar garis seperti itu. Karena tidak mudah untuk beradaptasi dengan arus pcolormesh
, kode berikut menunjukkan kemungkinan adaptasi. Perhatikan bahwa versi 2d dari x dan y telah diganti namanya, karena versi 1d diperlukan untuk segmen garis.
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()

Berikut adalah adaptasi dari jawaban kedua, yang dapat bekerja hanya dengan array 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()

Saya akan mencoba add_iso_line
metode refactor agar lebih jelas terbuka untuk pengoptimalan. Jadi, pada awalnya, ada bagian yang harus dilakukan:
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)
Sekarang highlight
adalah array biner yang terlihat seperti ini:

add_iso_line
metode) jadi hanya menggunakan loop sederhana:
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]))
Dan, akhirnya, saya mengubah ukuran dan koordinat garis tengah agar sesuai dengan 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()
Kesimpulannya, ini sangat mirip dengan solusi JohanC dan, secara umum, lebih lambat. Untungnya, kami dapat mengurangi jumlah cells
secara signifikan, mengekstraksi kontur hanya menggunakan python-opencv
paket:
import cv2
highlight = highlight.astype(np.uint8)
contours, hierarchy = cv2.findContours(highlight, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cells = np.vstack(contours).squeeze()
Ini adalah ilustrasi sel yang sedang diperiksa:
