如何使用matplotlib

时间:2016-02-25 16:50:49

标签: python matplotlib plot matplotlib-basemap

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

绘图后图像看起来像这样

Contour Plot

从图像中我已经包围了小而不需要绘制的轮廓。此外,我想如上图所示,将所有contour线条绘制得更加平滑。总的来说,我想得到这样的轮廓图像: -

Internet Image

我想到的可能解决方案是

  1. 找出绘制轮廓和遮罩所需的点数/如果它们的数量很少,则忽略这些线。
    1. 找到轮廓区域(因为我只想省略带圆圈的轮廓)并省略/掩盖那些较小的轮廓。
      1. 通过将距离增加到50 km - 100 km来降低分辨率(仅限轮廓)。
      2. 我能够使用SO线程Python: find contour lines from matplotlib.pyplot.contour()

        成功获得积分

        但我无法使用这些要点实现上述任何建议的解决方案。

        非常感谢任何实施上述建议解决方案的解决方案。

        修改: -

        @Andras Deak 我在print 'diameter is ', diameter行上方使用del(level.get_paths()[kp])行来检查代码是否过滤掉了所需的直径。这是我设置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()
        

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

        Pic of working example

        您可以看到线条尚未移除。以下是我们尝试使用http://www.mediafire.com/download/7vi0mxqoe0y6pm9/mslp.txt

        mslp数组的链接

        如果您想要上述代码中使用的xy数据,我可以上传以供您查看。

        平滑线

        您可以编码以移除较小的圆圈。然而,我在原帖(平滑线)中提出的另一个问题似乎不起作用。我已经使用您的代码对数组进行切片以获得最小值并对其进行轮廓分析。我使用以下代码来减少数组大小: -

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

        结果如下。

        irregular line

        搜索了几个小时后,我发现这个SO线程有类似的问题: -

        Regridding regular netcdf data

        但是那里提供的解决方案都没有起作用。上面类似于我的问题没有适当的解决方案。如果这个问题得到解决,那么代码就是完美而完整的。

2 个答案:

答案 0 :(得分:13)

一般想法

你的问题似乎有两个非常不同的一半:一个是关于省略小轮廓,另一个关于平滑轮廓线。后者更简单,因为除了降低contour()电话的分辨率之外,我无法想到其他任何事情,就像你说的那样。

至于去除一些轮廓线,这是一个基于直接去除轮廓线的解决方案。您必须遍历collections返回的对象的contour(),并为每个元素检查每个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级的小部分):

original for reference remove smaller than d=1

注意,顶部的小线被移除,而中间的巨大青色线没有,即使两者都对应于相同的collections元素,即相同的轮廓水平。如果我们不想允许这样做,我们可以调用CS.collections[k].remove(),这可能是一种更安全的做同样事情的方式(但它不允许我们区分对应于相同的轮廓水平)。

为了显示切割直径的摆动工作符合预期,这是阈值2的结果:

result with threshold of 2

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

您的实际案例

由于您已添加了实际数据,因此这是您的案例的应用程序。请注意,您可以使用level在一行中直接生成np,这几乎可以为您提供相同的结果。完全相同的可以在2行中生成(生成arange,然后选择介于p1p2之间的那些。此外,由于您在调用levels时设置了contour,我相信函数调用的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()

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

before after

通过重新采样平滑

我也决定解决平滑问题。我能想到的是对原始数据进行下采样,然后使用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'一个会更快,但不太流畅。

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

after downsample after upsample

当然,在重新采样业务之后你仍然应该使用小线删除算法,并记住这会严重扭曲你的输入数据(因为如果它没有失真,那么它就不会顺利)。此外,请注意,由于下采样步骤中使用的粗略方法,我们在考虑周围的区域的顶部/右边缘附近引入了一些缺失值。如果这是一个问题,您应该考虑根据我之前提到的griddata进行下采样。<​​/ p>

答案 1 :(得分:0)

这是一个非常糟糕的解决方案,但它是我唯一提出的解决方案。使用您链接到的this solution中的get_contour_verts函数,可能使用matplotlib._cntr模块,以便最初不会绘制任何内容。这将为您提供轮廓线,截面,顶点等的列表。然后,您必须浏览该列表并pop您不想要的轮廓。例如,您可以通过计算最小直径来完成此操作;如果点之间的最大距离小于一些截止值,则抛弃它。

这将为您提供LineCollection个对象的列表。 现在如果您创建了FigureAxes实例,则可以使用Axes.add_collection添加列表中的所有LineCollection

我很快检查了这一点,但似乎有效。如果我有机会,我会以最低限度的工作示例回来。希望它有所帮助!

编辑:这是一个基本想法的MWE。我对plt._cntr.Cntr并不熟悉,所以我最终使用plt.contour来获取初始轮廓对象。结果,你最终得到两个数字;你只需要关闭第一个。您可以使用任何有效的函数替换checkDiameter。我认为你可以将线段转换为Polygon并计算区域,但你必须自己解决这个问题。如果您遇到此代码的问题,请告诉我,但它至少对我有用。

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

def checkDiameter(seg, tol=.3):
    # Function for screening line segments. NB: Not actually a proper diameter.
    diam = (seg[:,0].max() - seg[:,0].min(),
            seg[:,1].max() - seg[:,1].min())
    return not (diam[0] < tol or diam[1] < tol)

# Create testing data
x = np.linspace(-1,1, 21)
xx, yy = np.meshgrid(x,x)
z = np.exp(-(xx**2 + .5*yy**2))

# Original plot with plt.contour
fig0, ax0 = plt.subplots()
# Make sure this contour object actually has a tiny contour to remove
cntrObj = ax0.contour(xx,yy,z, levels=[.2,.4,.6,.8,.9,.95,.99,.999])

# Primary loop: Copy contours into a new LineCollection
lineNew = list()
for lineOriginal in cntrObj.collections:
    # Get properties of the original LineCollection
    segments = lineOriginal.get_segments()
    propDict = lineOriginal.properties()
    propDict = {key: value for (key,value) in propDict.items()
        if key in ['linewidth','color','linestyle']}  # Whatever parameters you want to carry over
    # Filter out the lines with small diameters
    segments = [seg for seg in segments if checkDiameter(seg)]
    # Create new LineCollection out of the OK segments
    if len(segments) > 0:
        lineNew.append(mpl.collections.LineCollection(segments, **propDict))

# Make new plot with only these line collections; display results
fig1, ax1 = plt.subplots()
ax1.set_xlim(ax0.get_xlim())
ax1.set_ylim(ax0.get_ylim())
for line in lineNew:
    ax1.add_collection(line)
plt.show()

FYI propDict的位只是自动从原始图中引入一些线属性。但是,您不能同时使用整个字典。首先,它包含旧情节的线段,但您可以将它们换成新的线段。但其次,它似乎包含许多相互冲突的参数:多个线宽,面部颜色等。{key for key in propDict if I want key}解决方法是我绕过它的方法,但我确定其他人可以做得更干净。