From 3cdabccdd14d5d2e8ad01abe35726c4516698705 Mon Sep 17 00:00:00 2001 From: Ed Safford <62339196+EdwardSafford-NOAA@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:54:49 -0400 Subject: [PATCH] Expand batch plots to support processing by level (#157) * Ref #152 Add level components, streamline level, channel, scan. * Ref #152 Rm unnecessary yaxis pseudo-data. * Revert "Ref #152" This reverts commit 40b5a49ed7b002f089cef6adcc5783c841e3727d. * Ref #152 Reduce marker psuedo data to single variable. * Ref #156 Update figure_driver to parse level in addition to channel. * Ref #156 Eliminate pycode complaints. * Ref #156 Add title mod variable for channel/level. * Ref #156 Make requested changes. * Ref #156 Fix no plots for datasets with no channels. * Ref #156 Fix code review items. --- src/eva/data/data_collections.py | 61 +++++++++++++------ .../plotting/emcpy/diagnostics/line_plot.py | 11 ++-- .../emcpy/plot_tools/figure_driver.py | 33 +++++++--- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/src/eva/data/data_collections.py b/src/eva/data/data_collections.py index 8d903760..28326ef5 100644 --- a/src/eva/data/data_collections.py +++ b/src/eva/data/data_collections.py @@ -169,7 +169,8 @@ def add_variable_to_collection(self, collection_name, group_name, variable_name, # ---------------------------------------------------------------------------------------------- - def get_variable_data_array(self, collection_name, group_name, variable_name, channels=None): + def get_variable_data_array(self, collection_name, group_name, variable_name, + channels=None, levels=None): """ Retrieve a specific variable (as a DataArray) from a collection. @@ -179,6 +180,7 @@ def get_variable_data_array(self, collection_name, group_name, variable_name, ch group_name (str): Name of the group where the variable belongs. variable_name (str): Name of the variable. channels (int or list[int]): Indices of channels to select (optional). + levels (int or list[int]): Indices of levels to select (optional). Returns: DataArray: The selected variable as an xarray DataArray. @@ -188,31 +190,49 @@ def get_variable_data_array(self, collection_name, group_name, variable_name, ch """ group_variable_name = group_name + '::' + variable_name - data_array = self._collections[collection_name][group_variable_name] - if channels is None: + if channels is None and levels is None: return data_array - elif isinstance(channels, int) or not any(not isinstance(c, int) for c in channels): - # Channel must be a dimension if it will be used for selection - if 'Channel' not in list(self._collections[collection_name].dims): - self.logger.abort(f'In get_variable_data_array channels is provided but ' + - f'Channel is not a dimension in Dataset') - # Make sure it is a list - channels_sel = [] - channels_sel.append(channels) - - # Create a new DataArray with the requested channels - data_array_channels = data_array.sel(Channel=channels_sel) - return data_array_channels - else: - self.logger.abort('In get_variable_data_array channels is neither none or list of ' + - 'integers') + if channels is not None: + if isinstance(channels, int) or not any(not isinstance(c, int) for c in channels): + # Channel must be a dimension if it will be used for selection + if 'Channel' not in list(self._collections[collection_name].dims): + self.logger.abort(f'In get_variable_data_array channels is provided but ' + + f'Channel is not a dimension in Dataset') + # Make sure it is a list + channels_sel = [] + channels_sel.append(channels) + + # Create a new DataArray with the requested channels + data_array_channels = data_array.sel(Channel=channels_sel) + return data_array_channels + else: + self.logger.abort('In get_variable_data_array channels is neither none ' + + 'nor a list of integers') + + elif levels is not None: + if isinstance(levels, int) or not any(not isinstance(lev, int) for lev in levels): + # Level must be a dimension if it will be used for selection + if 'Level' not in list(self._collections[collection_name].dims): + self.logger.abort(f'In get_variable_data_array levels is provided but ' + + f'Level is not a dimension in Dataset') + # Make sure it is a list + levels_sel = [] + levels_sel.append(levels) + + # Create a new DataArray with the requested channels + data_array_levels = data_array.sel(Level=levels_sel) + return data_array_levels + else: + self.logger.abort('In get_variable_data_array levels is neither none ' + + 'nor a list of integers') # ---------------------------------------------------------------------------------------------- - def get_variable_data(self, collection_name, group_name, variable_name, channels=None): + def get_variable_data(self, collection_name, group_name, variable_name, + channels=None, levels=None): """ Retrieve the data of a specific variable from a collection. @@ -222,13 +242,14 @@ def get_variable_data(self, collection_name, group_name, variable_name, channels group_name (str): Name of the group where the variable belongs. variable_name (str): Name of the variable. channels (int or list[int]): Indices of channels to select (optional). + levels (int or list[int]): Indices of levels to select (optional). Returns: ndarray: The selected variable data as a NumPy array. """ variable_array = self.get_variable_data_array(collection_name, group_name, variable_name, - channels) + channels, levels) # Extract the actual data array variable_data = variable_array.data diff --git a/src/eva/plotting/emcpy/diagnostics/line_plot.py b/src/eva/plotting/emcpy/diagnostics/line_plot.py index 1ea34603..8b8c2d14 100644 --- a/src/eva/plotting/emcpy/diagnostics/line_plot.py +++ b/src/eva/plotting/emcpy/diagnostics/line_plot.py @@ -58,13 +58,16 @@ def __init__(self, config, logger, dataobj): logger.abort('In Scatter comparison the first variable \'var1\' does not appear to ' + 'be in the required format of collection::group::variable.') - # Optionally get the channel to plot + # Optionally get the channel|level to plot channel = None if 'channel' in config: channel = config.get('channel') + level = None + if 'level' in config: + level = config.get('level') - xdata = dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2], channel) - ydata = dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], var1_cgv[2], channel) + xdata = dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2], channel, level) + ydata = dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], var1_cgv[2], channel, level) # see if we need to slice data xdata = slice_var_from_str(config['x'], xdata, logger) @@ -93,7 +96,7 @@ def __init__(self, config, logger, dataobj): layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', 'emcpy', 'defaults', 'line_plot.yaml')) config = get_schema(layer_schema, config, logger) - delvars = ['x', 'y', 'type', 'schema', 'channel'] + delvars = ['x', 'y', 'type', 'schema', 'channel', 'level'] for d in delvars: config.pop(d, None) self.plotobj = update_object(self.plotobj, config, logger) diff --git a/src/eva/plotting/emcpy/plot_tools/figure_driver.py b/src/eva/plotting/emcpy/plot_tools/figure_driver.py index 46e923b1..706c65e9 100644 --- a/src/eva/plotting/emcpy/plot_tools/figure_driver.py +++ b/src/eva/plotting/emcpy/plot_tools/figure_driver.py @@ -65,27 +65,40 @@ def figure_driver(config, data_collections, timing, logger): if batch_conf: # Get potential variables variables = batch_conf.get('variables', []) - # Get list of channels + + # Get list of channels and load step variables channels_str_or_list = batch_conf.get('channels', []) channels = parse_channel_list(channels_str_or_list, logger) + step_vars = channels if channels else ['none'] + step_var_name = 'channel' + title_fill = ' Ch. ' + + # Get list of levels, conditionally override step variables + levels_str_or_list = batch_conf.get('levels', []) + levels = parse_channel_list(levels_str_or_list, logger) + if levels: + step_vars = levels + step_var_name = 'level' + title_fill = ' Lev. ' + # Set some fake values to ensure the loops are entered - if variables == []: + if not variables: logger.abort("Batch Figure must provide variables, even if with channels") - if channels == []: - channels = ['none'] # Loop over variables and channels for variable in variables: - for channel in channels: + for step_var in step_vars: batch_conf_this = {} batch_conf_this['variable'] = variable + # Version to be used in titles batch_conf_this['variable_title'] = variable.replace('_', ' ').title() - channel_str = str(channel) - if channel_str != 'none': - batch_conf_this['channel'] = channel_str - var_title = batch_conf_this['variable_title'] + ' Ch. ' + channel_str + + step_var_str = str(step_var) + if step_var_str != 'none': + batch_conf_this[step_var_name] = step_var_str + var_title = batch_conf_this['variable_title'] + title_fill + step_var_str batch_conf_this['variable_title'] = var_title # Replace templated variables in figure and plots config @@ -100,6 +113,7 @@ def figure_driver(config, data_collections, timing, logger): # Make plot make_figure(figure_conf_fill, plots_conf_fill, dynamic_options_conf_fill, data_collections, logger) + else: # make just one figure per configuration make_figure(figure_conf, plots_conf, dynamic_options_conf, data_collections, logger) @@ -175,6 +189,7 @@ def make_figure(figure_conf, plots, dynamic_options, data_collections, logger): stats_helper(logger, plotobj, data_collections, value) plot_list.append(plotobj) + # create figure fig = CreateFigure(nrows=figure_conf['layout'][0], ncols=figure_conf['layout'][1],