def test_legs_col_arr_one_col_grey_txt(self): #one color as a text string blue_mapping = legs.PalObj(color_arr = 'grey_175') data = [0.00, 1.00] rgb_img = blue_mapping.to_rgb(data) # data value color ans = [[175, 175, 175], # 1.00 grey 175 [175, 175, 175]] # 0.66 grey 175 self.assertEqual(rgb_img.tolist(),ans)
def test_legs_col_arr_one_col_txt(self): #one color as a text string blue_mapping = legs.PalObj(color_arr = 'blue') data = [0.00, 1.00] rgb_img = blue_mapping.to_rgb(data) # data value color ans = [[169, 222, 255], # 1.00 light blue [ 0, 81, 237]] # 0.66 dark_blue self.assertEqual(rgb_img.tolist(),ans)
def test_legs_col_arr_one_col_txt_solid_supplied(self): #or any color you like by passing RGB values to color_arr orange_mapping = legs.PalObj(color_arr = [255, 194, 124], solid='supplied') #light orange data = [0.00, 1.00] rgb_img = orange_mapping.to_rgb(data) # data value color ans = [[255, 194, 124], # 0.00 light orange [255, 194, 124]] # 1.00 light orange self.assertEqual(rgb_img.tolist(),ans)
def test_legs_range(self): #one solution is to adjust range of palette so that is encompasses the data black_white_mapping = legs.PalObj(range_arr=[0.,1.1]) data = [0.000, 0.366, 0.733, 1.100] rgb_img = black_white_mapping.to_rgb(data) # data value color ans = [[255, 255, 255], # 0.000 white [170, 170, 170], # 0.366 grey [ 85, 85, 85], # 0.733 grey [ 0, 0, 0]] # 1.100 black self.assertEqual(rgb_img.tolist(),ans)
def test_legs_no_args(self): #default map is a black and white palette for data values in the interval [0,1] black_white_mapping = legs.PalObj() data = [0.00, 0.33, 0.66, 1.00] rgb_img = black_white_mapping.to_rgb(data) # data value color ans = [[255, 255, 255], # 0.00 white [170, 170, 170], # 0.33 grey [ 86, 86, 86], # 0.66 grey [ 0, 0, 0]] # 1.00 black self.assertEqual(rgb_img.tolist(),ans)
def test_legs_col_arr_two_col_txt_solid_light(self): #solid can also be set to col_light orange_blue_mapping = legs.PalObj(color_arr = ['orange','blue'], solid='col_light') data = [0.00, 0.33, 0.66, 1.00] rgb_img = orange_blue_mapping.to_rgb(data) # data value color ans = [[255, 194, 124], # 0.00 light orange [255, 194, 124], # 0.33 light orange [169, 222, 255], # 0.66 light blue [169, 222, 255]] # 1.00 light blue self.assertEqual(rgb_img.tolist(),ans)
def test_legs_col_arr_two_col_txt(self): #any number of colors can be specified orange_blue_mapping = legs.PalObj(color_arr = ['orange','blue']) data = [0.00, 0.33, 0.66, 1.00] rgb_img = orange_blue_mapping.to_rgb(data) # data value color ans = [[255, 194, 124], # 0.00 light orange [255, 122, 42], # 0.33 dark orange [114, 176, 249], # 0.66 light blue [ 0, 81, 237]] # 1.00 dark_blue self.assertEqual(rgb_img.tolist(),ans)
def test_legs_range_over_high_under_low(self): #end points can be handled separately #this is done with the "over_high" and "under_low" keywords black_white_mapping = legs.PalObj(range_arr=[0.,1],over_high='extend',under_low='exact') data = [0.000, 0.366, 0.733, 1.100] # 1.1 is above [0,1] range of the palette rgb_img = black_white_mapping.to_rgb(data) #No error is raised # data value color ans = [[255, 255, 255], # 0.000 white [161, 161, 161], # 0.366 grey [ 68, 68, 68], # 0.733 grey [ 0, 0, 0]] # 1.100 black self.assertEqual(rgb_img.tolist(),ans)
def test_legs_range_over_under(self): #another possibility is to extend the first and last color of the palette beyond the range of the palette #this is done with the "over_under" keyword black_white_mapping = legs.PalObj(range_arr=[0.,1],over_under='extend') data = [0.000, 0.366, 0.733, 1.100]# 1.1 is above [0,1] range of the palette rgb_img = black_white_mapping.to_rgb(data) # data value color ans = [[255, 255, 255], # 0.000 white [161, 161, 161], # 0.366 grey [ 68, 68, 68], # 0.733 grey [ 0, 0, 0]] # 1.100 black self.assertEqual(rgb_img.tolist(),ans)
def test_legs_col_arr_two_col_txt_solid_dark(self): #for categorical color palettes we need conly one color per legs #this is done with the "solid" keyword orange_blue_mapping = legs.PalObj(color_arr = ['orange','blue'], solid='col_dark') data = [0.00, 0.33, 0.66, 1.00] rgb_img = orange_blue_mapping.to_rgb(data) # data value color ans = [[255, 86, 0], # 0.00 dark orange [255, 86, 0], # 0.33 dark orange [ 0, 81, 237], # 0.66 dark blue [ 0, 81, 237]] # 1.00 dark blue self.assertEqual(rgb_img.tolist(),ans)
def test_legs_col_arr_two_col_txt_dark_pos(self): #by default, dark colors are associated to high data values #this can be changed for all color legs with the "dark_pos" keyword orange_blue_mapping = legs.PalObj(color_arr = ['orange','blue'], dark_pos='low') data = [0.00, 0.33, 0.66, 1.00] rgb_img = orange_blue_mapping.to_rgb(data) # data value color ans = [[255, 86, 0], # 0.00 dark orange [255, 157, 81], # 0.33 light orange [ 54, 126, 242], # 0.66 dark_blue [169, 222, 255]] # 1.00 light blue self.assertEqual(rgb_img.tolist(),ans)
def test_legs_n_col(self): #keyword n_col makes for easy semi-continuous palettes #default color order: ['brown','blue','green','orange','red','pink','purple','yellow'] two_color_mapping = legs.PalObj(n_col=2) data = [0.00, 0.33, 0.66, 1.00] rgb_img = two_color_mapping.to_rgb(data) # data value color ans = [[223, 215, 208], # 0.00 light brown [ 139, 110, 83], # 0.33 dark brown [ 114, 176, 249], # 0.66 light blue [ 0, 81, 237]] # 1.00 dark blue self.assertEqual(rgb_img.tolist(),ans)
def test_legs_col_arr_one_col_txt(self): #if you don't like the default order, colors can be speficied directly #see color dictionary in col_utils for the list of available colors orange_mapping = legs.PalObj(color_arr = 'orange') data = [0.00, 0.33, 0.66, 1.00] rgb_img = orange_mapping.to_rgb(data) # data value color ans = [[255, 194, 124], # 0.00 light orange [255, 158, 83], # 0.33 [255, 122, 42], # 0.66 [255, 86, 0]] # 1.00 dark orange self.assertEqual(rgb_img.tolist(),ans)
def test_legs_col_arr_two_col_rgb(self): #for exact color control, RGB values can be specified directly orange_blue_mapping = legs.PalObj(color_arr = [[[255, 194, 124],[255, 86, 0]], #[light orange],[dark orange] [[169, 222, 255],[000, 81, 237]]]) #[light blue] ,[dark blue ] data = [0.00, 0.33, 0.66, 1.00] rgb_img = orange_blue_mapping.to_rgb(data) # data value color ans = [[255, 194, 124], # 0.00 light orange [255, 122, 42], # 0.33 dark orange [114, 176, 249], # 0.66 light blue [ 0, 81, 237]] # 1.00 dark_blue self.assertEqual(rgb_img.tolist(),ans)
def test_legs_col_arr_two_col_txt_dark_pos_diff(self): #for diverging palette #we need dark color to be associated with high and low values #for different color legs orange_blue_mapping = legs.PalObj(color_arr = ['orange','blue'], dark_pos=['low','high']) data = [0.00, 0.33, 0.66, 1.00] rgb_img = orange_blue_mapping.to_rgb(data) # data value color ans = [[255, 86, 0], # 0.00 dark orange [255, 157, 81], # 0.33 light orange [114, 176, 249], # 0.66 light blue [ 0, 81, 237]] # 1.00 dark_blue self.assertEqual(rgb_img.tolist(),ans)
def test_legs_col_excep_1(self): #data value with special meaning (nodata, zero, etc) can be assigned special colors #using exceptions black_white_mapping = legs.PalObj(range_arr=[1,6],excep_val=-999.) data = [1.,2,3,-999,5,6] rgb_img = black_white_mapping.to_rgb(data) # data value color ans = [[255, 255, 255], # 1 white [204, 204, 204], # 2 grey [153, 153, 153], # 3 grey [158, 0, 13], # -999 -> special value in red [ 51, 51, 51], # 5 grey [ 0, 0, 0]] # 6 black self.assertEqual(rgb_img.tolist(),ans)
def test_legs_excep_2(self): black_white_mapping = legs.PalObj(range_arr=[1,6],excep_val=[0, -999.], excep_tol=[.7, 1e-3], excep_col=['dark_blue','dark_red']) data = [1,-.5, 0, .5, 2, 3, 4, -999, 5, 6] rgb_img = black_white_mapping.to_rgb(data) # data value color ans = [[255, 255, 255], # 1 white [ 0, 81, 237], # .-5 -> special value in dark_blue [ 0, 81, 237], # 0 -> special value in dark_blue [ 0, 81, 237], # .5 -> special value in dark_blue [204, 204, 204], # 2 grey [153, 153, 153], # 3 grey [102, 102, 102], # 4 grey [158, 0, 13], # -999 -> special value in dark_blue [ 51, 51, 51], # 5 grey [ 0, 0, 0]] # 6 black self.assertEqual(rgb_img.tolist(),ans)
def test_general_lam_projection(self): import os, inspect import pickle import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature import domutils import domutils._py_tools as py_tools # In your scripts use something like : import domutils.legs as legs import domutils.geo_tools as geo_tools #recover previously prepared data domutils_dir = os.path.dirname(domutils.__file__) package_dir = os.path.dirname(domutils_dir) + '/' source_file = package_dir + '/test_data/pal_demo_data.pickle' with open(source_file, 'rb') as f: data_dict = pickle.load(f) longitudes = data_dict['longitudes'] #2D longitudes [deg] latitudes = data_dict['latitudes'] #2D latitudes [deg] ground_mask = data_dict[ 'groundMask'] #2D land fraction [0-1]; 1 = all land terrain_height = data_dict[ 'terrainHeight'] #2D terrain height of model [m ASL] #flag non-terrain (ocean and lakes) as -3333. inds = np.asarray((ground_mask.ravel() <= .01)).nonzero() if inds[0].size != 0: terrain_height.flat[inds] = -3333. #missing value missing = -9999. #pixel density of image to plot ratio = 0.8 hpix = 600. #number of horizontal pixels vpix = ratio * hpix #number of vertical pixels img_res = (int(hpix), int(vpix)) ##define Albers projection and extend of map #Obtained through trial and error for good fit of the mdel grid being plotted proj_aea = ccrs.AlbersEqualArea(central_longitude=-94., central_latitude=35., standard_parallels=(30., 40.)) map_extent = [-104.8, -75.2, 27.8, 48.5] #point density for figure mpl.rcParams['figure.dpi'] = 400 #larger characters mpl.rcParams.update({'font.size': 15}) #instantiate figure fig = plt.figure(figsize=(7.5, 6.)) #instantiate object to handle geographical projection of data proj_inds = geo_tools.ProjInds(src_lon=longitudes, src_lat=latitudes, extent=map_extent, dest_crs=proj_aea, image_res=img_res, missing=missing) #axes for this plot ax = fig.add_axes([.01, .1, .8, .8], projection=proj_aea) ax.set_extent(map_extent) # Set up colormapping object # # Two color segments for this palette red_green = [ [[227, 209, 130], [20, 89, 69]], # bottom color leg : yellow , dark green [[227, 209, 130], [140, 10, 10]] ] # top color leg : yellow , dark red map_terrain = legs.PalObj( range_arr=[0., 750, 1500.], color_arr=red_green, dark_pos=['low', 'high'], excep_val=[-3333., missing], excep_col=[[170, 200, 250], [120, 120, 120]], #blue , grey_120 over_high='extend') #geographical projection of data into axes space proj_data = proj_inds.project_data(terrain_height) #plot data & palette map_terrain.plot_data(ax=ax, data=proj_data, zorder=0, palette='right', pal_units='[meters]', pal_format='{:4.0f}') #palette options #add political boundaries ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=0.5, edgecolor='0.2', zorder=1) #plot border and mask everything outside model domain proj_inds.plot_border(ax, mask_outside=True, linewidth=.5) #uncomment to save figure output_dir = package_dir + 'test_results/' if not os.path.isdir(output_dir): os.mkdir(output_dir) image_name = output_dir + 'test_general_lam_projection.svg' plt.savefig(image_name) plt.close(fig) print('saved: ' + image_name) #compare image with saved reference #copy reference image to testdir reference_image = package_dir + '/test_data/_static/' + os.path.basename( image_name) images_are_similar = py_tools.render_similarly(image_name, reference_image) #test fails if images are not similar self.assertEqual(images_are_similar, True)
def test_no_extent_in_cartopy_projection(self): ''' make sure ProjInds works with projections requiring no extent ''' import os import numpy as np import cartopy.crs as ccrs import cartopy.feature as cfeature import matplotlib.pyplot as plt import domutils import domutils.legs as legs import domutils.geo_tools as geo_tools import domutils._py_tools as py_tools regular_lons = [[-100., -100, -100], [-90., -90, -90], [-80., -80, -80]] regular_lats = [[30, 40, 50], [30, 40, 50], [30, 40, 50]] data_vals = [[6.5, 3.5, .5], [7.5, 4.5, 1.5], [8.5, 5.5, 2.5]] missing = -9999. image_ratio = .5 rec_w = 6.4 #inches! rec_h = image_ratio * rec_w #inches! grid_w_pts = 2000. image_res = [grid_w_pts, image_ratio * grid_w_pts] #100 x 50 #cartopy projection with no extent proj_rob = ccrs.Robinson() #instantiate object to handle geographical projection of data # onto geoAxes with this specific crs ProjInds = geo_tools.ProjInds(src_lon=regular_lons, src_lat=regular_lats, dest_crs=proj_rob, image_res=image_res, extend_x=False, extend_y=False) #geographical projection of data into axes space projected_data = ProjInds.project_data(data_vals) #plot data to make sure it works #the image that is generated can be looked at to insure proper functionning of the test #it is not currently tested color_map = legs.PalObj(range_arr=[0., 9.], color_arr=[ 'brown', 'blue', 'green', 'orange', 'red', 'pink', 'purple', 'yellow', 'b_w' ], excep_val=missing, excep_col='grey_220') fig_w = 9. fig_h = image_ratio * fig_w fig = plt.figure(figsize=(fig_w, fig_h)) pos = [.1, .1, rec_w / fig_w, rec_h / fig_h] ax = fig.add_axes(pos, projection=proj_rob) x1, x2, y1, y2 = ax.get_extent() ax.outline_patch.set_linewidth(.3) projected_rgb = color_map.to_rgb(projected_data) ax.imshow(projected_rgb, interpolation='nearest', aspect='auto', extent=[x1, x2, y1, y2], origin='upper') ax.coastlines(resolution='110m', linewidth=0.3, edgecolor='0.3', zorder=10) color_map.plot_palette(data_ax=ax) domutils_dir = os.path.dirname(domutils.__file__) package_dir = os.path.dirname(domutils_dir) test_results_dir = package_dir + '/test_results/' if not os.path.isdir(test_results_dir): os.mkdir(test_results_dir) svg_name = test_results_dir + '/test_no_extent_in_cartopy_projection.svg' plt.savefig(svg_name, dpi=500) plt.close(fig) print('saved: ' + svg_name) #compare image with saved reference #copy reference image to testdir reference_image = package_dir + '/test_data/_static/' + os.path.basename( svg_name) images_are_similar = py_tools.render_similarly(svg_name, reference_image) #test fails if images are not similar self.assertEqual(images_are_similar, True)
def main(): #recover previously prepared data currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname(currentdir) #directory where this package lives source_file = parentdir + '/test_data/pal_demo_data.pickle' with open(source_file, 'rb') as f: data_dict = pickle.load(f) longitudes = data_dict['longitudes'] #2D longitudes [deg] latitudes = data_dict['latitudes'] #2D latitudes [deg] ground_mask = data_dict['groundMask'] #2D land fraction [0-1]; 1 = all land terrain_height = data_dict['terrainHeight'] #2D terrain height of model [m ASL] #flag non-terrain (ocean and lakes) as -3333. inds = np.asarray( (ground_mask.ravel() <= .01) ).nonzero() if inds[0].size != 0: terrain_height.flat[inds] = -3333. #missing value missing = -9999. #pixel density of image to plot ratio = 0.8 hpix = 600. #number of horizontal pixels E-W vpix = ratio*hpix #number of vertical pixels S-N img_res = (int(hpix),int(vpix)) ##define Albers projection and extend of map #Obtained through trial and error for good fit of the mdel grid being plotted proj_aea = ccrs.AlbersEqualArea(central_longitude=-94., central_latitude=35., standard_parallels=(30.,40.)) map_extent=[-104.8,-75.2,27.8,48.5] #point density for figure mpl.rcParams['figure.dpi'] = 400 #larger characters mpl.rcParams.update({'font.size': 15}) #instantiate figure fig = plt.figure(figsize=(7.5,6.)) #instantiate object to handle geographical projection of data proj_inds = geo_tools.ProjInds(src_lon=longitudes, src_lat=latitudes, extent=map_extent, dest_crs=proj_aea, image_res=img_res, missing=missing) #axes for this plot ax = fig.add_axes([.01,.1,.8,.8], projection=proj_aea) ax.set_extent(map_extent) # Set up colormapping object # # Two color segments for this palette red_green = [[[227,209,130],[ 20, 89, 69]], # bottom color leg : yellow , dark green [[227,209,130],[140, 10, 10]]] # top color leg : yellow , dark red map_terrain = legs.PalObj(range_arr=[0., 750, 1500.], color_arr=red_green, dark_pos=['low','high'], excep_val=[-3333. ,missing], excep_col=[[170,200,250],[120,120,120]], #blue , grey_120 over_high='extend') #geographical projection of data into axes space proj_data = proj_inds.project_data(terrain_height) #plot data & palette map_terrain.plot_data(ax=ax,data=proj_data, zorder=0, palette='right', pal_units='[meters]', pal_format='{:4.0f}') #palette options #add political boundaries ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=0.5, edgecolor='0.2',zorder=1) #plot border and mask everything outside model domain proj_inds.plot_border(ax, mask_outside=True, linewidth=.5)
def plot_rdpr_rdqi(fst_file: str = None, this_date: Any = None, fig_dir: Optional[str] = None, fig_format: Optional[str] = 'gif', args=None): """ plot RDPR and RDQI from a rpn "standard" file Data needs to be at correct valid time If the file does not exist, the image will display "Non existing file" Args: fst_file: full path of standard file to read this_date: validity time of data to plot fig_dir: directory where figures will be written Returns: Nothing, only plot a figure """ import os from os import linesep as newline import datetime import logging import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt from packaging import version import cartopy import cartopy.crs as ccrs import cartopy.feature as cfeature import domutils.legs as legs import domcmc.fst_tools as fst_tools import domutils.geo_tools as geo_tools import domutils._py_tools as dpy #logging logger = logging.getLogger(__name__) #use provided data path for cartiopy shapefiles #TODO there has got to be a better way to do this! if args is not None: if hasattr(args, 'cartopy_dir'): if args.cartopy_dir is not None: cartopy.config['pre_existing_data_dir'] = args.cartopy_dir #Read data logger.info('Reading RDPR and RDQI from: ' + fst_file) pr_dict = fst_tools.get_data(file_name=fst_file, var_name='RDPR', datev=this_date, latlon=True) qi_dict = fst_tools.get_data(file_name=fst_file, var_name='RDQI', datev=this_date) #value assigned to missing data missing = -9999. #make figure directory if it does not exist dpy.parallel_mkdir(fig_dir) #setup figure properties ratio = 0.5 fig_name_recipe = '%Y%m%d_%H%M.svg' # all sizes are inches for consistency with matplotlib rec_w = 6. # Horizontal size of a panel /2.54 for dimensions in cm rec_h = ratio * rec_w # Vertical size of a panel sp_w = .1 # horizontal space between panels sp_h = .5 # vertical space between panels pal_sp = .1 # spavce between panel and palette pal_w = .25 # width of palette tit_h = 1. # height of title xp = .04 # axes relative x position of image caption yp = 1.05 # axes relative y position of image caption dpi = 500 # density of pixel for figure #size of figure fig_w = 3. + 2 * (sp_w + rec_w + pal_w + pal_sp) fig_h = 2. * sp_h + sp_h + rec_h + tit_h #normalize all dimensions rec_w /= fig_w rec_h /= fig_h sp_w /= fig_w sp_h /= fig_h pal_sp /= fig_w pal_w /= fig_w tit_h /= fig_h # matplotlib global settings mpl.rcParams.update({'font.size': 24}) # Use this for editable text in svg mpl.rcParams['text.usetex'] = False mpl.rcParams['svg.fonttype'] = 'none' # Hi def figure mpl.rcParams['figure.dpi'] = dpi # instantiate figure fig = plt.figure(figsize=(fig_w, fig_h)) ax = fig.add_axes([0, 0, 1, 1], zorder=0) ax.axis('off') ax.annotate(this_date.strftime('%Y-%m-%d %H:%M'), size=35, xy=(0.015, 0.92), xycoords='figure fraction', bbox=dict(boxstyle="round", fc=[1, 1, 1, .9], ec=[1, 1, 1, 0])) if pr_dict is None: #data not found or not available at desired date #print warning and make empty image message = ('RDPR not available in file: ' + newline + fst_file + newline + 'at date' + newline + str(this_date)) logger.warning(message) ax = fig.add_axes([0, 0, 1, 1]) ax.axis('off') ax.annotate(message, size=18, xy=(.1, .5), xycoords='axes fraction') else: #PR found in file, proceed to plot it #setup color mapping objects: # #Precip rate map_pr = legs.PalObj(range_arr=[.1, 3., 6., 12., 25., 50., 100.], n_col=6, over_high='extend', under_low='white', excep_val=missing, excep_col='grey_200') #custom pastel color segments for QI index pastel = [ [[255, 190, 187], [230, 104, 96]], #pale/dark red [[255, 185, 255], [147, 78, 172]], #pale/dark purple [[255, 227, 215], [205, 144, 73]], #pale/dark brown [[210, 235, 255], [58, 134, 237]], #pale/dark blue [[223, 255, 232], [61, 189, 63]] ] #pale/dark green map_qi = legs.PalObj(range_arr=[0., 1.], dark_pos='high', color_arr=pastel, excep_val=[missing, 0.], excep_col=['grey_220', 'white']) #Setup geographical projection # Full HRDPS grid pole_latitude = 35.7 pole_longitude = 65.5 lat_0 = 48.8 delta_lat = 10. lon_0 = 266.00 delta_lon = 40. map_extent = [ lon_0 - delta_lon, lon_0 + delta_lon, lat_0 - delta_lat, lat_0 + delta_lat ] proj_aea = ccrs.RotatedPole(pole_latitude=pole_latitude, pole_longitude=pole_longitude) logger.info('Making projection object') proj_obj = geo_tools.ProjInds(src_lon=pr_dict['lon'], src_lat=pr_dict['lat'], extent=map_extent, dest_crs=proj_aea, image_res=(800, 400)) #plot precip rate # #position of this fig x0 = sp_w + 0. * (rec_w + sp_w) y0 = sp_h + 0. * (rec_h + sp_h) pos = [x0, y0, rec_w, rec_h] #setup axes ax = fig.add_axes(pos, projection=proj_aea) ax.set_extent(map_extent) #thinner lines if version.parse(cartopy.__version__) >= version.parse("0.18.0"): ax.spines['geo'].set_linewidth(0.3) else: ax.outline_patch.set_linewidth(0.3) #plot image caption ax.annotate('RDPR', size=30, xy=(xp, yp), xycoords='axes fraction') #geographical projection of data projected_pr = proj_obj.project_data(pr_dict['values']) #apply color mapping n plot data map_pr.plot_data(ax, projected_pr, palette='right', pal_linewidth=0.3, pal_units='[mm/h]', pal_format='{:5.1f}', equal_legs=True) #force axes to respect ratio we previously indicated... ax.set_aspect('auto') #plot geographical contours ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=0.3, edgecolor='0.3', zorder=1) #plot border proj_obj.plot_border(ax, mask_outside=True, linewidth=.3) #plot quality index # #position of this fig x0 = sp_w + 1. * (rec_w + sp_w + pal_sp + pal_w + 1.5 / fig_w) y0 = sp_h + 0. * (rec_h + sp_h) pos = [x0, y0, rec_w, rec_h] #setup axes ax = fig.add_axes(pos, projection=proj_aea) ax.set_extent(map_extent) #thinner lines if version.parse(cartopy.__version__) >= version.parse("0.18.0"): ax.spines['geo'].set_linewidth(0.3) else: ax.outline_patch.set_linewidth(0.3) #plot image caption ax.annotate('RDQI', size=30, xy=(xp, yp), xycoords='axes fraction') if qi_dict is None: #QI not available indicate it on figure message = 'RDQI not available in file: ' + newline + fst_file logger.warning.warn(message) ax.annotate(message, size=10, xy=(.0, .5), xycoords='axes fraction') else: #QI is available, plot it # #geographical projection of data projected_qi = proj_obj.project_data(qi_dict['values']) #apply color mapping n plot data map_qi.plot_data(ax, projected_qi, palette='right', pal_linewidth=0.3, pal_units='[unitless]', pal_format='{:2.1f}') #force axes to respect ratio we previously indicated... ax.set_aspect('auto') #plot geographical contours ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=0.3, edgecolor='0.3', zorder=1) #plot border proj_obj.plot_border(ax, mask_outside=True, linewidth=.3) #save figure svg_name = fig_dir + this_date.strftime(fig_name_recipe) logger.info('Saving figure:' + svg_name + ', changing typeface and converting format if needed.') plt.savefig(svg_name) plt.close(fig) dpy.lmroman(svg_name) if fig_format != 'svg': dpy.convert(svg_name, fig_format, del_orig=True, density=500, geometry='50%') #not sure what is accumulating but adding this garbage collection step #prevents jobs from aborting when a large number of files are made #gc.collect() #not sure if needed anymore... keeping commented command here just in case. logger.info('plot_rdpr_rdqi Done')
def main(): #recover previously prepared data currentdir = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname( currentdir) #directory where this package lives source_file = parentdir + '/test_data/goes_gpm_data.pickle' with open(source_file, 'rb') as f: data_dict = pickle.load(f) dpr_lats = data_dict['dprLats'] dpr_lons = data_dict['dprLons'] dpr_pr = data_dict['dprPrecipRate'] goes_lats = data_dict['goesLats'] goes_lons = data_dict['goesLons'] goes_albedo = data_dict['goesAlbedo'] #missing value missing = -9999. #Figure position stuff pic_h = 5.4 pic_w = 8.8 pal_sp = .1 / pic_w pal_w = .25 / pic_w ratio = .8 sq_sz = 6. rec_w = sq_sz / pic_w rec_h = ratio * sq_sz / pic_h sp_w = .1 / pic_w sp_h = 1.0 / pic_h x1 = .1 / pic_w y1 = .2 / pic_h #number of pixels of the image that will be shown hpix = 400. #number of horizontal pixels E-W vpix = ratio * hpix #number of vertical pixels S-N img_res = (int(hpix), int(vpix)) #point density for figure mpl.rcParams.update({'font.size': 17}) #Use this to make text editable in svg files mpl.rcParams['text.usetex'] = False mpl.rcParams['svg.fonttype'] = 'none' #Hi def figure mpl.rcParams['figure.dpi'] = 400 #instantiate figure fig = plt.figure(figsize=(pic_w, pic_h)) # Set up colormapping objects #For precip rates ranges = [0., .4, .8, 1.5, 3., 6., 12.] map_pr = legs.PalObj(range_arr=ranges, n_col=6, over_high='extend', under_low='white', excep_val=[missing, 0.], excep_col=['grey_230', 'white']) #For Goes albedo map_goes = legs.PalObj(range_arr=[0., .6], over_high='extend', color_arr='b_w', dark_pos='low', excep_val=[-1, missing], excep_col=['grey_130', 'grey_130']) #Plot data on a domain covering North-America # # map_extent = [-141.0, -16., -7.0, 44.0] #position x0 = x1 y0 = y1 pos = [x0, y0, rec_w, rec_h] #border of smaller domain to plot on large figure map_extent_small = [-78., -68., 12., 22.] # make_panel(fig, pos, img_res, map_extent, missing, dpr_lats, dpr_lons, dpr_pr, goes_lats, goes_lons, goes_albedo, map_pr, map_goes, map_extent_small, average_dpr=True) #instantiate 2nd figure fig2 = plt.figure(figsize=(pic_w, pic_h)) # sphinx_gallery_thumbnail_number = 2 #Closeup on a domain in the viscinity of Haiti # # map_extent = map_extent_small #position x0 = x1 y0 = y1 pos = [x0, y0, rec_w, rec_h] # make_panel(fig2, pos, img_res, map_extent, missing, dpr_lats, dpr_lons, dpr_pr, goes_lats, goes_lons, goes_albedo, map_pr, map_goes, include_zero=False) #plot palettes x0 = x0 + rec_w + pal_w y0 = y0 map_pr.plot_palette(pal_pos=[x0, y0, pal_w, rec_h], pal_units='[mm/h]', pal_format='{:2.1f}', equal_legs=True) x0 = x0 + 5. * pal_w y0 = y0 map_goes.plot_palette(pal_pos=[x0, y0, pal_w, rec_h], pal_units='unitless', pal_format='{:2.1f}')
def main(): #recover previously prepared data currentdir = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname( currentdir) #directory where this package lives source_file = parentdir + '/test_data/pal_demo_data.pickle' with open(source_file, 'rb') as f: data_dict = pickle.load(f) longitudes = data_dict['longitudes'] #2D longitudes [deg] latitudes = data_dict['latitudes'] #2D latitudes [deg] quality_index = data_dict[ 'qualityIndex'] #2D quality index of a radar mosaic [0-1]; 1 = best quality #missing value missing = -9999. #pixel density of image to plot ratio = 0.8 hpix = 600. #number of horizontal pixels E-W vpix = ratio * hpix #number of vertical pixels S-N img_res = (int(hpix), int(vpix)) ##define Albers projection and extend of map #Obtained through trial and error for good fit of the mdel grid being plotted proj_aea = ccrs.AlbersEqualArea(central_longitude=-94., central_latitude=35., standard_parallels=(30., 40.)) map_extent = [-104.8, -75.2, 27.8, 48.5] #point density for figure mpl.rcParams['figure.dpi'] = 400 #larger characters mpl.rcParams.update({'font.size': 15}) #instantiate figure fig = plt.figure(figsize=(7.5, 6.)) #instantiate object to handle geographical projection of data proj_inds = geo_tools.ProjInds(src_lon=longitudes, src_lat=latitudes, extent=map_extent, dest_crs=proj_aea, image_res=img_res, missing=missing) #axes for this plot ax = fig.add_axes([.01, .1, .8, .8], projection=proj_aea) ax.set_extent(map_extent) # Set up colormapping object # #custom pastel color segments pastel = [ [[255, 190, 187], [230, 104, 96]], #pale/dark red [[255, 185, 255], [147, 78, 172]], #pale/dark purple [[255, 227, 215], [205, 144, 73]], #pale/dark brown [[210, 235, 255], [58, 134, 237]], #pale/dark blue [[223, 255, 232], [61, 189, 63]] ] #pale/dark green #init color mapping object map_qi = legs.PalObj(range_arr=[0., 1.], dark_pos='high', color_arr=pastel, excep_val=[missing], excep_col='grey_120') #geographical projection of data into axes space proj_data = proj_inds.project_data(quality_index) #plot data & palette map_qi.plot_data(ax=ax, data=proj_data, zorder=0, palette='right', pal_units='[unitless]', pal_format='{:2.1f}') #palette options #add political boundaries ax.add_feature(cfeature.STATES.with_scale('50m'), linewidth=0.5, edgecolor='0.2', zorder=1) #plot border and mask everything outside model domain proj_inds.plot_border(ax, mask_outside=True, linewidth=.5)
def main(): import os, inspect import copy import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt import cartopy.crs as ccrs # imports from domutils import domutils.legs as legs import domutils.geo_tools as geo_tools import domutils.radar_tools as radar_tools #missing values ane the associated color missing = -9999. missing_color = 'grey_160' undetect = -3333. undetect_color = 'grey_180' #file to read currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname(currentdir) #directory where this package lives odim_file = parentdir + '/test_data/odimh5_radar_volume_scans/2019071120_24_ODIMH5_PVOL6S_VOL.qc.casbv.h5' #Read a PPI from a volume scan in the ODIM H5 format #we want the PPI with a nominal elevation of 0.4 degree #we retrieve reflectivity (dbzh) and Doppler velocity (vradh) and the Depolarization ratio (dr) #the reader computes the lat/lon of data points in the PPI for you out_dict = radar_tools.read_h5_vol(odim_file=odim_file, elevations=[0.4], quantities=['dbzh','vradh','dr'], no_data=missing, undetect=undetect, include_quality=True, latlon=True) #radar properties radar_lat = out_dict['radar_lat'] radar_lon = out_dict['radar_lon'] #PPIs # You can see the quantities available in the dict with # print(out_dict['0.4'].keys()) reflectivity = out_dict['0.4']['dbzh'] doppvelocity = out_dict['0.4']['vradh'] dr = out_dict['0.4']['dr'] tot_qi = out_dict['0.4']['quality_qi_total'] latitudes = out_dict['0.4']['latitudes'] longitudes = out_dict['0.4']['longitudes'] # #Quality controls on Doppler velocity #pixel is considered weather is DR < 12 dB weather_target = np.where( dr < -12., 1, 0) #not a weather target when DR is missing weather_target = np.where( np.isclose(dr, missing), 0, weather_target) #not a weather target when DR is undetect weather_target = np.where( np.isclose(dr, undetect), 0, weather_target) #a 3D representation of a boxcar filter see radar_tools API for docs on this function #5x5 search area in polar coordinates remapped_data_5 = radar_tools.median_filter.remap_data(weather_target, window=5, mode='wrap') #if less than 12 points (half the points in a 5x5 window) have good dr, pixel is marked as clutter is_clutter = np.where(np.sum(remapped_data_5, axis=2) <= 12, True, False) #3x3 search area in polar coordinates remapped_data_3 = radar_tools.median_filter.remap_data(weather_target, window=3, mode='wrap') #if less than 9 points (all the points in a 3x3 window) have good dr, pixel is marked as clutter is_clutter = np.where(np.sum(remapped_data_3, axis=2) < 9, True, is_clutter) # doppvelocity_qc = copy.deepcopy(doppvelocity) #DR filtering only applied to points with reflectivities < 35 dB doppvelocity_qc = np.where( np.logical_and(is_clutter , reflectivity < 35.) , undetect, doppvelocity_qc) #add filtering using total quality index doppvelocity_qc = np.where( tot_qi < 0.2 , undetect, doppvelocity_qc) #pixel density of panels in figure ratio = 1. hpix = 800. #number of horizontal pixels E-W vpix = ratio*hpix #number of vertical pixels S-N img_res = (int(hpix),int(vpix)) #cartopy Rotated Pole projection pole_latitude=90. pole_longitude=0. proj_rp = ccrs.RotatedPole(pole_latitude=pole_latitude, pole_longitude=pole_longitude) #plate carree proj_pc = ccrs.PlateCarree() #a domain ~250km around the Montreal area where the radar is lat_0 = 45.7063 delta_lat = 2.18 lon_0 = -73.85 delta_lon = 3.12 map_extent=[lon_0-delta_lon, lon_0+delta_lon, lat_0-delta_lat, lat_0+delta_lat] #a smaller domain for a closeup that will be inlaid in figure lat_0 = 46.4329666 delta_lat = 0.072666 lon_0 = -75.00555 delta_lon = 0.104 map_extent_close=[lon_0-delta_lon, lon_0+delta_lon, lat_0-delta_lat, lat_0+delta_lat] #a circular clipping mask for the closeup axes x = np.linspace(-1., 1, int(hpix)) y = np.linspace(-1., 1, int(vpix)) xx, yy = np.meshgrid(x, y, indexing='ij') clip_alpha = np.where( np.sqrt(xx**2.+yy**2.) < 1., 1., 0. ) #a circular line around the center of the closeup window radius=8. #km azimuths = np.arange(0,361.)#360 degree will be included for full circles closeup_lons, closeup_lats = geo_tools.lat_lon_range_az(lon_0, lat_0, radius, azimuths) #a line 5km long for showing scale in closeup insert azimuth = 90 #meteorological angle distance = np.linspace(0,5.,50)#360 degree will be included for full circles scale_lons, scale_lats = geo_tools.lat_lon_range_az(lon_0-0.07, lat_0-0.04, distance, azimuth) #point density for figure mpl.rcParams['figure.dpi'] = 100. #crank this up for high def images # Use this for editable text in svg (eg with Inkscape) mpl.rcParams['text.usetex'] = False mpl.rcParams['svg.fonttype'] = 'none' #larger characters mpl.rcParams.update({'font.size': 25}) # dimensions for figure panels and spaces # all sizes are inches for consistency with matplotlib fig_w = 13.5 # Width of figure fig_h = 12.5 # Height of figure rec_w = 5. # Horizontal size of a panel rec_h = ratio * rec_w # Vertical size of a panel sp_w = .5 # horizontal space between panels sp_h = .8 # vertical space between panels fig = plt.figure(figsize=(fig_w,fig_h)) xp = .02 #coords of title (axes normalized coordinates) yp = 1.02 #coords for the closeup that is overlayed x0 = .55 #x-coord of bottom left position of closeup (axes coords) y0 = .55 #y-coord of bottom left position of closeup (axes coords) dx = .4 #x-size of closeup axes (fraction of a "regular" panel) dy = .4 #y-size of closeup axes (fraction of a "regular" panel) #normalize sizes to obtain figure coordinates (0-1 both horizontally and vertically) rec_w = rec_w / fig_w rec_h = rec_h / fig_h sp_w = sp_w / fig_w sp_h = sp_h / fig_h #instantiate objects to handle geographical projection of data proj_inds = geo_tools.ProjInds(src_lon=longitudes, src_lat=latitudes, extent=map_extent, dest_crs=proj_rp, extend_x=False, extend_y=True, image_res=img_res, missing=missing) #for closeup image proj_inds_close = geo_tools.ProjInds(src_lon=longitudes, src_lat=latitudes, extent=map_extent_close, dest_crs=proj_rp, extend_x=False, extend_y=True, image_res=img_res, missing=missing) # #Reflectivity # #axes for this plot pos = [sp_w, sp_h+(sp_h+rec_h), rec_w, rec_h] ax = fig.add_axes(pos, projection=proj_rp) ax.spines['geo'].set_linewidth(0.3) ax.set_extent(map_extent) ax.set_aspect('auto') ax.annotate('Qced Reflectivity', size=30, xy=(xp, yp), xycoords='axes fraction') # colormapping object for reflectivity map_reflectivity = legs.PalObj(range_arr=[0.,60], n_col=6, over_high='extend', under_low='white', excep_val=[missing, undetect], excep_col=[missing_color, undetect_color]) #geographical projection of data into axes space proj_data = proj_inds.project_data(reflectivity) #plot data & palette map_reflectivity.plot_data(ax=ax, data=proj_data, zorder=0, palette='right', pal_units='[dBZ]', pal_format='{:3.0f}') #add political boundaries add_feature(ax) #radar circles and azimuths radar_ax_circ(ax, radar_lat, radar_lon) #circle indicating closeup area ax.plot(closeup_lons, closeup_lats, transform=proj_pc, c=(0.,0.,0.), zorder=300, linewidth=.8) #arrow pointing to closeup ax.annotate("", xy=(0.33, 0.67), xytext=(.55, .74), xycoords='axes fraction', arrowprops=dict(arrowstyle="<-")) # #Closeup of reflectivity # pos = [sp_w+x0*rec_w, sp_h+(sp_h+rec_h)+y0*rec_h, dx*rec_w, dy*rec_h] ax2 = fig.add_axes(pos, projection=proj_rp, label='reflectivity overlay') ax2.set_extent(map_extent_close) ax2.spines['geo'].set_linewidth(0.0) #no border line ax2.set_facecolor((1.,1.,1.,0.)) #transparent background #geographical projection of data into axes space proj_data = proj_inds_close.project_data(reflectivity) #RGB values for data to plot closeup_rgb = map_reflectivity.to_rgb(proj_data) #get corners of image in data space extent_data_space = ax2.get_extent() ## another way of doing the same thing is to get an object that convers axes coords to data coords ## this method is more powerfull as it will return data coords of any points in axes space #transform_data_to_axes = ax2.transData + ax2.transAxes.inverted() #transform_axes_to_data = transform_data_to_axes.inverted() #pts = ((0.,0.),(1.,1.)) #axes space coords #pt1, pt2 = transform_axes_to_data.transform(pts) #extent_data_space = [pt1[0],pt2[0],pt1[1],pt2[1]] #add alpha channel (transparency) to closeup image rgba = np.concatenate([closeup_rgb/255.,clip_alpha[...,np.newaxis]], axis=2) #plot image ax2.imshow(rgba, interpolation='nearest', origin='upper', extent=extent_data_space, zorder=100) ax2.set_aspect('auto') #circle indicating closeup area circle = ax2.plot(closeup_lons, closeup_lats, transform=proj_pc, c=(0.,0.,0.), zorder=300, linewidth=1.5) #prevent clipping of the circle we just drawn for line in ax2.lines: line.set_clip_on(False) # #Quality Controlled Doppler velocity # #axes for this plot pos = [sp_w+(sp_w+rec_w+1./fig_w), sp_h+(sp_h+rec_h), rec_w, rec_h] ax = fig.add_axes(pos, projection=proj_rp) ax.set_extent(map_extent) ax.set_aspect('auto') ax.annotate('Qced Doppler velocity', size=30, xy=(xp, yp), xycoords='axes fraction') #from https://colorbrewer2.org brown_purple=[[ 45, 0, 75], [ 84, 39,136], [128,115,172], [178,171,210], [216,218,235], [247,247,247], [254,224,182], [253,184, 99], [224,130, 20], [179, 88, 6], [127, 59, 8]] range_arr = [-48.,-40.,-30.,-20,-10.,-1.,1.,10.,20.,30.,40.,48.] map_dvel = legs.PalObj(range_arr=range_arr, color_arr = brown_purple, solid='supplied', excep_val=[missing, undetect], excep_col=[missing_color, undetect_color]) #geographical projection of data into axes space proj_data = proj_inds.project_data(doppvelocity_qc) #plot data & palette map_dvel.plot_data(ax=ax,data=proj_data, zorder=0, palette='right', pal_units='[m/s]', pal_format='{:3.0f}') #palette options #add political boundaries add_feature(ax) #radar circles and azimuths radar_ax_circ(ax, radar_lat, radar_lon) #circle indicating closeup area ax.plot(closeup_lons, closeup_lats, transform=proj_pc, c=(0.,0.,0.), zorder=300, linewidth=.8) #arrow pointing to closeup ax.annotate("", xy=(0.33, 0.67), xytext=(.55, .74), xycoords='axes fraction', arrowprops=dict(arrowstyle="<-")) # #Closeup of Doppler velocity # pos = [sp_w+1.*(sp_w+rec_w+1./fig_w)+x0*rec_w, sp_h+(sp_h+rec_h)+y0*rec_h, dx*rec_w, dy*rec_h] ax2 = fig.add_axes(pos, projection=proj_rp, label='overlay') ax2.set_extent(map_extent_close) ax2.spines['geo'].set_linewidth(0.0) #no border line ax2.set_facecolor((1.,1.,1.,0.)) #transparent background #geographical projection of data into axes space proj_data = proj_inds_close.project_data(doppvelocity_qc) #RGB values for data to plot closeup_rgb = map_dvel.to_rgb(proj_data) #get corners of image in data space extent_data_space = ax2.get_extent() #add alpha channel (transparency) to closeup image rgba = np.concatenate([closeup_rgb/255.,clip_alpha[...,np.newaxis]], axis=2) #plot image ax2.imshow(rgba, interpolation='nearest', origin='upper', extent=extent_data_space, zorder=100) ax2.set_aspect('auto') #line indicating closeup area circle = ax2.plot(closeup_lons, closeup_lats, transform=proj_pc, c=(0.,0.,0.), zorder=300, linewidth=1.5) for line in ax2.lines: line.set_clip_on(False) #Show scale in inlay ax2.plot(scale_lons, scale_lats, transform=proj_pc, c=(0.,0.,0.), zorder=300, linewidth=.8) ax2.annotate("5 km", size=18, xy=(.16, .25), xycoords='axes fraction', zorder=310) # #DR # #axes for this plot pos = [sp_w, sp_h, rec_w, rec_h] ax = fig.add_axes(pos, projection=proj_rp) ax.set_extent(map_extent) ax.set_aspect('auto') ax.annotate('Depolarization ratio', size=30, xy=(xp, yp), xycoords='axes fraction') # Set up colormapping object map_dr = legs.PalObj(range_arr=[-36.,-24.,-12., 0.], color_arr=['purple','blue','orange'], dark_pos =['high', 'high','low'], excep_val=[missing, undetect], excep_col=[missing_color, undetect_color]) #geographical projection of data into axes space proj_data = proj_inds.project_data(dr) #plot data & palette map_dr.plot_data(ax=ax,data=proj_data, zorder=0, palette='right', pal_units='[dB]', pal_format='{:3.0f}') #add political boundaries add_feature(ax) #radar circles and azimuths radar_ax_circ(ax, radar_lat, radar_lon) # #Total quality index # #axes for this plot pos = [sp_w+(sp_w+rec_w+1./fig_w), sp_h, rec_w, rec_h] ax = fig.add_axes(pos, projection=proj_rp) ax.set_extent(map_extent) ax.set_aspect('auto') ax.annotate('Total quality index', size=30, xy=(xp, yp), xycoords='axes fraction') # Set up colormapping object pastel = [ [[255,190,187],[230,104, 96]], #pale/dark red [[255,185,255],[147, 78,172]], #pale/dark purple [[255,227,215],[205,144, 73]], #pale/dark brown [[210,235,255],[ 58,134,237]], #pale/dark blue [[223,255,232],[ 61,189, 63]] ] #pale/dark green map_qi = legs.PalObj(range_arr=[0., 1.], dark_pos='high', color_arr=pastel, excep_val=[missing, undetect], excep_col=[missing_color, undetect_color]) #geographical projection of data into axes space proj_data = proj_inds.project_data(tot_qi) #plot data & palette map_qi.plot_data(ax=ax,data=proj_data, zorder=0, palette='right', pal_units='[unitless]', pal_format='{:3.1f}') #palette options #add political boundaries add_feature(ax) #radar circles and azimuths radar_ax_circ(ax, radar_lat, radar_lon)
def test_apply_wrong_low_oper(self): with self.assertRaises(ValueError): map_obj = legs.PalObj() map_obj.lows.oper = '=' validate.continuity_of_mapping(map_obj)
def test_apply_wrong_value_discontinuous_high_oper2(self): with self.assertRaises(ValueError): map_obj = legs.PalObj() map_obj.lows.oper = '>' map_obj.cols[0].oper_low = '<' validate.continuity_of_mapping(map_obj)
def test_apply_wrong_value_discontinuous_high_oper1(self): with self.assertRaises(ValueError): map_obj = legs.PalObj() map_obj.highs.oper = '>=' map_obj.cols[-1].oper_high = '<=' validate.continuity_of_mapping(map_obj)
def test_apply_wrong_value_discontinuous_high(self): with self.assertRaises(ValueError): map_obj = legs.PalObj(n_col=2) map_obj.highs.val = 0.1 validate.continuity_of_mapping(map_obj)
def test_apply_bounds_mismatch(self): with self.assertRaises(ValueError): map_obj = legs.PalObj(color_arr=['red', 'blue', 'green']) map_obj.cols[1].val_high = 1.0 map_obj.cols[2].val_low = 1.1 validate.continuity_of_mapping(map_obj)
def test_apply_wrong_value_oper_low(self): with self.assertRaises(ValueError): map_obj = legs.PalObj(color_arr=['red', 'blue']) map_obj.cols[1].oper_low = '<' validate.continuity_of_mapping(map_obj)