diff --git a/docs/Users_Guide/figure/example.png b/docs/Users_Guide/figure/example.png index 6ff73473..ca26ed4e 100644 Binary files a/docs/Users_Guide/figure/example.png and b/docs/Users_Guide/figure/example.png differ diff --git a/docs/Users_Guide/tcrmw_cross_section.rst b/docs/Users_Guide/tcrmw_cross_section.rst index 0b3078fc..d1db8b8c 100644 --- a/docs/Users_Guide/tcrmw_cross_section.rst +++ b/docs/Users_Guide/tcrmw_cross_section.rst @@ -85,44 +85,19 @@ Save and close the *test_plot_cross_section.sh* script. To generate the plot, perform the following: -* Make sure you have the following Python packages installed: - * **Python 3.7** or above - * METcalcpy (use the same version number as this METplotpy) - - * matplotlib 3.4.3 - - * metpy 1.1.0 - - * netcdf4 1.5.7 or above - - * numpy - - * pandas - - * pint 0.17 - - * xarray - - * yaml - - - -*Specific version numbers are specified when necessary. If versions are not specified, use a -compatible version number for your operating system and existing packages.* - -* If you are running in a conda environment, verify that you are running the conda environment that has the above Python packages installed. +* If you are running in a conda environment, verify that your conda environment that has the Python packages specified in the requirements.txt file located in the $METPLOTPY_BASE directory. * cd to the $METPLOTPY_BASE/metplotpy/contributed/tc_rmw directory -``cd $METPLOTPY_BASE/metplotpy/contributed/tc_rmw`` + ``cd $METPLOTPY_BASE/metplotpy/contributed/tc_rmw`` replacing the $METPLOTPY_BASE with the directory where you saved the METplotpy source code. * run the test_plot_cross_section.sh script: -``sh test_plot_cross_section.sh`` + ``sh test_plot_cross_section.sh`` Two files will be created, *example.png* and *example.pdf* (if you used the *plot_filename* value of 'example' in the *plot_cross_section.yaml* configuration file). They will be located in diff --git a/metplotpy/contributed/tc_rmw/plot_cross_section.py b/metplotpy/contributed/tc_rmw/plot_cross_section.py index be0fd8b2..1cc30016 100755 --- a/metplotpy/contributed/tc_rmw/plot_cross_section.py +++ b/metplotpy/contributed/tc_rmw/plot_cross_section.py @@ -1,13 +1,12 @@ # ============================* - # ** Copyright UCAR (c) 2020 - # ** University Corporation for Atmospheric Research (UCAR) - # ** National Center for Atmospheric Research (NCAR) - # ** Research Applications Lab (RAL) - # ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA - # ============================* - - - +# ** Copyright UCAR (c) 2025 +# ** University Corporation for Atmospheric Research (UCAR) +# ** National Science Foundation National Center for Atmospheric Research (NSF NCAR) +# ** Research Applications Lab (RAL) +# ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA +# ============================* + + """ plot_cross_section """ @@ -26,6 +25,7 @@ matplotlib.use('Agg') from metplotpy.plots import util + def plot_cross_section(config, data_set, args): """ Generate the cross-section plot of the field specified in the YAML config file @@ -57,7 +57,7 @@ def plot_cross_section(config, data_set, args): # Create the log file using the same name as the plot name with '_log' and in the # same location. try: - os.makedirs(plot_outdir, exist_ok=True) + os.makedirs(plot_outdir, exist_ok=True) except FileExistsError: pass log_filename = os.path.join(plot_outdir, config['plot_filename'] + '_log' + '.txt') @@ -69,6 +69,7 @@ def plot_cross_section(config, data_set, args): fig, ax = plt.subplots(figsize=(plot_width, plot_height)) field = data_set[config['field']] + itime = config['index_time_slice'] # originally, axis=1 but the order of dimensions was modified @@ -78,24 +79,47 @@ def plot_cross_section(config, data_set, args): # originally, the transpose of the field_azi_mean was used, but this is no # longer necessary. If the transpose is used, the dimensions are incorrect # and a TypeError will be raised by the contour plot. - scalar_contour = ax.contour(data_set['range'], - data_set[config['vertical_coord_name']], - field_azi_mean, - levels=np.arange(config['contour_level_start'], - config['contour_level_end'], + if config['colormap'] != 'custom': + colormap = config['colormap'] + else: + if config['colormap_by_rgb']: + methodology = "by_rgb" + colormap_triplet_list = config['colormap_colors_rgb'] + else: + methodology = "by_hex" + colormap_triplet_list = config['colormap_colors_hexadecimal'] + + colormap = util.customize_colormap(colormap_triplet_list, methodology) + + if config['filled_contour_on']: + scalar_contour = ax.contourf(data_set['range'], data_set[config['vertical_coord_name']], field_azi_mean, + levels=np.arange(config['contour_level_start'], config['contour_level_end'], + config['contour_level_stepsize']), cmap=colormap) + # Add colorbar legend + cbar = plt.colorbar(scalar_contour, label=config['filled_contour_colorbar_label']) + # cbar.set_label(config['filled_contour_colorbar_label']) + + # scalar_contour = ax.contour(data_set['range'], data_set[config['vertical_coord_name']], field_azi_mean, + # levels=np.arange(config['contour_level_start'], config['contour_level_end'], + # config['contour_level_stepsize']), + # colors=config['contour_line_colors'], linewidths=(config['line_width'],), + # linestyles=[config['contour_line_style']]) + + + + if config['line_contour_on']: + scalar_contour = ax.contour(data_set['range'], data_set[config['vertical_coord_name']], field_azi_mean, + levels=np.arange(config['contour_level_start'], config['contour_level_end'], config['contour_level_stepsize']), - colors=config['contour_line_colors'], - linewidths=(config['line_width']) - ) + colors=config['contour_line_colors'], linewidths=(config['line_width'],), + linestyles=[config['contour_line_style']]) plt.title(config['plot_title']) ax.clabel(scalar_contour, colors=config['contour_label_color'], fmt=config['contour_label_fmt']) ax.set_xlabel(config['x_label']) ax.set_ylabel(config['y_label']) ax.set_xticks(np.arange(config['x_tick_start'], config['x_tick_end'])) - ax.set_yticks(np.arange(config['y_tick_start'], - config['y_tick_end'], - config['y_tick_stepsize'])) + ax.set_yticks(np.arange(config['y_tick_start'], config['y_tick_end'], config['y_tick_stepsize'])) ax.set_yscale(config['y_scale']) ax.set_ylim(config['y_lim_start'], config['y_lim_end']) @@ -107,23 +131,13 @@ def plot_cross_section(config, data_set, args): if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Plot Tropical Cyclone Cross Section') - - parser.add_argument( - '--datadir', type=str, dest='datadir', - required=True) - parser.add_argument( - '--plotdir', type=str, dest='plotdir', - required=True) - parser.add_argument( - '--filename', type=str, dest='filename', - required=True) - parser.add_argument('--config', type=str, - required=True, - help='configuration file') - parser.add_argument('--loglevel', type=str, - required=False) + parser = argparse.ArgumentParser(description='Plot Tropical Cyclone Cross Section') + + parser.add_argument('--datadir', type=str, dest='datadir', required=True) + parser.add_argument('--plotdir', type=str, dest='plotdir', required=True) + parser.add_argument('--filename', type=str, dest='filename', required=True) + parser.add_argument('--config', type=str, required=True, help='configuration file') + parser.add_argument('--loglevel', type=str, required=False) input_args = parser.parse_args() @@ -131,8 +145,7 @@ def plot_cross_section(config, data_set, args): Read YAML configuration file """ try: - plotting_config = yaml.load( - open(input_args.config), Loader=yaml.FullLoader) + plotting_config = yaml.load(open(input_args.config), Loader=yaml.FullLoader) except yaml.YAMLError as exc: sys.exit(1) @@ -144,4 +157,3 @@ def plot_cross_section(config, data_set, args): except (ValueError, FileNotFoundError, PermissionError): sys.exit(1) plot_cross_section(plotting_config, input_data, input_args) - diff --git a/metplotpy/contributed/tc_rmw/plot_cross_section.yaml b/metplotpy/contributed/tc_rmw/plot_cross_section.yaml index 40204383..051fd254 100644 --- a/metplotpy/contributed/tc_rmw/plot_cross_section.yaml +++ b/metplotpy/contributed/tc_rmw/plot_cross_section.yaml @@ -9,8 +9,10 @@ vertical_coord_name: field: 'TMP' +filled_contour_colorbar_label: ' Temperature (K) ' + contour_level_start: 0 -contour_level_end: 500 +contour_level_end: 450 contour_level_stepsize: 5 # time slice of interest @@ -19,10 +21,77 @@ index_time_slice: 0 # # contour plot appearance customizations # -line_width: 2 +line_width: 1 contour_line_colors: 'darkblue' -contour_label_color: 'orange' +contour_label_color: 'darkblue' contour_label_fmt: '%1.0f' +# contour line style: solid, dashed, dotted +contour_line_style: 'dotted' +line_contour_on: True + + +# +# filled contour +# +filled_contour_on: True + +# for a list of available colormaps: +# https://matplotlib.org/stable/users/explain/colors/colormaps.html +# e.g. gist_stern, jet, gist_ncar + +colormap: 'custom' + +# for custom colormap, set value to 'custom' and then provide values for +# rgb (red, green, blue) triplets as either +#colormap: 'custom' + +# Method to define a custom colormap +# If True, then set the colormap by a list of RGB triplets. +# If False, then set colormap by a list of hex triplets +colormap_by_rgb: False + +colormap_colors_rgb: + - [ 110, 28, 255 ] # purples + - [ 56, 109, 249 ] # blues + - [ 44, 126, 247 ] + - [ 23, 203, 228 ] + - [ 36, 216, 212 ] # aqua blues + - [ 49, 225, 218 ] + - [ 74, 242, 207 ] # blue greens + - [ 100, 251, 195 ] + - [ 193, 235, 141 ] # greens + - [ 207, 225, 132 ] + - [ 219, 216, 123 ] # yellows + - [ 246, 190, 104 ] # oranges + - [ 255, 176, 84 ] + - [ 255, 160, 85 ] + - [ 255, 89, 45 ] + - [ 255, 68, 34 ] # reds + - [ 255, 50, 25 ] + - [ 255, 28, 14 ] + + +# case-insensitive hexadecimals, enclose each value with ' ' or " " +colormap_colors_hexadecimal: + - '#6e1cff' # purples + - '#386df9' # blues + - '#2c7ef7' + - '#17cbe4' + - '#24d8d4' # aqua blues + - '#31e1da' + - '#4af2cf' # blue greens + - '#64fbc3' + - '#c1eb8d' # greens + - '#cfe184' + - '#dbd87b' # yellows + - '#f6be68' # oranges + - '#ffb054' + - '#ffa055' + - '#ff592d' + - '#ff4422' # reds + - '#ff3219' + - '#ff1c0e' + # axis labels # x-axis is in units of RMW diff --git a/metplotpy/contributed/tc_rmw/tc_utils.py b/metplotpy/contributed/tc_rmw/tc_utils.py index fe8d052f..291e6b49 100644 --- a/metplotpy/contributed/tc_rmw/tc_utils.py +++ b/metplotpy/contributed/tc_rmw/tc_utils.py @@ -1,7 +1,7 @@ # ============================* - # ** Copyright UCAR (c) 2020 + # ** Copyright UCAR (c) 2025 # ** University Corporation for Atmospheric Research (UCAR) - # ** National Center for Atmospheric Research (NCAR) + # ** National Science Foundation National Center for Atmospheric Research (NSF NCAR) # ** Research Applications Lab (RAL) # ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA # ============================* @@ -188,7 +188,6 @@ def read_tcrmw_levels(filename, levels=['L0']): file_id.close() return valid_time, lat_grid, lon_grid, wind_data, scalar_data - - + if __name__ == '__main__': print(format_valid_time(2019050123)) diff --git a/metplotpy/contributed/tc_rmw/test_plot_cross_section.sh b/metplotpy/contributed/tc_rmw/test_plot_cross_section.sh index 5e077e97..4a6e8143 100755 --- a/metplotpy/contributed/tc_rmw/test_plot_cross_section.sh +++ b/metplotpy/contributed/tc_rmw/test_plot_cross_section.sh @@ -1,15 +1,16 @@ # ============================* -# ** Copyright UCAR (c) 2020 +# ** Copyright UCAR (c) 2025 # ** University Corporation for Atmospheric Research (UCAR) -# ** National Center for Atmospheric Research (NCAR) +# ** National Science Foundation National Center for Atmospheric Research (NSF NCAR) # ** Research Applications Lab (RAL) # ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA # ============================* +export datadir=/path/to/ +export plotdir=/path/to/plot/dir +#export filename=vertically_interpolated.nc +export filename=tc_rmw_vertical_interp.nc +export configfile=/path/to/METplotpy/metplotpy/contributed/tc_rmw/plot_cross_section.yaml -export datadir=/Users/minnawin/AF_STIG/feature_57_METplotpy_logging/METplotpy/metplotpy/contributed/tc_rmw/Data -export plotdir=/Users/minnawin/AF_STIG/feature_57_METplotpy_logging/METplotpy/metplotpy/contributed/tc_rmw/plots -export filename=vertically_interpolated.nc -export configfile=/Users/minnawin/AF_STIG/feature_57_METplotpy_logging/METplotpy/metplotpy/contributed/tc_rmw/plot_cross_section.yaml # Default is set to INFO in code, set to any other value here and add to the arguments # in the call to the plot_cross_section.py below export loglevel="ERROR" diff --git a/metplotpy/plots/util.py b/metplotpy/plots/util.py index 2664f32d..209e95ba 100644 --- a/metplotpy/plots/util.py +++ b/metplotpy/plots/util.py @@ -20,6 +20,7 @@ import re from datetime import datetime import matplotlib +import matplotlib.colors as colors import numpy as np from typing import Union import pandas as pd @@ -670,3 +671,63 @@ def strtobool(env_var:str)->bool: raise ValueError(msg) +def customize_colormap(colormap_triplet_list:list, method:str, rgb_quant_levels=256) -> matplotlib.colors.LinearSegmentedColormap: + """ + From a configuration file (dictionary representation of settings and values), + create a custom colormap from either hexidecimal triplet or RGB triplets. + + Args: + colormap_triplet_list: a list of triplet values either RGB values (0-255) or hexadecimal + method: either via RGB triplets or hexadecimal triplets + default is via RGB triplets + rgb_quant_levels [optional]: number of RGB quantization levels, default used by Matplotlib is 256 + + + Returns: + colormap: A LinearSegmentedColormap + + + """ + + colormap_name = 'custom' + + # Do some checking of the input list + if colormap_triplet_list is None: + raise ValueError("The input list of colors does not exist. ") + + if len(colormap_triplet_list) == 0: + raise ValueError("The input list of colors (RGB or hex) is empty.") + + if method == 'by_rgb': + #convert to hex + rgb_as_hex = convert_rgb_to_hex(colormap_triplet_list) + colormap = colors.LinearSegmentedColormap.from_list(colormap_name, rgb_as_hex) + + else: + # by_hex + # convert hexadecimal strings to actual hexidecimal values + colormap = colors.LinearSegmentedColormap.from_list(colormap_name, colormap_triplet_list) + + return colormap + +def convert_rgb_to_hex(rgb_list:list) -> list: + """ + Convert a list of RGB triplet values to a list of hexadecimal triplets + + Args + + rgb_list: a list of RGB triplets (integers) + + Returns + a list of hex triplets + + '""" + + hex_list = [] + + for cur_rgb in rgb_list: + triplet = (cur_rgb[0], cur_rgb[1], cur_rgb[2]) + hex_value = "#" + '%02x%02x%02x' % triplet + hex_list.append(hex_value) + + return hex_list