Cara membuat kontur tepi luar dari wilayah grid yang dipilih dengan Python

Aug 18 2020

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

1 JohanC Aug 18 2020 at 05:18

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()

mathfux Aug 18 2020 at 10:48

Saya akan mencoba add_iso_linemetode 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 highlightadalah array biner yang terlihat seperti ini:

Setelah itu kita dapat mengekstrak indeks sel True, mencari lingkungan Salah dan mengidentifikasi posisi garis 'merah'. Saya tidak cukup nyaman dengan melakukannya dengan cara vektorisasi (seperti di sini dalam add_iso_linemetode) 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 cellssecara signifikan, mengekstraksi kontur hanya menggunakan python-opencvpaket:

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: