如何使用Matplotlib删除/忽略较小的轮廓线 [英] How to remove/omit smaller contour lines using matplotlib

查看:137
本文介绍了如何使用Matplotlib删除/忽略较小的轮廓线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试绘制压力水平的contour条线.我正在使用一个包含更高分辨率数据(范围从3 km到27 km)的netCDF文件.由于具有更高的分辨率数据集,我得到了很多不需要绘制的压力值(相反,我不介意忽略一些无关紧要的轮廓线).我根据此链接中给出的示例编写了一些绘图脚本 http://matplotlib.org/basemap/users/examples.html .

绘制图像后,如下图

从图像中我圈出了很小的轮廓,不需要绘制.另外,我想绘制所有contour线,使其更平滑,如上图所示.总的来说,我想得到这样的轮廓图像:-

我认为可能的解决方案

  1. 找出绘制轮廓线和遮罩/忽略这些线的数量所需的点数.

  1. 找到轮廓区域(因为我只想忽略圆形的轮廓),并忽略/遮​​罩那些较小的区域.

  1. 将距离增加到50 km-100 km,降低分辨率(仅轮廓).

我能够使用SO线程 Python成功获得积分:从matplotlib.pyplot.contour()找到轮廓线

但是我不能使用这些要点来实现上面建议的任何解决方案.

任何实现上述建议解决方案的方法都将受到赞赏.

修改:-

@安德拉斯·迪克(Andras Deak) 我在del(level.get_paths()[kp])行上方使用了print 'diameter is ', diameter行,以检查代码是否滤出了所需的直径.这是我设置if diameter < 15000:时的过滤消息:

diameter is  9099.66295612
diameter is  13264.7838257
diameter is  445.574234531
diameter is  1618.74618114
diameter is  1512.58974168

但是,生成的图像没有任何效果.看起来都与上面的姿势相同.我很确定我已经保存了图形(绘制了风钩后).

关于降低分辨率的解决方案,plt.contour(x[::2,::2],y[::2,::2],mslp[::2,::2])它可以工作.我必须应用一些过滤器才能使曲线平滑.

用于删除行的完整示例代码:-

这是您进行审核的示例代码

#!/usr/bin/env python
from netCDF4 import Dataset
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import numpy as np
import scipy.ndimage
from mpl_toolkits.basemap import interp
from mpl_toolkits.basemap import Basemap


# Set default map
west_lon = 68
east_lon = 93
south_lat = 7
north_lat = 23

nc = Dataset('ncfile.nc')
# Get this variable for later calucation
temps = nc.variables['T2']
time = 0  # We will take only first interval for this example
# Draw basemap
m = Basemap(projection='merc', llcrnrlat=south_lat, urcrnrlat=north_lat,
                llcrnrlon=west_lon, urcrnrlon=east_lon, resolution='l')
m.drawcoastlines()
m.drawcountries(linewidth=1.0)
# This sets the standard grid point structure at full resolution
x, y = m(nc.variables['XLONG'][0], nc.variables['XLAT'][0])

# Set figure margins
width = 10
height = 8

plt.figure(figsize=(width, height))
plt.rc("figure.subplot", left=.001)
plt.rc("figure.subplot", right=.999)
plt.rc("figure.subplot", bottom=.001)
plt.rc("figure.subplot", top=.999)

plt.figure(figsize=(width, height), frameon=False)

# Convert Surface Pressure to Mean Sea Level Pressure
stemps = temps[time] + 6.5 * nc.variables['HGT'][time] / 1000.
mslp = nc.variables['PSFC'][time] * np.exp(9.81 / (287.0 * stemps) * nc.variables['HGT'][time]) * 0.01 + (
    6.7 * nc.variables['HGT'][time] / 1000)

# Contour only at 2 hpa interval
level = []
for i in range(mslp.min(), mslp.max(), 1):
    if i % 2 == 0:
        if i >= 1006 and i <= 1018:
            level.append(i)

# Save mslp values to upload to SO thread
# np.savetxt('mslp.txt', mslp, fmt='%.14f', delimiter=',')

P = plt.contour(x, y, mslp, V=2, colors='b', linewidths=2, levels=level)


# Solution suggested by Andras Deak
for level in P.collections:
    for kp,path in enumerate(level.get_paths()):
        # include test for "smallness" of your choice here:
        # I'm using a simple estimation for the diameter based on the
        #    x and y diameter...
        verts = path.vertices # (N,2)-shape array of contour line coordinates
        diameter = np.max(verts.max(axis=0) - verts.min(axis=0))

        if diameter < 15000: # threshold to be refined for your actual dimensions!
            #print 'diameter is ', diameter
            del(level.get_paths()[kp])  # no remove() for Path objects:(
            #level.remove() # This does not work. produces ValueError: list.remove(x): x not in list

plt.gcf().canvas.draw()


plt.savefig('dummy', bbox_inches='tight')
plt.close()

保存情节后,我得到相同的图像

您可以看到行尚未删除.这是指向我们尝试与 http://的mslp数组的链接. www.mediafire.com/download/7vi0mxqoe0y6pm9/mslp.txt

如果您想要以上代码中使用的xy数据,我可以上载以供审核.

线条流畅

您编写了代码,以删除效果理想的较小圆圈.但是,我在原始帖子(平滑行)中询问的另一个问题似乎不起作用.我已经使用您的代码对数组进行切片,以获取最小值并绘制轮廓.我使用以下代码来减小数组大小:-

slice = 15

CS = plt.contour(x[::slice,::slice],y[::slice,::slice],mslp[::slice,::slice], colors='b', linewidths=1, levels=levels)

结果在下面.

经过几个小时的搜索,我发现此SO线程存在类似问题:-

重新捕获常规的netcdf数据

但是那里提供的解决方案都没有用.类似于上面我的问题没有适当的解决方案.如果解决了此问题,则代码是完美的和完整的.

解决方案

一般思想

您的问题似乎有两个截然不同的一半:一个关于省略小的轮廓,另一个关于使轮廓线平滑.后者更简单,因为除了降低您的contour()呼叫的分辨率(就像您说的那样)之外,我除了考虑之外,别无选择.

至于去除一些轮廓线,这是一个基于直接直接去除轮廓线的解决方案.您必须遍历contour()返回的对象的collections,并针对每个元素检查每个Path,然后删除不需要的元素.重新绘制figure的画布将消除不必要的线条:

# dummy example based on matplotlib.pyplot.clabel example:
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt

delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)


plt.figure()
CS = plt.contour(X, Y, Z)

for level in CS.collections:
    for kp,path in reversed(list(enumerate(level.get_paths()))):
        # go in reversed order due to deletions!

        # include test for "smallness" of your choice here:
        # I'm using a simple estimation for the diameter based on the
        #    x and y diameter...
        verts = path.vertices # (N,2)-shape array of contour line coordinates
        diameter = np.max(verts.max(axis=0) - verts.min(axis=0))

        if diameter<1: # threshold to be refined for your actual dimensions!
            del(level.get_paths()[kp])  # no remove() for Path objects:(

# this might be necessary on interactive sessions: redraw figure
plt.gcf().canvas.draw()

这是直径阈值为1的原始(左)和已删除的版本(右)(请注意顶部的0级小块):

请注意,顶部的小行被删除,而中间的巨大青色行则没有被删除,即使它们都对应于相同的collections元素,即相同的轮廓线.如果我们不想允许这样做,我们可以调用CS.collections[k].remove(),这可能是一种更安全的方法来执行相同的操作(但不允许我们在对应同一轮廓的多条线之间进行区分级别).

为了表明摆弄截止直径可以按预期进行,这是阈值2的结果:

总而言之,这似乎很合理.


您的实际情况

由于您已经添加了实际数据,因此这是您的案例中的应用程序.请注意,您可以使用np直接在一行中生成level,这几乎可以得到相同的结果.可以在2行中实现完全相同的操作(生成arange,然后选择介于p1p2之间的那些行).另外,由于您在对contour的调用中设置了levels,所以我相信函数调用的V=2部分无效.

import numpy as np
import matplotlib.pyplot as plt

# insert actual data here...
Z = np.loadtxt('mslp.txt',delimiter=',')
X,Y = np.meshgrid(np.linspace(0,300000,Z.shape[1]),np.linspace(0,200000,Z.shape[0]))
p1,p2 = 1006,1018

# this is almost the same as the original, although it will produce
# [p1, p1+2, ...] instead of `[Z.min()+n, Z.min()+n+2, ...]`
levels = np.arange(np.maximum(Z.min(),p1),np.minimum(Z.max(),p2),2)


#control
plt.figure()
CS = plt.contour(X, Y, Z, colors='b', linewidths=2, levels=levels)


#modified
plt.figure()
CS = plt.contour(X, Y, Z, colors='b', linewidths=2, levels=levels)

for level in CS.collections:
    for kp,path in reversed(list(enumerate(level.get_paths()))):
        # go in reversed order due to deletions!

        # include test for "smallness" of your choice here:
        # I'm using a simple estimation for the diameter based on the
        #    x and y diameter...
        verts = path.vertices # (N,2)-shape array of contour line coordinates
        diameter = np.max(verts.max(axis=0) - verts.min(axis=0))

        if diameter<15000: # threshold to be refined for your actual dimensions!
            del(level.get_paths()[kp])  # no remove() for Path objects:(

# this might be necessary on interactive sessions: redraw figure
plt.gcf().canvas.draw()
plt.show()

结果,原始(左)对新(右):


通过重采样进行平滑

我也决定解决平滑问题.我所能想到的就是对原始数据进行下采样,然后使用griddata(插值)再次进行上采样.下采样部分也可以通过插值来完成,尽管输入数据的小范围变化可能会使此问题不适当地解决.所以这是原始版本:

import scipy.interpolate as interp   #the new one

# assume you have X,Y,Z,levels defined as before

# start resampling stuff
dN = 10 # use every dN'th element of the gridded input data
my_slice = [slice(None,None,dN),slice(None,None,dN)]

# downsampled data
X2,Y2,Z2 = X[my_slice],Y[my_slice],Z[my_slice]
# same as X2 = X[::dN,::dN] etc.

# upsampling with griddata over original mesh
Zsmooth = interp.griddata(np.array([X2.ravel(),Y2.ravel()]).T,Z2.ravel(),(X,Y),method='cubic')

# plot
plt.figure()
CS = plt.contour(X, Y, Zsmooth, colors='b', linewidths=2, levels=levels)

您可以自由地使用用于插值的网格,在这种情况下,我只使用了原始网格,就象现在一样.您还可以尝试各种插值方法:默认的'linear'插值速度更快,但平滑度更低.

下采样(左)和上采样(右)后的结果:

当然,在进行重新采样后,您仍然应该应用小线去除算法,并且要记住,这会严重扭曲您的输入数据(因为如果不失真,那么它就不会很平滑).另外,请注意,由于在降采样步骤中使用了粗略的方法,我们在考虑中的区域的上/右边缘附近引入了一些缺失值.如果这是个问题,您应该考虑根据我之前提到的方法griddata进行下采样.<​​/p>

I am trying to plot contour lines of pressure level. I am using a netCDF file which contain the higher resolution data (ranges from 3 km to 27 km). Due to higher resolution data set, I get lot of pressure values which are not required to be plotted (rather I don't mind omitting certain contour line of insignificant values). I have written some plotting script based on the examples given in this link http://matplotlib.org/basemap/users/examples.html.

After plotting the image looks like this

From the image I have encircled the contours which are small and not required to be plotted. Also, I would like to plot all the contour lines smoother as mentioned in the above image. Overall I would like to get the contour image like this:-

Possible solution I think of are

  1. Find out the number of points required for plotting contour and mask/omit those lines if they are small in number.

or

  1. Find the area of the contour (as I want to omit only circled contour) and omit/mask those are smaller.

or

  1. Reduce the resolution (only contour) by increasing the distance to 50 km - 100 km.

I am able to successfully get the points using SO thread Python: find contour lines from matplotlib.pyplot.contour()

But I am not able to implement any of the suggested solution above using those points.

Any solution to implement the above suggested solution is really appreciated.

Edit:-

@ Andras Deak I used print 'diameter is ', diameter line just above del(level.get_paths()[kp]) line to check if the code filters out the required diameter. Here is the filterd messages when I set if diameter < 15000::

diameter is  9099.66295612
diameter is  13264.7838257
diameter is  445.574234531
diameter is  1618.74618114
diameter is  1512.58974168

However the resulting image does not have any effect. All look same as posed image above. I am pretty sure that I have saved the figure (after plotting the wind barbs).

Regarding the solution for reducing the resolution, plt.contour(x[::2,::2],y[::2,::2],mslp[::2,::2]) it works. I have to apply some filter to make the curve smooth.

Full working example code for removing lines:-

Here is the example code for your review

#!/usr/bin/env python
from netCDF4 import Dataset
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import numpy as np
import scipy.ndimage
from mpl_toolkits.basemap import interp
from mpl_toolkits.basemap import Basemap


# Set default map
west_lon = 68
east_lon = 93
south_lat = 7
north_lat = 23

nc = Dataset('ncfile.nc')
# Get this variable for later calucation
temps = nc.variables['T2']
time = 0  # We will take only first interval for this example
# Draw basemap
m = Basemap(projection='merc', llcrnrlat=south_lat, urcrnrlat=north_lat,
                llcrnrlon=west_lon, urcrnrlon=east_lon, resolution='l')
m.drawcoastlines()
m.drawcountries(linewidth=1.0)
# This sets the standard grid point structure at full resolution
x, y = m(nc.variables['XLONG'][0], nc.variables['XLAT'][0])

# Set figure margins
width = 10
height = 8

plt.figure(figsize=(width, height))
plt.rc("figure.subplot", left=.001)
plt.rc("figure.subplot", right=.999)
plt.rc("figure.subplot", bottom=.001)
plt.rc("figure.subplot", top=.999)

plt.figure(figsize=(width, height), frameon=False)

# Convert Surface Pressure to Mean Sea Level Pressure
stemps = temps[time] + 6.5 * nc.variables['HGT'][time] / 1000.
mslp = nc.variables['PSFC'][time] * np.exp(9.81 / (287.0 * stemps) * nc.variables['HGT'][time]) * 0.01 + (
    6.7 * nc.variables['HGT'][time] / 1000)

# Contour only at 2 hpa interval
level = []
for i in range(mslp.min(), mslp.max(), 1):
    if i % 2 == 0:
        if i >= 1006 and i <= 1018:
            level.append(i)

# Save mslp values to upload to SO thread
# np.savetxt('mslp.txt', mslp, fmt='%.14f', delimiter=',')

P = plt.contour(x, y, mslp, V=2, colors='b', linewidths=2, levels=level)


# Solution suggested by Andras Deak
for level in P.collections:
    for kp,path in enumerate(level.get_paths()):
        # include test for "smallness" of your choice here:
        # I'm using a simple estimation for the diameter based on the
        #    x and y diameter...
        verts = path.vertices # (N,2)-shape array of contour line coordinates
        diameter = np.max(verts.max(axis=0) - verts.min(axis=0))

        if diameter < 15000: # threshold to be refined for your actual dimensions!
            #print 'diameter is ', diameter
            del(level.get_paths()[kp])  # no remove() for Path objects:(
            #level.remove() # This does not work. produces ValueError: list.remove(x): x not in list

plt.gcf().canvas.draw()


plt.savefig('dummy', bbox_inches='tight')
plt.close()

After the plot is saved I get the same image

You can see that the lines are not removed yet. Here is the link to mslp array which we are trying to play with http://www.mediafire.com/download/7vi0mxqoe0y6pm9/mslp.txt

If you want x and y data which are being used in the above code, I can upload for your review.

Smooth line

You code to remove the smaller circles working perfectly. However the other question I have asked in the original post (smooth line) does not seems to work. I have used your code to slice the array to get minimal values and contoured it. I have used the following code to reduce the array size:-

slice = 15

CS = plt.contour(x[::slice,::slice],y[::slice,::slice],mslp[::slice,::slice], colors='b', linewidths=1, levels=levels)

The result is below.

After searching for few hours I found this SO thread having simmilar issue:-

Regridding regular netcdf data

But none of the solution provided over there works.The questions similar to mine above does not have proper solutions. If this issue is solved then the code is perfect and complete.

解决方案

General idea

Your question seems to have 2 very different halves: one about omitting small contours, and another one about smoothing the contour lines. The latter is simpler, since I can't really think of anything else other than decreasing the resolution of your contour() call, just like you said.

As for removing a few contour lines, here's a solution which is based on directly removing contour lines individually. You have to loop over the collections of the object returned by contour(), and for each element check each Path, and delete the ones you don't need. Redrawing the figure's canvas will get rid of the unnecessary lines:

# dummy example based on matplotlib.pyplot.clabel example:
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt

delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)


plt.figure()
CS = plt.contour(X, Y, Z)

for level in CS.collections:
    for kp,path in reversed(list(enumerate(level.get_paths()))):
        # go in reversed order due to deletions!

        # include test for "smallness" of your choice here:
        # I'm using a simple estimation for the diameter based on the
        #    x and y diameter...
        verts = path.vertices # (N,2)-shape array of contour line coordinates
        diameter = np.max(verts.max(axis=0) - verts.min(axis=0))

        if diameter<1: # threshold to be refined for your actual dimensions!
            del(level.get_paths()[kp])  # no remove() for Path objects:(

# this might be necessary on interactive sessions: redraw figure
plt.gcf().canvas.draw()

Here's the original(left) and the removed version(right) for a diameter threshold of 1 (note the little piece of the 0 level at the top):

Note that the top little line is removed while the huge cyan one in the middle doesn't, even though both correspond to the same collections element i.e. the same contour level. If we didn't want to allow this, we could've called CS.collections[k].remove(), which would probably be a much safer way of doing the same thing (but it wouldn't allow us to differentiate between multiple lines corresponding to the same contour level).

To show that fiddling around with the cut-off diameter works as expected, here's the result for a threshold of 2:

All in all it seems quite reasonable.


Your actual case

Since you've added your actual data, here's the application to your case. Note that you can directly generate the levels in a single line using np, which will almost give you the same result. The exact same can be achieved in 2 lines (generating an arange, then selecting those that fall between p1 and p2). Also, since you're setting levels in the call to contour, I believe the V=2 part of the function call has no effect.

import numpy as np
import matplotlib.pyplot as plt

# insert actual data here...
Z = np.loadtxt('mslp.txt',delimiter=',')
X,Y = np.meshgrid(np.linspace(0,300000,Z.shape[1]),np.linspace(0,200000,Z.shape[0]))
p1,p2 = 1006,1018

# this is almost the same as the original, although it will produce
# [p1, p1+2, ...] instead of `[Z.min()+n, Z.min()+n+2, ...]`
levels = np.arange(np.maximum(Z.min(),p1),np.minimum(Z.max(),p2),2)


#control
plt.figure()
CS = plt.contour(X, Y, Z, colors='b', linewidths=2, levels=levels)


#modified
plt.figure()
CS = plt.contour(X, Y, Z, colors='b', linewidths=2, levels=levels)

for level in CS.collections:
    for kp,path in reversed(list(enumerate(level.get_paths()))):
        # go in reversed order due to deletions!

        # include test for "smallness" of your choice here:
        # I'm using a simple estimation for the diameter based on the
        #    x and y diameter...
        verts = path.vertices # (N,2)-shape array of contour line coordinates
        diameter = np.max(verts.max(axis=0) - verts.min(axis=0))

        if diameter<15000: # threshold to be refined for your actual dimensions!
            del(level.get_paths()[kp])  # no remove() for Path objects:(

# this might be necessary on interactive sessions: redraw figure
plt.gcf().canvas.draw()
plt.show()

Results, original(left) vs new(right):


Smoothing by resampling

I've decided to tackle the smoothing problem as well. All I could come up with is downsampling your original data, then upsampling again using griddata (interpolation). The downsampling part could also be done with interpolation, although the small-scale variation in your input data might make this problem ill-posed. So here's the crude version:

import scipy.interpolate as interp   #the new one

# assume you have X,Y,Z,levels defined as before

# start resampling stuff
dN = 10 # use every dN'th element of the gridded input data
my_slice = [slice(None,None,dN),slice(None,None,dN)]

# downsampled data
X2,Y2,Z2 = X[my_slice],Y[my_slice],Z[my_slice]
# same as X2 = X[::dN,::dN] etc.

# upsampling with griddata over original mesh
Zsmooth = interp.griddata(np.array([X2.ravel(),Y2.ravel()]).T,Z2.ravel(),(X,Y),method='cubic')

# plot
plt.figure()
CS = plt.contour(X, Y, Zsmooth, colors='b', linewidths=2, levels=levels)

You can freely play around with the grids used for interpolation, in this case I just used the original mesh, as it was at hand. You can also play around with different kinds of interpolation: the default 'linear' one will be faster, but less smooth.

Result after downsampling(left) and upsampling(right):

Of course you should still apply the small-line-removal algorithm after this resampling business, and keep in mind that this heavily distorts your input data (since if it wasn't distorted, then it wouldn't be smooth). Also, note that due to the crude method used in the downsampling step, we introduce some missing values near the top/right edges of the region under consideraton. If this is a problem, you should consider doing the downsampling based on griddata as I've noted earlier.

这篇关于如何使用Matplotlib删除/忽略较小的轮廓线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆