Python'da seçili ızgara bölgesinin dış kenarını çevrelemenin yolu

Aug 18 2020

Takip koduna sahibim:

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)

Bu görüntüyü veren:

Şimdi, belirli ızgara kutularını vurgulamak istediğimi varsayalım:

highlight = (z > 0.9)

Kontur fonksiyonunu kullanabilirim, ancak bu "düzleştirilmiş" bir konturla sonuçlanır. Izgara kutularının kenarını takip ederek bir bölgenin kenarını vurgulamak istiyorum.

En yakın geldiğim şey şuna benzer bir şey eklemek:

highlight = np.ma.masked_less(highlight, 1)

ax.pcolormesh(x, y, highlight, facecolor = 'None', edgecolors = 'w')

Bu arsa verir:

Hangisi yakın, ama gerçekten istediğim şey, o "çörek" in sadece dış ve iç kenarlarının vurgulanması.

Bu yüzden, esasen kontur ve pcolormesh fonksiyonlarının bazı melezlerini arıyorum - bir değerin konturunu takip eden, ancak noktadan noktaya bağlamak yerine "adımlarla" grid kutularını takip eden bir şey. bu mantıklı mı?

Yan not: pcolormesh argümanlarında var edgecolors = 'w', ancak kenarlar hala mavi görünüyor. Orada neler oluyor?

DÜZENLEME: JohanC'nin add_iso_line () kullanan ilk cevabı, sorulduğu gibi soru için işe yarar. Bununla birlikte, kullandığım gerçek veriler, 1D'ye dönüştürülemeyen çok düzensiz bir x, y ızgarasıdır (gerektiği gibi add_iso_line().

Kutupsal koordinatlardan (rho, phi) kartezyen'e (x, y) dönüştürülmüş verileri kullanıyorum. JohanC'nin ortaya koyduğu 2D çözüm aşağıdaki durumda işe yaramıyor gibi görünüyor:

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)

Yanıtlar

1 JohanC Aug 18 2020 at 05:18

Bu gönderi , bu tür çizgiler çizmenin bir yolunu gösteriyor. Akıma uyum sağlamak kolay olmadığından pcolormesh, aşağıdaki kod olası bir uyarlamayı göstermektedir. Çizgi dilimleri için 1d sürümleri gerektiğinden, x ve y'nin 2d sürümlerinin yeniden adlandırıldığına dikkat edin.

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

İşte sadece 2d dizileriyle çalışabilen ikinci cevabın bir uyarlaması:

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

İyileştirmelere add_iso_linedaha açık ve net bir hale getirmek için yöntemi yeniden düzenlemeye çalışacağım . Yani, ilk başta yapılması gereken bir bölüm geliyor:

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)

Şimdi highlightşuna benzeyen bir ikili dizi var:

Bundan sonra Gerçek hücrelerin dizinlerini çıkarabiliriz, Yanlış mahalleleri arayabilir ve 'kırmızı' çizgilerin konumlarını belirleyebiliriz. Bunu vektörleştirilmiş bir şekilde yapmak konusunda yeterince rahat değilim ( add_iso_lineyöntemdeki gibi ) bu yüzden sadece basit döngü kullanarak:

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

Ve son olarak, pcolormesh ile sığdırmak için çizgilerin koordinatlarını yeniden boyutlandırıyorum ve ortalıyorum:

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

Sonuç olarak, bu JohanC çözümüne çok benzer ve genel olarak daha yavaştır. Neyse ki, cellskonturları yalnızca python-opencvpaketi kullanarak çıkararak, miktarını önemli ölçüde azaltabiliriz :

import cv2
highlight = highlight.astype(np.uint8)
contours, hierarchy = cv2.findContours(highlight, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cells = np.vstack(contours).squeeze()

Bu, kontrol edilen hücrelerin bir örneğidir: