source: trunk/UTIL/PYTHON/planetoplot.py @ 428

Last change on this file since 428 was 428, checked in by acolaitis, 13 years ago

PYTHON. Very simple modifications to allow for slices with LES files. Performance is not optimized and turbulence computations are not implemented. More to come in time.

  • Property svn:executable set to *
File size: 23.5 KB
Line 
1#######################
2##### PLANETOPLOT #####
3#######################
4
5### A. Spiga     -- LMD -- 06~09/2011 -- General building and mapping capabilities
6### T. Navarro   -- LMD -- 10~11/2011 -- Improved use for GCM and added sections + 1Dplot capabilities
7### A. Colaitis  -- LMD --    11/2011 -- Mostly minor improvements and inter-plot operation capabilities + zrecast interpolation for gcm
8### A. Spiga     -- LMD --    11/2011 -- Extended multivar subplot capabilities + cosmetic changes + general cleaning and tests
9
10def planetoplot (namefiles,\
11           level=0,\
12           vertmode=0,\
13           proj=None,\
14           back=None,\
15           target=None,
16           stride=3,\
17           var=None,\
18           colorb="def",\
19           winds=False,\
20           addchar=None,\
21           interv=[0,1],\
22           vmin=None,\
23           vmax=None,\
24           tile=False,\
25           zoom=None,\
26           display=True,\
27           hole=False,\
28           save="gui",\
29           anomaly=False,\
30           var2=None,\
31           ndiv=10,\
32           mult=1.,\
33           zetitle="fill",\
34           slon=None,\
35           slat=None,\
36           svert=None,\
37           stime=None,\
38           outputname=None,\
39           resolution=200,\
40           ope=None,\
41           fileref=None,\
42           minop=0.,\
43           maxop=0.,\
44           titleref="fill",\
45           invert_y=False,\
46           xaxis=[None,None],\
47           yaxis=[None,None],\
48           ylog=False,\
49           yintegral=False,\
50           blat=None,\
51           tsat=False,\
52           flagnolow=False):
53
54
55    ####################################################################################################################
56    ### Colorbars http://www.scipy.org/Cookbook/Matplotlib/Show_colormaps?action=AttachFile&do=get&target=colormaps3.png
57
58    #################################
59    ### Load librairies and functions
60    from netCDF4 import Dataset
61    from myplot import getcoord2d,define_proj,makeplotres,simplinterv,vectorfield,ptitle,latinterv,getproj,wrfinterv,dumpbdy,\
62                       fmtvar,definecolorvec,defcolorb,getprefix,putpoints,calculate_bounds,errormess,definesubplot,\
63                       zoomset,getcoorddef,getwinddef,whatkindfile,reducefield,bounds,getstralt,getfield,smooth,nolow,\
64                       getname,localtime,polarinterv,getsindex,define_axis,determineplot,readslices,bidimfind,getlschar,hole_bounds
65    from mymath import deg,max,min,mean,get_tsat,writeascii
66    import matplotlib as mpl
67    from matplotlib.pyplot import contour,contourf, subplot, figure, rcParams, savefig, colorbar, pcolor, show, plot, clabel, title
68    from matplotlib.cm import get_cmap
69    import numpy as np
70    from numpy.core.defchararray import find
71
72    ################################
73    ### Preliminary stuff
74    ################################
75    print "********************************************"
76    print "********** WELCOME TO PLANETOPLOT **********"
77    print "********************************************"
78    if not isinstance(namefiles, np.ndarray): namefiles = [namefiles]
79    if not isinstance(var, np.ndarray):       var = [var]
80
81    ################################
82    ### Which plot needs to be done?
83    ################################
84    nlon, nlat, nvert, ntime, mapmode, nslices = determineplot(slon, slat, svert, stime)
85    vlon = None ; vlat = None
86    if slon is not None: vlon = slon[0][0]
87    if slat is not None: vlat = slat[0][0]
88    if mapmode == 0:       winds=False
89    elif mapmode == 1:     
90        if svert is None:  svert = readslices(str(level)) ; nvert=1
91    zelen = len(namefiles)*len(var)
92    numplot = zelen*nslices
93    print "********** FILES, SLICES, VARS, TOTAL PLOTS: ", len(namefiles), nslices, len(var), numplot
94    if ope is not None:
95        if fileref is not None:       zelen = zelen + 2
96        elif "var" in ope:            zelen = zelen + 1
97    all_var  = [[]]*zelen ; all_var2  = [[]]*zelen ; all_title = [[]]*zelen ; all_varname = [[]]*zelen ; all_namefile = [[]]*zelen ; all_time = [[]]*zelen
98 
99    #################################################################################################
100    ### Loop over the files + vars initially separated by commas to be plotted on the same figure ###
101    #################################################################################################
102    k = 0 ; firstfile = True
103    for nnn in range(len(namefiles)):
104     for vvv in range(len(var)): 
105
106      print "********** LOOP..... THIS IS SUBPLOT NUMBER.....",k
107
108      ######################
109      ### Load NETCDF object
110      namefile = namefiles[nnn] ; print "********** THE NAMEFILE IS....", namefile
111      nc  = Dataset(namefile)
112
113      ##################################
114      ### Initial checks and definitions
115      ### ... TYPEFILE
116      typefile = whatkindfile(nc)                                 
117      if typefile in ['les']:
118          mapmode=0
119          winds=False
120      if firstfile: print "********** MAPMODE: ", mapmode
121      if firstfile:                 typefile0 = typefile
122      elif typefile != typefile0:   errormess("Not the same kind of files !", [typefile0, typefile])
123      ### ... VAR
124      varname=var[vvv]
125      print "********** THE VAR IS....",varname, var2
126      if varname not in nc.variables: varname = False
127      ### ... WINDS
128      if winds:                                                   
129         [uchar,vchar,metwind] = getwinddef(nc)             
130         if uchar == 'not found': winds = False
131      if not varname and not winds: errormess("please set at least winds or var",printvar=nc.variables)
132      ### ... COORDINATES, could be moved below
133      [lon2d,lat2d] = getcoorddef(nc)
134      ### ... PROJECTION
135      if ((proj == None) and (typefile not in ['les'])):   proj = getproj(nc)                 
136
137##########################################################
138      if typefile == "gcm":
139          lat = nc.variables["latitude"][:] ; lon = nc.variables["longitude"][:]
140          if "Time" in nc.variables:      time = nc.variables["Time"][:]
141          elif "time" in nc.variables:    time = nc.variables["time"][:]
142          else:                           errormess("no time axis found.")
143          vert = nc.variables["altitude"][:]
144      elif typefile in ['meso','mesoapi','geo','les']:
145          if vlon is not None or vlat is not None:   indices = bidimfind(lon2d,lat2d,vlon,vlat) ; print '********** INDICES: ', indices
146          if slon is not None: slon[0][0] = indices[0] ; slon[0][1] = indices[0]
147          if slat is not None: slat[0][0] = indices[1] ; slat[0][1] = indices[1]
148          if varname in ['PHTOT','W']:    vertdim='BOTTOM-TOP_PATCH_END_STAG'
149          else:                           vertdim='BOTTOM-TOP_PATCH_END_UNSTAG'
150          if varname in ['V']:  latdim='SOUTH-NORTH_PATCH_END_STAG'
151          else:                 latdim='SOUTH-NORTH_PATCH_END_UNSTAG'
152          if varname in ['U']:  londim='WEST-EAST_PATCH_END_STAG'
153          else:                 londim='WEST-EAST_PATCH_END_UNSTAG'
154          lon = np.arange(0,getattr(nc,londim),1) ; lat = np.arange(0,getattr(nc,latdim),1)
155          time = np.arange(0,len(nc.variables["Times"]),1)
156          if typefile in ['geo']:   vert = [0.] ; stime = readslices(str(0))
157          else:
158              if vertmode is None:  vertmode=0
159              if vertmode == 0:     vert = np.arange(0,getattr(nc,vertdim),1)
160              else:                 vert = nc.variables["vert"][:]
161       #if firstfile:
162       #   lat0 = lat
163       #elif len(lat0) != len(lat):
164       #   errormess("Not the same latitude lengths !", [len(lat0), len(lat)])
165       #elif sum((lat == lat0) == False) != 0:
166       #   errormess("Not the same latitudes !", [lat,lat0])
167       ## Faire d'autre checks sur les compatibilites entre fichiers!!
168##########################################################
169
170      if firstfile:
171         ##########################
172         ### Define plot boundaries
173         ### todo: possible areas in latinterv in argument (ex: "Far_South_Pole")
174         if proj in ["npstere","spstere"]: [wlon,wlat] = polarinterv(lon2d,lat2d)
175         elif proj in ["lcc","laea"]:      [wlon,wlat] = wrfinterv(lon2d,lat2d)
176         else:                             [wlon,wlat] = simplinterv(lon2d,lat2d)
177         if zoom:                          [wlon,wlat] = zoomset(wlon,wlat,zoom) 
178
179      all_varname[k] = varname
180      all_namefile[k] = namefile
181      all_time[k] = time
182      if var2: all_var2[k] = getfield(nc,var2)
183      ##### SPECIFIC
184      if varname in ["temp","t","T_nadir_nit","T_nadir_day","temp_day","temp_night"] and tsat:
185          tt=getfield(nc,varname) ; print "computing Tsat-T, I ASSUME Z-AXIS IS PRESSURE"
186          if type(tt).__name__=='MaskedArray':  tt.set_fill_value([np.NaN]) ; tinput=tt.filled()
187          else:                                 tinput=tt
188          all_var[k]=get_tsat(vert,tinput,zlon=lon,zlat=lat,zalt=vert,ztime=time)
189      else:
190      ##### GENERAL STUFF HERE
191          all_var[k] = getfield(nc,varname)
192      print "********** all_var[k].shape", all_var[k].shape
193      k += 1
194      firstfile = False
195      #### End of for namefile in namefiles
196
197    ##################################
198    ### Operation on files
199    if ope is not None:
200        print ope
201        if "var" not in ope:
202             if len(var) > 1: errormess("for this operation... please set only one var !")
203             if ope in ["-","+"]:
204                if fileref is not None:   all_var[k] = getfield(Dataset(fileref),all_varname[k-1]) ; all_varname[k] = all_varname[k-1] ; all_time[k] = all_time[k-1] ; all_namefile[k] = all_namefile[k-1]
205                else:                     errormess("fileref is missing!") 
206                if ope == "-":     all_var[k+1]= all_var[k-1] - all_var[k]
207                elif ope == "+":   all_var[k+1]= all_var[k-1] + all_var[k]
208                all_varname[k+1] = all_varname[k] ; all_time[k+1] = all_time[k] ; all_namefile[k+1] = all_namefile[k] ; numplot = numplot+2
209             elif ope in ["cat"]:
210                tab = all_var[0];k = 1
211                while k != len(namefiles)-1: 
212                    tab = np.append(tab,all_var[k],axis=0) ; k += 1
213                all_time[0] = np.arange(0,len(tab),1) ### AS: time reference is too simplistic, should be better
214                all_var[0] = np.array(tab) ; numplot = 1
215             else: errormess(ope+" : non-implemented operation. Check pp.py --help")
216        else:
217             if len(namefiles) > 1: errormess("for this operation... please set only one file !") 
218             if len(var) > 2:       errormess("not sure this works for more than 2 vars... please check.")
219             if   ope in ["div_var"]: all_var[k] = all_var[k-2] / all_var[k-1] ; insert = '_div_'
220             elif ope in ["mul_var"]: all_var[k] = all_var[k-2] * all_var[k-1] ; insert = '_mul_'
221             elif ope in ["add_var"]: all_var[k] = all_var[k-2] + all_var[k-1] ; insert = '_add_'
222             elif ope in ["sub_var"]: all_var[k] = all_var[k-2] - all_var[k-1] ; insert = '_sub_'
223             else:                    errormess(ope+" : non-implemented operation. Check pp.py --help")
224             numplot = numplot + 1 ; all_time[k] = all_time[k-1] ; all_namefile[k] = all_namefile[k-1]
225             all_varname[k] = all_varname[k-2] + insert + all_varname[k-1] 
226
227    ##################################
228    ### Open a figure and set subplots
229    fig = figure()
230    subv,subh = definesubplot( numplot, fig ) 
231 
232    #################################
233    ### Time loop for plotting device
234    nplot = 1;error = False
235    print "********************************************"
236    while error is False:
237       print "********** NPLOT", nplot
238     
239       ### General plot settings
240       if nplot > numplot: break
241       if numplot > 1:  subplot(subv,subh,nplot)
242
243       ### Map projection                   
244       if mapmode == 1:
245           m = define_proj(proj,wlon,wlat,back=back,blat=blat)
246           x, y = m(lon2d, lat2d)
247
248       ####################################################################
249       ## get all indexes to be taken into account for this subplot and then reduce field
250       ## We plot 1) all lon slices 2) all lat slices 3) all vert slices 4) all time slices and then go to the next slice
251       indexlon  = getsindex(slon,(nplot-1)%nlon,lon)
252       indexlat  = getsindex(slat,((nplot-1)//nlon)%nlat,lat)
253       indexvert = getsindex(svert,((nplot-1)//(nlon*nlat))%nvert,vert) 
254       if ope is not None:
255           if fileref is not None:      index_f = ((nplot-1)//(nlon*nlat*nvert*ntime))%(len(namefiles)+2)  ## OK only 1 var,  see test in the beginning
256           elif "var" in ope:           index_f = ((nplot-1)//(nlon*nlat*nvert*ntime))%(len(var)+1)        ## OK only 1 file, see test in the beginning
257       else:                            yeah = len(namefiles)*len(var) ; index_f = ((nplot-1)//(nlon*nlat*nvert*ntime))%yeah
258       time = all_time[index_f]
259       if stime is not None:
260           if stime[0][0] < 0:
261               if typefile in ['mesoapi','meso']:
262                   for i in range(len(time)):  time[i] = localtime ( interv[0]+time[i]*interv[1], 0.5*(wlon[0]+wlon[1]) )
263                   print "OK... WORKING WITH LOCAL TIMES"
264               else: errormess("local times not supported for GCM files. not too hard to modify the code though.")
265       if mapmode == 1 and stime is None:   indextime = 1
266       else:                                indextime = getsindex(stime,((nplot-1)//(nlon*nlat*nvert))%ntime,time)
267       ltst = None 
268       if typefile in ['mesoapi','meso'] and indextime is not None:  ltst = localtime ( interv[0]+indextime*interv[1], 0.5*(wlon[0]+wlon[1]) ) 
269       print "********** index lon, lat, vert, time ",indexlon,indexlat,indexvert,indextime
270       ####################################################################
271
272       ticks = ndiv + 1
273
274       #### Contour plot
275       if var2:
276           what_I_plot, error = reducefield(all_var2[index_f], d4=indextime, d1=indexlon, d2=indexlat , d3=indexvert, yint=yintegral, alt=vert)
277           #what_I_plot = what_I_plot*mult
278           if not error:
279              if mapmode == 1:
280                  if typefile in ['mesoapi','meso']:    what_I_plot = dumpbdy(what_I_plot,6)
281              zevmin, zevmax = calculate_bounds(what_I_plot)
282              zelevels = np.linspace(zevmin,zevmax,ticks) #20)
283              if var2 == 'HGT':  zelevels = np.arange(-10000.,30000.,2000.)
284              if mapmode == 0:
285                  what_I_plot, x, y = define_axis(lon,lat,vert,time,indexlon,indexlat,indexvert,\
286                        indextime,what_I_plot, len(all_var2[index_f].shape),vertmode)
287              ### If we plot a 2-D field
288              if len(what_I_plot.shape) is 2:
289                  cs = contour(x,y,what_I_plot, zelevels, colors='k', linewidths = 1 ) #0.33 colors='w' )# , alpha=0.5)
290#                  clabel(cs,#zelevels[::2],
291#                     inline=3,
292#                     fmt='%1.1e',
293#                     fontsize=7)
294              ### If we plot a 1-D field
295              elif len(what_I_plot.shape) is 1:
296                  plot(what_I_plot,x) 
297           else:
298              errormess("There is an error in reducing field !")
299
300       #### Shaded plot
301       varname = all_varname[index_f]
302       if varname:
303           what_I_plot, error = reducefield(all_var[index_f], d4=indextime, d1=indexlon, d2=indexlat , d3=indexvert , yint=yintegral, alt=vert, anomaly=anomaly)
304           what_I_plot = what_I_plot*mult
305           if not error: 
306               fvar = varname
307               if anomaly: fvar = 'anomaly'
308               ##### MAPMODE-SPECIFIC SETTINGS #####
309               if mapmode == 1:
310                   if typefile in ['mesoapi','meso']:    what_I_plot = dumpbdy(what_I_plot,6)
311               elif mapmode == 0:
312                   what_I_plot, x, y = define_axis(lon,lat,vert,time,indexlon,indexlat,indexvert,\
313                         indextime,what_I_plot, len(all_var[index_f].shape),vertmode)
314                   zxmin, zxmax = xaxis ; zymin, zymax = yaxis
315                   if zxmin is not None: mpl.pyplot.xlim(xmin=zxmin)
316                   if zxmax is not None: mpl.pyplot.xlim(xmax=zxmax)
317                   if zymin is not None: mpl.pyplot.ylim(ymin=zymin) 
318                   if zymax is not None: mpl.pyplot.ylim(ymax=zymax)
319                   if invert_y:     lima,limb = mpl.pyplot.ylim() ; mpl.pyplot.ylim(limb,lima)
320                   if ylog:         mpl.pyplot.semilogy()
321               if (fileref is not None) and (index_f is numplot-1):    zevmin, zevmax = calculate_bounds(what_I_plot,vmin=minop,vmax=maxop)
322               else:                                                   zevmin, zevmax = calculate_bounds(what_I_plot,vmin=vmin,vmax=vmax)
323               if colorb in ["def","nobar"]:   palette = get_cmap(name=defcolorb(fvar.upper()))
324               elif (fileref is not None) and (index_f is numplot-1): palette = get_cmap(name="RdBu_r")
325               else:                           palette = get_cmap(name=colorb)
326               ##### 2D field
327               if len(what_I_plot.shape) is 2:
328                 if hole: what_I_plot = hole_bounds(what_I_plot,zevmin,zevmax)
329                 else: what_I_plot = bounds(what_I_plot,zevmin,zevmax)
330                 if flagnolow: what_I_plot = nolow(what_I_plot)
331                 if not tile:
332                     #zelevels = np.linspace(zevmin*(1. + 1.e-7),zevmax*(1. - 1.e-7)) #,num=20)
333                     zelevels = np.linspace(zevmin,zevmax,num=ticks)
334                     print np.array(x).shape
335                     print np.array(y).shape
336                     print np.array(what_I_plot).shape
337
338                     if mapmode == 1:       m.contourf( x, y, what_I_plot, zelevels, cmap = palette)
339                     elif mapmode == 0:     contourf( x, y, what_I_plot, zelevels, cmap = palette)
340                 else:
341                     if mapmode == 1:       m.pcolor( x, y, what_I_plot, cmap = palette, vmin=zevmin, vmax=zevmax )
342                     elif mapmode == 0:     pcolor( x, y, what_I_plot, cmap = palette, vmin=zevmin, vmax=zevmax )
343                 if colorb != 'nobar':       
344                     if (fileref is not None) and (index_f is numplot-1):   daformat = "%.3f"
345                     else:                                                  daformat = fmtvar(fvar.upper())
346                     colorbar( fraction=0.05,pad=0.03,format=daformat,\
347                               ticks=np.linspace(zevmin,zevmax,num=min([ticks/2+1,20])),extend='neither',spacing='proportional' ) 
348               ##### 1D field
349               elif len(what_I_plot.shape) is 1:
350                 plot(x,what_I_plot)
351                 if save == 'txt':  writeascii(np.transpose(what_I_plot),'profile'+str(nplot)+'.txt')
352
353               #### Other cases: (maybe plot 3-D field one day or movie ??)
354               else:
355                 print "WARNING!!! ",len(what_I_plot.shape),"-D PLOT NOT SUPPORTED !!! dimensions: ",what_I_plot.shape
356                 errormess("Are you sure you did not forget to prescribe a dimension ?")
357           else:
358               errormess("There is an error in reducing field !")
359
360       ### Vector plot --- a adapter
361       if winds:
362           vecx, error = reducefield( getfield(nc,uchar), d4=indextime, d3=indexvert , yint=yintegral , alt=vert)
363           vecy, error = reducefield( getfield(nc,vchar), d4=indextime, d3=indexvert , yint=yintegral , alt=vert)
364           #what_I_plot, error = reducefield(all_var[index_f], d4=indextime, d1=indexlon, d2=indexlat , d3=indexvert )
365           if not error:
366               if typefile in ['mesoapi','meso']:   
367                   [vecx,vecy] = [dumpbdy(vecx,6,stag=uchar), dumpbdy(vecy,6,stag=vchar)]
368                   key = True
369               elif typefile in ['gcm']:           
370                   key = False
371               if metwind:  [vecx,vecy] = m.rotate_vector(vecx, vecy, lon2d, lat2d)
372               if varname == False:       colorvec = definecolorvec(back)
373               else:                  colorvec = definecolorvec(colorb)
374               vectorfield(vecx, vecy,\
375                          x, y, stride=stride, csmooth=2,\
376                          #scale=15., factor=300., color=colorvec, key=key)
377                          scale=20., factor=250., color=colorvec, key=key)
378                                            #200.         ## or csmooth=stride
379   
380       ### Next subplot
381       basename = getname(var=varname,winds=winds,anomaly=anomaly)
382       basename = basename + getstralt(nc,level) 
383       if typefile in ['mesoapi','meso']:
384            if slon is not None: basename = basename + "_lon_" + str(int(lon2d[indices[1],indices[0]]))
385            if slat is not None: basename = basename + "_lat_" + str(int(lat2d[indices[1],indices[0]]))
386            plottitle = basename
387            if addchar: 
388                [addchar,gogol,gogol2] = getlschar ( all_namefile[index_f] )
389                plottitle = plottitle + addchar + "_LT"
390            else:        plottitle = plottitle + "_LT"
391            if ltst is not None: plottitle = plottitle + str(ltst)
392       else:
393            if fileref is not None:
394                if index_f is numplot-1:     plottitle = basename+' '+"fig(1) "+ope+" fig(2)"
395                elif index_f is numplot-2:   plottitle = basename+' '+fileref
396                else:                        plottitle = basename+' '+namefiles[0]#index_f]
397            else:                            plottitle = basename+' '+namefiles[0]#index_f]
398       if mult != 1:                         plottitle = str(mult) + "*" + plottitle
399       if zetitle != "fill":                 
400          plottitle = zetitle
401          if titleref is "fill":             titleref=zetitle
402          if fileref is not None:
403             if index_f is numplot-2:        plottitle = titleref
404             if index_f is numplot-1:        plottitle = "fig(1) "+ope+" fig(2)"
405#       if indexlon is not None:      plottitle = plottitle + " lon: " + str(min(lon[indexlon])) +" "+ str(max(lon[indexlon]))
406#       if indexlat is not None:      plottitle = plottitle + " lat: " + str(min(lat[indexlat])) +" "+ str(max(lat[indexlat]))
407#       if indexvert is not None:     plottitle = plottitle + " vert: " + str(min(vert[indexvert])) +" "+ str(max(vert[indexvert]))
408#       if indextime is not None:     plottitle = plottitle + " time: " + str(min(time[indextime])) +" "+ str(max(time[indextime]))
409       title( plottitle )
410       if nplot >= numplot: error = True
411       nplot += 1
412
413 
414
415
416
417
418
419     
420    ##########################################################################
421    ### Save the figure in a file in the data folder or an user-defined folder
422    if outputname is None:
423       if typefile in ['meso','mesoapi']:   prefix = getprefix(nc)
424       elif typefile in ['gcm']:            prefix = 'LMD_GCM_'
425       else:                                prefix = ''
426    ###
427       zeplot = prefix + basename
428       if addchar:         zeplot = zeplot + addchar
429       if numplot <= 0:    zeplot = zeplot + "_LT"+str(abs(numplot))
430    ###
431       if not target:      zeplot = namefile[0:find(namefile,'wrfout')] + zeplot
432       else:               zeplot = target + "/" + zeplot 
433    ###
434    else:
435       zeplot=outputname
436
437    pad_inches_value = 0.35
438    print "********** SAVE ", save
439    if save == 'png': 
440        if display: makeplotres(zeplot,res=100.,pad_inches_value=pad_inches_value) #,erase=True)  ## a miniature
441        makeplotres(zeplot,res=resolution,pad_inches_value=pad_inches_value,disp=False)
442    elif save in ['eps','svg','pdf']:     makeplotres(zeplot,pad_inches_value=pad_inches_value,disp=False,ext=save)
443    elif save == 'gui':                   show()
444    elif save == 'txt':                   print "Saved results in txt file." 
445    else: 
446         print "INFO: save mode not supported. using gui instead."
447         show()
448
449    ###############
450    ### Now the end
451    return zeplot
Note: See TracBrowser for help on using the repository browser.