简体   繁体   English

matplotlib轮廓** /凹形**非网格数据的轮廓/轮廓

[英]matplotlib contour/contourf of **concave** non-gridded data

I'd like to do a matplotlib contour or contourf plot of non-gridded 3D data (x, y, z) which is somehow C-shaped in x and y (see sketch) -- therefore part of the enclosing hull around the data is concave in x and y. 我想要做一个matplotlib contourcontourf非网格化的三维数据(X,Y,Z)的曲线图,是某种C形在x和y(见示意图) -因此围绕数据封闭船体部分在x和y中凹。

Usually I do plots of non-gridded 3D data by first interpolating it with 通常,我会先通过对非网格3D数据进行插值来进行绘制

from matplotlib.mlab import griddata
griddata...

but this generates artifacts in the concave part of the data such that the concave part is filled by the interpolation. 但是这会在数据的凹入部分中产生伪像,从而通过插值填充凹入部分。

Is it possible to do the interpolating or contourf/contour plots such that the concave part of the data is respected? 是否可以进行插值或等高线/等高线图,以使数据的凹面部分得到尊重?

在此处输入图片说明

Masking by condition 按条件屏蔽

Below is an example on how to use tricontourf with masking to obtain a concave shape without interpolated portions outside the data. 下面是一个示例,说明如何使用tricontourf和遮罩来获得凹形形状,而无需在数据外部插入部分。 It relies on the ability to mask the data depending on a condition. 它依赖于根据条件屏蔽数据的能力。

import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np

# create some data
rawx = np.random.rand(500)
rawy = np.random.rand(len(rawx))
cond01 = (rawx-1)**2 + rawy**2 <=1
cond02 = (rawx-0.7)**2 + rawy**2 >0.3
x = rawx[cond01 & cond02]
y = rawy[cond01 & cond02]
f = lambda x,y: np.sin(x*4)+np.cos(y)
z = f(x,y)
# now, x,y are points within a partially concave shape

triang0 = tri.Triangulation(x, y)
triang = tri.Triangulation(x, y)
x2 = x[triang.triangles].mean(axis=1) 
y2 = y[triang.triangles].mean(axis=1)
#note the very obscure mean command, which, if not present causes an error.
#now we need some masking condition.
# this is easy in this case where we generated the data according to the same condition
cond1 = (x2-1)**2 + y2**2 <=1
cond2 = (x2-0.7)**2 + (y2)**2 >0.3
mask = np.where(cond1 & cond2,0,1)
# apply masking
triang.set_mask(mask)


fig, (ax, ax2) = plt.subplots(ncols=2, figsize=(6,3))
ax.set_aspect("equal")
ax2.set_aspect("equal")

ax.tricontourf(triang0, z,  cmap="Oranges")
ax.scatter(x,y, s=3, color="k")

ax2.tricontourf(triang, z,  cmap="Oranges")
ax2.scatter(x,y, s=3, color="k")

ax.set_title("tricontourf without mask")
ax2.set_title("tricontourf with mask")
ax.set_xlim(0,1)
ax.set_ylim(0,1)
ax2.set_xlim(0,1)
ax2.set_ylim(0,1)

plt.show()

在此处输入图片说明

Masking by maximum side-length 通过最大边长掩盖

If you do not have access to the exact condition, but have a maximum side-length (distance) between points, the following would be a solution. 如果您无法获得确切的条件,但是在点之间具有最大的边长(距离),则可以采用以下解决方案。 It would mask out all triangles for which at least one side is longer than some maximum distance. 它将掩盖至少一侧长于某个最大距离的所有三角形。 This can be well applied if the point density is rather high. 如果点密度很高,则可以很好地应用。

import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np

# create some data
rawx = np.random.rand(500)
rawy = np.random.rand(len(rawx))
cond01 = (rawx-1)**2 + rawy**2 <=1
cond02 = (rawx-0.7)**2 + rawy**2 >0.3
x = rawx[cond01 & cond02]
y = rawy[cond01 & cond02]
f = lambda x,y: np.sin(x*4)+np.cos(y)
z = f(x,y)
# now, x,y are points within a partially concave shape

triang1 = tri.Triangulation(x, y)
triang2 = tri.Triangulation(x, y)
triang3 = tri.Triangulation(x, y)

def apply_mask(triang, alpha=0.4):
    # Mask triangles with sidelength bigger some alpha
    triangles = triang.triangles
    # Mask off unwanted triangles.
    xtri = x[triangles] - np.roll(x[triangles], 1, axis=1)
    ytri = y[triangles] - np.roll(y[triangles], 1, axis=1)
    maxi = np.max(np.sqrt(xtri**2 + ytri**2), axis=1)
    # apply masking
    triang.set_mask(maxi > alpha)

apply_mask(triang2, alpha=0.1)
apply_mask(triang3, alpha=0.3)

fig, (ax1, ax2, ax3) = plt.subplots(ncols=3, figsize=(9,3))

ax1.tricontourf(triang1, z,  cmap="Oranges")
ax1.scatter(x,y, s=3, color="k")

ax2.tricontourf(triang2, z,  cmap="Oranges")
ax2.scatter(x,y, s=3, color="k")

ax3.tricontourf(triang3, z,  cmap="Oranges")
ax3.scatter(x,y, s=3, color="k")

ax1.set_title("tricontourf without mask")
ax2.set_title("with mask (alpha=0.1)")
ax3.set_title("with mask (alpha=0.3)")

for ax in (ax1, ax2, ax3):
    ax.set(xlim=(0,1), ylim=(0,1), aspect="equal")

plt.show()

在此处输入图片说明

As can be seen, finding the correct parameter ( alpha ) here is may need some tweaking. 可以看出,在此处找到正确的参数( alpha )可能需要一些调整。

The answer provided by TheImportanceOfBeingErnest gave me the perfect starting point and I have manipulated the code above to provide a general solution to the contour plot of a concave hull/ alpha shape. TheImportanceOfBeingErnest提供的答案给了我一个完美的起点,我已经操纵了上面的代码为凹壳/ alpha形状的轮廓图提供了一个通用的解决方案。 I use the Python package shapely to create a polygon of the concave hull. 我匀称地使用Python包来创建凹壳的多边形。 This is not a built-in function it needs to be added, which I have taken from this post . 这不是一个内置函数,需要添加,我从这篇文章中获取了函数。 Once we have the polygon we simply check if the mean of the triangulated points are within the polygon and use this as our condition to form the mask. 一旦有了多边形,我们只需检查三角点的均值是否在多边形内,并以此为条件形成遮罩。

import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
# Start Using SHAPELY
import shapely.geometry as geometry
from shapely.geometry import Polygon, MultiPoint, Point
from shapely.ops import triangulate
from shapely.ops import cascaded_union, polygonize
from scipy.spatial import Delaunay

from descartes.patch import PolygonPatch
import math

# create some data
rawx = np.random.rand(500)
rawy = np.random.rand(len(rawx))
cond01 = (rawx-1)**2 + rawy**2 <=1
cond02 = (rawx-0.7)**2 + rawy**2 >0.3
x = rawx[cond01 & cond02]
y = rawy[cond01 & cond02]
f = lambda x,y: np.sin(x*4)+np.cos(y)
z = f(x,y)
# now, x,y are points within a partially concave shape

triang0 = tri.Triangulation(x, y)
triang = tri.Triangulation(x, y)
# Function for finding an alpha function
def alpha_shape(points, alpha):
  """
  Compute the alpha shape (concave hull) of a set
  of points.
  @param points: Iterable container of points.
  @param alpha: alpha value to influence the
      gooeyness of the border. Smaller numbers
      don't fall inward as much as larger numbers.
      Too large, and you lose everything!
  """
  if len(points) < 4:
    # When you have a triangle, there is no sense
    # in computing an alpha shape.
    return geometry.MultiPoint(list(points)).convex_hull
  def add_edge(edges, edge_points, coords, i, j):
    """
    Add a line between the i-th and j-th points,
    if not in the list already
    """
    if (i, j) in edges or (j, i) in edges:
       # already added
       return
    edges.add( (i, j) )
    edge_points.append(coords[ [i, j] ])

  coords = np.array([point.coords[0]
                     for point in points])

tri = Delaunay(coords)
  edges = set()
  edge_points = []
  # loop over triangles:
  # ia, ib, ic = indices of corner points of the
  # triangle
  for ia, ib, ic in tri.vertices:
      pa = coords[ia]
      pb = coords[ib]
      pc = coords[ic]
      # Lengths of sides of triangle
      a = math.sqrt((pa[0]-pb[0])**2 + (pa[1]-pb[1])**2)
      b = math.sqrt((pb[0]-pc[0])**2 + (pb[1]-pc[1])**2)
      c = math.sqrt((pc[0]-pa[0])**2 + (pc[1]-pa[1])**2)
      # Semiperimeter of triangle
      s = (a + b + c)/2.0
      # Area of triangle by Heron's formula
      area = math.sqrt(s*(s-a)*(s-b)*(s-c))
      circum_r = a*b*c/(4.0*area)
      # Here's the radius filter.
      #print circum_r
      if circum_r < 1.0/alpha:
          add_edge(edges, edge_points, coords, ia, ib)
          add_edge(edges, edge_points, coords, ib, ic)
          add_edge(edges, edge_points, coords, ic, ia)
  m = geometry.MultiLineString(edge_points)
  triangles = list(polygonize(m))
  #import ipdb; ipdb.set_trace()
  return cascaded_union(triangles), edge_points

# create array of points from reduced exp data to convert to Polygon
crds=np.array( [x,y]).transpose()
# Adjust the length of acceptable sides by adjusting the alpha parameter
concave_hull, edge_points = alpha_shape(MultiPoint(crds), alpha=2.3)

# View the polygon and adjust alpha if needed
def plot_polygon(polygon):
    fig = plt.figure(figsize=(10,10))
    ax = fig.add_subplot(111)
    margin = .3
    x_min, y_min, x_max, y_max = polygon.bounds
    ax.set_xlim([x_min-margin, x_max+margin])
    ax.set_ylim([y_min-margin, y_max+margin])
    patch = PolygonPatch(polygon, fc='#999999',
                         ec='#000000', fill=True,
                         zorder=-1)
    ax.add_patch(patch)
    return fig
plot_polygon(concave_hull); plt.plot(x,y,'.'); #plt.show()


# Use the mean distance between the triangulated x & y poitns
x2 = x[triang.triangles].mean(axis=1)
y2 = y[triang.triangles].mean(axis=1)
##note the very obscure mean command, which, if not present causes an error.
##now we need some masking condition.

# Create an empty set to fill with zeros and ones
cond = np.empty(len(x2))
# iterate through points checking if the point lies within the polygon
for i in range(len(x2)):
  cond[i] = concave_hull.contains(Point(x2[i],y2[i]))

mask = np.where(cond,0,1)
# apply masking
triang.set_mask(mask)

fig, (ax, ax2) = plt.subplots(ncols=2, figsize=(6,3))
ax.set_aspect("equal")
ax2.set_aspect("equal")

ax.tricontourf(triang0, z,  cmap="Oranges")
ax.scatter(x,y, s=3, color="k")

ax2.tricontourf(triang, z,  cmap="Oranges")
ax2.scatter(x,y, s=3, color="k")

ax.set_title("tricontourf without mask")
ax2.set_title("tricontourf with mask")
ax.set_xlim(0,1)
ax.set_ylim(0,1)
ax2.set_xlim(0,1)
ax2.set_ylim(0,1)

plt.show()

带有alpha形状蒙版的tricontour

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM