From a47ecce84f8cbc10b1aa1d30bab3797be8295dee Mon Sep 17 00:00:00 2001 From: asewnath Date: Mon, 11 Sep 2023 16:12:45 -0400 Subject: [PATCH 01/17] bokeh batch plot --- .github/workflows/.eva-docs_dry_run.yml.swp | Bin 0 -> 12288 bytes src/eva/eva_driver.py | 12 +- src/eva/eva_interactive.py | 2 +- src/eva/plotting/bokeh/__init__.py | 9 ++ src/eva/plotting/bokeh/defaults/__init__.py | 9 ++ .../plotting/bokeh/diagonistics/__init__.py | 9 ++ src/eva/plotting/bokeh/plot_tools/__init__.py | 9 ++ .../bokeh/plot_tools/figure_driver.py | 137 ++++++++++++++++++ .../emcpy/plot_tools/figure_driver.py | 5 +- .../{hvplot => interactive}/__init__.py | 0 .../interactive_plot_tools.py | 0 11 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/.eva-docs_dry_run.yml.swp create mode 100644 src/eva/plotting/bokeh/__init__.py create mode 100644 src/eva/plotting/bokeh/defaults/__init__.py create mode 100644 src/eva/plotting/bokeh/diagonistics/__init__.py create mode 100644 src/eva/plotting/bokeh/plot_tools/__init__.py create mode 100644 src/eva/plotting/bokeh/plot_tools/figure_driver.py rename src/eva/plotting/{hvplot => interactive}/__init__.py (100%) rename src/eva/plotting/{hvplot => interactive}/interactive_plot_tools.py (100%) diff --git a/.github/workflows/.eva-docs_dry_run.yml.swp b/.github/workflows/.eva-docs_dry_run.yml.swp new file mode 100644 index 0000000000000000000000000000000000000000..4224ce25e0b621452a84ab02e57376c92db88fb0 GIT binary patch literal 12288 zcmeI2%WvaE9LHz(#H-8e#9^f$O{KJ2>}7zI^zj=LT##Pj5 zTs_#jNza&98PX$+h3r@N_vb#k_{0^)Y{`4VR_&zdtmJyq3A#L5UOp>|u~I8+%1+4@ zjg{3>)_AGqsdn5b@vdDm)8}3gz1H-Do=kELH*jR#jr#Q{7G}TWpWTTr)dsYIX#=rp z8c)yduUuOm9`(Zfll1sw`_pkcM;p)vv;l2E8_)){0c}7V&<6gu22^-~y@lCbOlSCf z`g!5hXZlrtXam}SHlPh?1KNN#pbcmP+JH8o4QK<}z<41HXW8!6)E7@Gdw8eef!{1m?gW zbBuilz6PIxPr>`(5Y)jYxC|Zze?7$5=ioLF;1JZo6>#@K#=Zi5@CtYdY=a*kVC+lq z4!8wYz@M1!58w{?04UG|iH8=@NgL1xv;l2E8_)){fq!R!#%ZwhEE|6rKMZE%5j7%P zIPIF<;TDBa;BYBTF1qIC&PMI}je2e4W_3nAPlU0O7M;u}i+IzbqRYG755nS8Ga|C9 zmvzszDEqP(>_uLy#Us;mXH|-L7@*zkvgXW&gP!0Kg0`>kRyGVzC>{yhp9r;kwx?)x z2h_A32XvcVe-apO}M?z~`nPv=*mEyOf(>5~!vpMCEawG_wYz+4O`v zZjZ`q;~-ODHK<%J!(#4QY7wiN>+JL*=SNvI3?m7rT*{XpW>ckIg^63VaFUJ7lu6Fu z@LaXGDic*b_nh@c+b!2=GUHQqvjI&g3!d^(GzlQW#8MX zk_K4QnLEk%q}stw%>>KbPLfXKh03Bg(^2$rDcMS&W65!x(*2lTS2ji!*1N02AcGQ1 z7Ny68vM#duOE*Fl+W!4ap2wZO9vbDtNz;Rk$ zE&k(>C0B81Bo)`Y*Cx8!<%;5vYA7;~#+?jD%bj*W^SM_|d0}yww{Y^}*4~Z%&H7ID zg#unEvef3bD@%i=d0Jbeg|xI}-h*igf-svly9q9lxczaH_|V~DdDT;ezKa9>sr PHmH&}MTyLvSg^kV(^y6i literal 0 HcmV?d00001 diff --git a/src/eva/eva_driver.py b/src/eva/eva_driver.py index 546a1dab..e2b630b1 100644 --- a/src/eva/eva_driver.py +++ b/src/eva/eva_driver.py @@ -14,7 +14,8 @@ from eva.utilities.timing import Timing from eva.data.data_driver import data_driver from eva.transforms.transform_driver import transform_driver -from eva.plotting.emcpy.plot_tools.figure_driver import figure_driver +from eva.plotting.emcpy.plot_tools.figure_driver import emcpy_figure_driver +from eva.plotting.bokeh.plot_tools.figure_driver import bokeh_figure_driver from eva.data.data_collections import DataCollections from eva.utilities.utils import load_yaml_file import argparse @@ -80,9 +81,16 @@ def eva(eva_config, eva_logger=None): timing.stop('TransformDriverExecute') # Generate figure(s) + # May want a separate function handling the "inner working" of figure driver selection + plotting_backend = eva_dict['graphics']['plotting_backend'] logger.info(f'Running figure driver') timing.start('FigureDriverExecute') - figure_driver(eva_dict, data_collections, timing, logger) + if plotting_backend == 'emcpy': + emcpy_figure_driver(eva_dict, data_collections, timing, logger) + elif plotting_backend == 'bokeh': + bokeh_figure_driver(eva_dict, data_collections, timing, logger) + else: + print("throw error") timing.stop('FigureDriverExecute') timing.finalize() diff --git a/src/eva/eva_interactive.py b/src/eva/eva_interactive.py index 37b84111..0112b31c 100644 --- a/src/eva/eva_interactive.py +++ b/src/eva/eva_interactive.py @@ -23,7 +23,7 @@ from eva.transforms.arithmetic import arithmetic, generate_arithmetic_config from eva.transforms.accept_where import accept_where, generate_accept_where_config -import eva.plotting.hvplot.interactive_plot_tools as plot +import eva.plotting.interactive.interactive_plot_tools as plot # -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/bokeh/__init__.py b/src/eva/plotting/bokeh/__init__.py new file mode 100644 index 00000000..ebda1763 --- /dev/null +++ b/src/eva/plotting/bokeh/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2023 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +import os + +repo_directory = os.path.dirname(__file__) diff --git a/src/eva/plotting/bokeh/defaults/__init__.py b/src/eva/plotting/bokeh/defaults/__init__.py new file mode 100644 index 00000000..ebda1763 --- /dev/null +++ b/src/eva/plotting/bokeh/defaults/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2023 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +import os + +repo_directory = os.path.dirname(__file__) diff --git a/src/eva/plotting/bokeh/diagonistics/__init__.py b/src/eva/plotting/bokeh/diagonistics/__init__.py new file mode 100644 index 00000000..ebda1763 --- /dev/null +++ b/src/eva/plotting/bokeh/diagonistics/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2023 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +import os + +repo_directory = os.path.dirname(__file__) diff --git a/src/eva/plotting/bokeh/plot_tools/__init__.py b/src/eva/plotting/bokeh/plot_tools/__init__.py new file mode 100644 index 00000000..ebda1763 --- /dev/null +++ b/src/eva/plotting/bokeh/plot_tools/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2023 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +import os + +repo_directory = os.path.dirname(__file__) diff --git a/src/eva/plotting/bokeh/plot_tools/figure_driver.py b/src/eva/plotting/bokeh/plot_tools/figure_driver.py new file mode 100644 index 00000000..53280c8c --- /dev/null +++ b/src/eva/plotting/bokeh/plot_tools/figure_driver.py @@ -0,0 +1,137 @@ +# (C) Copyright 2023 NOAA/NWS/EMC +# +# (C) Copyright 2023 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + + +# -------------------------------------------------------------------------------------------------- + +from eva.utilities.utils import replace_vars_dict, parse_channel_list +from bokeh.plotting import figure, output_file, save +import copy +import os + +# -------------------------------------------------------------------------------------------------- + +def bokeh_figure_driver(config, data_collections, timing, logger): + """ + Generates and saves multiple figures based on the provided configuration. + + Args: + config (dict): A dictionary containing the configuration for generating figures. + data_collections (DataCollections): An instance of the DataCollections class containing + input data. + timing (Timing): A timing instance to measure the execution time. + logger (Logger): An instance of the logger for logging messages. + + This function generates and saves multiple figures based on the provided configuration. It + processes each graphic specified in the configuration and creates corresponding figures with + plots. This figure driver uses the bokeh backend to create interactive plots. + """ + + # Get list of graphics from configuration + # ------------------- + graphics_section = config.get("graphics") + graphics = graphics_section.get("figure_list") + + # Loop through specified graphics + # ------------------- + timing.start('Graphics Loop') + for graphic in graphics: + + # Parse configuration for this graphic + # ------------------- + batch_conf = graphic.get("batch figure", {}) # batch configuration (default nothing) + figure_conf = graphic.get("figure") # figure configuration + plot_conf = graphic.get("plot") + #dynamic_options_conf = graphic.get("dynamic options", []) # Dynamic overwrites + + # update figure conf based on schema - later + # ---------------------------------- + + # --------------------------------------- + if batch_conf: + # Get potential variables + variables = batch_conf.get('variables', []) + # Get list of channels + channels_str_or_list = batch_conf.get('channels', []) + channels = parse_channel_list(channels_str_or_list, logger) + + # Set some fake values to ensure the loops are entered + if 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: + 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 + batch_conf_this['variable_title'] = var_title + + # Replace templated variables in figure and plots config + figure_conf_fill = copy.copy(figure_conf) + figure_conf_fill = replace_vars_dict(figure_conf_fill, **batch_conf_this) + plot_conf_fill = copy.copy(plot_conf) + plot_conf_fill = replace_vars_dict(plot_conf_fill, **batch_conf_this) + #dynamic_options_conf_fill = copy.copy(dynamic_options_conf) + #dynamic_options_conf_fill = replace_vars_dict(dynamic_options_conf_fill, + # **batch_conf_this) + + # Make plot + #make_figure(figure_conf_fill, plots_conf_fill, + # dynamic_options_conf_fill, data_collections, logger) + make_figure(figure_conf_fill, plot_conf_fill, data_collections, logger) + else: + # make just one figure per configuration + #make_figure(figure_conf, plots_conf, dynamic_options_conf, data_collections, logger) + make_figure(figure_conf, plot_conf, data_collections, logger) + timing.stop('Graphics Loop') + +# -------------------------------------------------------------------------------------------------- + +def make_figure(figure_conf, plot, data_collections, logger): + """ + Generates a figure based on the provided configuration and plots. + + Args: + figure_conf (dict): A dictionary containing the configuration for the figure layout + and appearance. + plots (list): A list of dictionaries containing plot configurations. + data_collections (DataCollections): An instance of the DataCollections class containing + input data. + logger (Logger): An instance of the logger for logging messages. + + This function generates a figure based on the provided configuration and plot settings. It + processes the specified plots, applies dynamic options, and saves the generated figure. + """ + + # set up figure + fig = figure(title = figure_conf['title'], + x_axis_label = plot['x_axis_label'], + y_axis_label = plot['y_axis_label']) + + # retrieve variables from data collection + x_args = plot['x']['variable'].split('::') + y_args = plot['y']['variable'].split('::') + x = data_collections.get_variable_data(x_args[0], x_args[1], x_args[2]) + y = data_collections.get_variable_data(y_args[0], y_args[1], y_args[2]) + fig.scatter(x, y) + + output_name = figure_conf['output name'] + dir_path = os.path.dirname(output_name) + if not os.path.exists(dir_path): + os.makedirs(dir_path) + output_file(output_name) + save(fig) diff --git a/src/eva/plotting/emcpy/plot_tools/figure_driver.py b/src/eva/plotting/emcpy/plot_tools/figure_driver.py index 46e923b1..d78323ba 100644 --- a/src/eva/plotting/emcpy/plot_tools/figure_driver.py +++ b/src/eva/plotting/emcpy/plot_tools/figure_driver.py @@ -22,7 +22,7 @@ # -------------------------------------------------------------------------------------------------- -def figure_driver(config, data_collections, timing, logger): +def emcpy_figure_driver(config, data_collections, timing, logger): """ Generates and saves multiple figures based on the provided configuration. @@ -40,7 +40,8 @@ def figure_driver(config, data_collections, timing, logger): # Get list of graphics from configuration # ------------------- - graphics = config.get("graphics") + graphics_section = config.get("graphics") + graphics = graphics_section.get("figure_list") # Loop through specified graphics # ------------------- diff --git a/src/eva/plotting/hvplot/__init__.py b/src/eva/plotting/interactive/__init__.py similarity index 100% rename from src/eva/plotting/hvplot/__init__.py rename to src/eva/plotting/interactive/__init__.py diff --git a/src/eva/plotting/hvplot/interactive_plot_tools.py b/src/eva/plotting/interactive/interactive_plot_tools.py similarity index 100% rename from src/eva/plotting/hvplot/interactive_plot_tools.py rename to src/eva/plotting/interactive/interactive_plot_tools.py From 0b92c20c485cb0bb8dc7db56d30093c0816a3760 Mon Sep 17 00:00:00 2001 From: asewnath Date: Tue, 12 Sep 2023 14:57:39 -0400 Subject: [PATCH 02/17] bokeh components --- .../bokeh/plot_tools/figure_driver.py | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/eva/plotting/bokeh/plot_tools/figure_driver.py b/src/eva/plotting/bokeh/plot_tools/figure_driver.py index 53280c8c..025df6ac 100644 --- a/src/eva/plotting/bokeh/plot_tools/figure_driver.py +++ b/src/eva/plotting/bokeh/plot_tools/figure_driver.py @@ -12,8 +12,10 @@ from eva.utilities.utils import replace_vars_dict, parse_channel_list from bokeh.plotting import figure, output_file, save +from bokeh.embed import components import copy import os +import pickle as pkl # -------------------------------------------------------------------------------------------------- @@ -127,11 +129,29 @@ def make_figure(figure_conf, plot, data_collections, logger): y_args = plot['y']['variable'].split('::') x = data_collections.get_variable_data(x_args[0], x_args[1], x_args[2]) y = data_collections.get_variable_data(y_args[0], y_args[1], y_args[2]) + + # TODO handle this in a case/factory fig.scatter(x, y) - output_name = figure_conf['output name'] - dir_path = os.path.dirname(output_name) - if not os.path.exists(dir_path): - os.makedirs(dir_path) - output_file(output_name) + comp_name = figure_conf['components name'] + html_name = figure_conf['html name'] + + # create directories if they don't exist + html_dir_path = os.path.dirname(html_name) + if not os.path.exists(html_dir_path): + os.makedirs(html_dir_path) + + comp_dir_path = os.path.dirname(comp_name) + if not os.path.exists(comp_dir_path): + os.makedirs(comp_dir_path) + + # save html, div, script to file + script, div = components(fig) + dictionary = {'script': script, + 'div': div} + + with open(comp_name, 'wb') as f: + pkl.dump(dictionary, f) + + output_file(html_name) save(fig) From 0c70334ea7e693e6f3ae14ab6259dd2257ee837f Mon Sep 17 00:00:00 2001 From: asewnath Date: Mon, 18 Sep 2023 16:09:49 -0400 Subject: [PATCH 03/17] first pass diag inherit --- src/eva/eva_driver.py | 10 +- src/eva/plotting/{bokeh => batch}/__init__.py | 0 .../defaults => batch/base}/__init__.py | 0 .../base/diagnostics}/__init__.py | 0 .../base}/diagnostics/density.py | 0 .../base}/diagnostics/histogram.py | 0 .../base}/diagnostics/horizontal_line.py | 0 .../base}/diagnostics/line_plot.py | 0 .../base}/diagnostics/map_gridded.py | 0 .../base}/diagnostics/map_scatter.py | 0 .../batch/base/diagnostics/scatter.py | 105 +++++++ .../base}/diagnostics/vertical_line.py | 0 .../base/plot_tools}/__init__.py | 0 .../base}/plot_tools/dynamic_config.py | 0 .../batch/base/plot_tools/figure_driver.py | 274 ++++++++++++++++++ .../plot_tools => batch/emcpy}/__init__.py | 0 .../{ => batch}/emcpy/defaults/density.yaml | 0 .../{ => batch}/emcpy/defaults/figure.yaml | 0 .../{ => batch}/emcpy/defaults/histogram.yaml | 0 .../emcpy/defaults/horizontal_line.yaml | 0 .../{ => batch}/emcpy/defaults/line_plot.yaml | 0 .../emcpy/defaults/map_gridded.yaml | 0 .../emcpy/defaults/map_scatter.yaml | 0 .../{ => batch}/emcpy/defaults/scatter.yaml | 0 .../emcpy/defaults/scatter_density.yaml | 0 .../emcpy/defaults/vertical_line.yaml | 0 .../batch/emcpy/diagnostics/__init__.py | 9 + .../batch/emcpy/diagnostics/density.py | 90 ++++++ .../batch/emcpy/diagnostics/emcpy_scatter.py | 33 +++ .../batch/emcpy/diagnostics/histogram.py | 90 ++++++ .../emcpy/diagnostics/horizontal_line.py | 61 ++++ .../emcpy/diagnostics/line_plot.py} | 34 +-- .../batch/emcpy/diagnostics/map_gridded.py | 70 +++++ .../batch/emcpy/diagnostics/map_scatter.py | 83 ++++++ .../batch/emcpy/diagnostics/vertical_line.py | 61 ++++ .../batch/emcpy/plot_tools/__init__.py | 9 + .../batch/emcpy/plot_tools/dynamic_config.py | 203 +++++++++++++ .../emcpy/plot_tools/figure_driver.py | 0 .../diagonistics => batch/hvplot}/__init__.py | 0 .../hvplot/defaults}/__init__.py | 0 .../batch/hvplot/diagonistics/__init__.py | 9 + .../batch/hvplot/plot_tools/__init__.py | 9 + .../hvplot}/plot_tools/figure_driver.py | 0 43 files changed, 1124 insertions(+), 26 deletions(-) rename src/eva/plotting/{bokeh => batch}/__init__.py (100%) rename src/eva/plotting/{bokeh/defaults => batch/base}/__init__.py (100%) rename src/eva/plotting/{emcpy => batch/base/diagnostics}/__init__.py (100%) rename src/eva/plotting/{emcpy => batch/base}/diagnostics/density.py (100%) rename src/eva/plotting/{emcpy => batch/base}/diagnostics/histogram.py (100%) rename src/eva/plotting/{emcpy => batch/base}/diagnostics/horizontal_line.py (100%) rename src/eva/plotting/{emcpy => batch/base}/diagnostics/line_plot.py (100%) rename src/eva/plotting/{emcpy => batch/base}/diagnostics/map_gridded.py (100%) rename src/eva/plotting/{emcpy => batch/base}/diagnostics/map_scatter.py (100%) create mode 100644 src/eva/plotting/batch/base/diagnostics/scatter.py rename src/eva/plotting/{emcpy => batch/base}/diagnostics/vertical_line.py (100%) rename src/eva/plotting/{emcpy/diagnostics => batch/base/plot_tools}/__init__.py (100%) rename src/eva/plotting/{emcpy => batch/base}/plot_tools/dynamic_config.py (100%) create mode 100644 src/eva/plotting/batch/base/plot_tools/figure_driver.py rename src/eva/plotting/{emcpy/plot_tools => batch/emcpy}/__init__.py (100%) rename src/eva/plotting/{ => batch}/emcpy/defaults/density.yaml (100%) rename src/eva/plotting/{ => batch}/emcpy/defaults/figure.yaml (100%) rename src/eva/plotting/{ => batch}/emcpy/defaults/histogram.yaml (100%) rename src/eva/plotting/{ => batch}/emcpy/defaults/horizontal_line.yaml (100%) rename src/eva/plotting/{ => batch}/emcpy/defaults/line_plot.yaml (100%) rename src/eva/plotting/{ => batch}/emcpy/defaults/map_gridded.yaml (100%) rename src/eva/plotting/{ => batch}/emcpy/defaults/map_scatter.yaml (100%) rename src/eva/plotting/{ => batch}/emcpy/defaults/scatter.yaml (100%) rename src/eva/plotting/{ => batch}/emcpy/defaults/scatter_density.yaml (100%) rename src/eva/plotting/{ => batch}/emcpy/defaults/vertical_line.yaml (100%) create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/__init__.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/density.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/histogram.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py rename src/eva/plotting/{emcpy/diagnostics/scatter.py => batch/emcpy/diagnostics/line_plot.py} (72%) create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py create mode 100644 src/eva/plotting/batch/emcpy/plot_tools/__init__.py create mode 100644 src/eva/plotting/batch/emcpy/plot_tools/dynamic_config.py rename src/eva/plotting/{ => batch}/emcpy/plot_tools/figure_driver.py (100%) rename src/eva/plotting/{bokeh/diagonistics => batch/hvplot}/__init__.py (100%) rename src/eva/plotting/{bokeh/plot_tools => batch/hvplot/defaults}/__init__.py (100%) create mode 100644 src/eva/plotting/batch/hvplot/diagonistics/__init__.py create mode 100644 src/eva/plotting/batch/hvplot/plot_tools/__init__.py rename src/eva/plotting/{bokeh => batch/hvplot}/plot_tools/figure_driver.py (100%) diff --git a/src/eva/eva_driver.py b/src/eva/eva_driver.py index e2b630b1..2f8d3945 100644 --- a/src/eva/eva_driver.py +++ b/src/eva/eva_driver.py @@ -14,8 +14,7 @@ from eva.utilities.timing import Timing from eva.data.data_driver import data_driver from eva.transforms.transform_driver import transform_driver -from eva.plotting.emcpy.plot_tools.figure_driver import emcpy_figure_driver -from eva.plotting.bokeh.plot_tools.figure_driver import bokeh_figure_driver +from eva.plotting.batch.base.plot_tools.figure_driver import figure_driver from eva.data.data_collections import DataCollections from eva.utilities.utils import load_yaml_file import argparse @@ -85,12 +84,7 @@ def eva(eva_config, eva_logger=None): plotting_backend = eva_dict['graphics']['plotting_backend'] logger.info(f'Running figure driver') timing.start('FigureDriverExecute') - if plotting_backend == 'emcpy': - emcpy_figure_driver(eva_dict, data_collections, timing, logger) - elif plotting_backend == 'bokeh': - bokeh_figure_driver(eva_dict, data_collections, timing, logger) - else: - print("throw error") + figure_driver(eva_dict, data_collections, timing, logger) timing.stop('FigureDriverExecute') timing.finalize() diff --git a/src/eva/plotting/bokeh/__init__.py b/src/eva/plotting/batch/__init__.py similarity index 100% rename from src/eva/plotting/bokeh/__init__.py rename to src/eva/plotting/batch/__init__.py diff --git a/src/eva/plotting/bokeh/defaults/__init__.py b/src/eva/plotting/batch/base/__init__.py similarity index 100% rename from src/eva/plotting/bokeh/defaults/__init__.py rename to src/eva/plotting/batch/base/__init__.py diff --git a/src/eva/plotting/emcpy/__init__.py b/src/eva/plotting/batch/base/diagnostics/__init__.py similarity index 100% rename from src/eva/plotting/emcpy/__init__.py rename to src/eva/plotting/batch/base/diagnostics/__init__.py diff --git a/src/eva/plotting/emcpy/diagnostics/density.py b/src/eva/plotting/batch/base/diagnostics/density.py similarity index 100% rename from src/eva/plotting/emcpy/diagnostics/density.py rename to src/eva/plotting/batch/base/diagnostics/density.py diff --git a/src/eva/plotting/emcpy/diagnostics/histogram.py b/src/eva/plotting/batch/base/diagnostics/histogram.py similarity index 100% rename from src/eva/plotting/emcpy/diagnostics/histogram.py rename to src/eva/plotting/batch/base/diagnostics/histogram.py diff --git a/src/eva/plotting/emcpy/diagnostics/horizontal_line.py b/src/eva/plotting/batch/base/diagnostics/horizontal_line.py similarity index 100% rename from src/eva/plotting/emcpy/diagnostics/horizontal_line.py rename to src/eva/plotting/batch/base/diagnostics/horizontal_line.py diff --git a/src/eva/plotting/emcpy/diagnostics/line_plot.py b/src/eva/plotting/batch/base/diagnostics/line_plot.py similarity index 100% rename from src/eva/plotting/emcpy/diagnostics/line_plot.py rename to src/eva/plotting/batch/base/diagnostics/line_plot.py diff --git a/src/eva/plotting/emcpy/diagnostics/map_gridded.py b/src/eva/plotting/batch/base/diagnostics/map_gridded.py similarity index 100% rename from src/eva/plotting/emcpy/diagnostics/map_gridded.py rename to src/eva/plotting/batch/base/diagnostics/map_gridded.py diff --git a/src/eva/plotting/emcpy/diagnostics/map_scatter.py b/src/eva/plotting/batch/base/diagnostics/map_scatter.py similarity index 100% rename from src/eva/plotting/emcpy/diagnostics/map_scatter.py rename to src/eva/plotting/batch/base/diagnostics/map_scatter.py diff --git a/src/eva/plotting/batch/base/diagnostics/scatter.py b/src/eva/plotting/batch/base/diagnostics/scatter.py new file mode 100644 index 00000000..beaacd40 --- /dev/null +++ b/src/eva/plotting/batch/base/diagnostics/scatter.py @@ -0,0 +1,105 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object, slice_var_from_str +#import emcpy.plots.plots +import os +import numpy as np + +from abc import ABC, abstractmethod + +# -------------------------------------------------------------------------------------------------- + + +class Scatter(ABC): + + """Base class for creating scatter plots.""" + + def __init__(self, config, logger, dataobj): + + """ + Creates a scatter plot on a map based on the provided configuration. + + Args: + config (dict): A dictionary containing the configuration for the scatter plot on a map. + logger (Logger): An instance of the logger for logging messages. + dataobj: An instance of the data object containing input data. + + This class initializes and configures a scatter plot on a map based on the provided + configuration. The scatter plot is created using a declarative plotting library from EMCPy + (https://github.com/NOAA-EMC/emcpy). + + Example: + + :: + + config = { + "longitude": {"variable": "collection::group::variable"}, + "latitude": {"variable": "collection::group::variable"}, + "data": {"variable": "collection::group::variable", + "channel": "channel_name"}, + "plot_property": "property_value", + "plot_option": "option_value", + "schema": "path_to_schema_file.yaml" + } + logger = Logger() + map_scatter_plot = MapScatter(config, logger, None) + """ + self.config = config + self.logger = logger + self.dataobj = dataobj + self.xdata = [] + self.ydata = [] + self.plotobj = None + +# -------------------------------------------------------------------------------------------------- + + def data_prep(self): + + # Get the data to plot from the data_collection + # --------------------------------------------- + var0 = self.config['x']['variable'] + var1 = self.config['y']['variable'] + + var0_cgv = var0.split('::') + var1_cgv = var1.split('::') + + if len(var0_cgv) != 3: + self.logger.abort('In Scatter comparison the first variable \'var0\' does not appear to ' + + 'be in the required format of collection::group::variable.') + if len(var1_cgv) != 3: + self.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 + channel = None + if 'channel' in self.config: + channel = self.config.get('channel') + + xdata = self.dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2], channel) + xdata1 = self.dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2]) + ydata = self.dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], var1_cgv[2], channel) + + # see if we need to slice data + xdata = slice_var_from_str(self.config['x'], xdata, self.logger) + ydata = slice_var_from_str(self.config['y'], ydata, self.logger) + + # scatter data should be flattened + xdata = xdata.flatten() + ydata = ydata.flatten() + + # Remove NaN values to enable regression + # -------------------------------------- + mask = ~np.isnan(xdata) + xdata = xdata[mask] + ydata = ydata[mask] + + mask = ~np.isnan(ydata) + self.xdata = xdata[mask] + self.ydata = ydata[mask] + + + @abstractmethod + def configure_plot(self): + pass + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/emcpy/diagnostics/vertical_line.py b/src/eva/plotting/batch/base/diagnostics/vertical_line.py similarity index 100% rename from src/eva/plotting/emcpy/diagnostics/vertical_line.py rename to src/eva/plotting/batch/base/diagnostics/vertical_line.py diff --git a/src/eva/plotting/emcpy/diagnostics/__init__.py b/src/eva/plotting/batch/base/plot_tools/__init__.py similarity index 100% rename from src/eva/plotting/emcpy/diagnostics/__init__.py rename to src/eva/plotting/batch/base/plot_tools/__init__.py diff --git a/src/eva/plotting/emcpy/plot_tools/dynamic_config.py b/src/eva/plotting/batch/base/plot_tools/dynamic_config.py similarity index 100% rename from src/eva/plotting/emcpy/plot_tools/dynamic_config.py rename to src/eva/plotting/batch/base/plot_tools/dynamic_config.py diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py new file mode 100644 index 00000000..4f2c5b9c --- /dev/null +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -0,0 +1,274 @@ +# (C) Copyright 2021-2022 NOAA/NWS/EMC +# +# (C) Copyright 2021-2022 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + + +# -------------------------------------------------------------------------------------------------- + +from eva.eva_path import return_eva_path +from eva.utilities.stats import stats_helper +from eva.utilities.utils import get_schema, camelcase_to_underscore, parse_channel_list +from eva.utilities.utils import replace_vars_dict +from emcpy.plots.create_plots import CreatePlot, CreateFigure +import copy +import importlib as im +import os + +# -------------------------------------------------------------------------------------------------- + + +def figure_driver(config, data_collections, timing, logger): + """ + Generates and saves multiple figures based on the provided configuration. + + Args: + config (dict): A dictionary containing the configuration for generating figures. + data_collections (DataCollections): An instance of the DataCollections class containing + input data. + timing (Timing): A timing instance to measure the execution time. + logger (Logger): An instance of the logger for logging messages. + + This function generates and saves multiple figures based on the provided configuration. It + processes each graphic specified in the configuration and creates corresponding figures with + plots. + """ + + # Get list of graphics from configuration + # ------------------- + graphics_section = config.get("graphics") + graphics = graphics_section.get("figure_list") + + # Loop through specified graphics + # ------------------- + timing.start('Graphics Loop') + for graphic in graphics: + + # Parse configuration for this graphic + # ------------------- + batch_conf = graphic.get("batch figure", {}) # batch configuration (default nothing) + figure_conf = graphic.get("figure") # figure configuration + plots_conf = graphic.get("plots") # list of plots/subplots + dynamic_options_conf = graphic.get("dynamic options", []) # Dynamic overwrites + + # update figure conf based on schema + # ---------------------------------- + fig_schema = figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', + 'emcpy', 'defaults', 'figure.yaml')) + figure_conf = get_schema(fig_schema, figure_conf, logger) + + # pass configurations and make graphic(s) + # --------------------------------------- + if batch_conf: + # Get potential variables + variables = batch_conf.get('variables', []) + # Get list of channels + channels_str_or_list = batch_conf.get('channels', []) + channels = parse_channel_list(channels_str_or_list, logger) + + # Set some fake values to ensure the loops are entered + if 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: + 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 + batch_conf_this['variable_title'] = var_title + + # Replace templated variables in figure and plots config + figure_conf_fill = copy.copy(figure_conf) + figure_conf_fill = replace_vars_dict(figure_conf_fill, **batch_conf_this) + plots_conf_fill = copy.copy(plots_conf) + plots_conf_fill = replace_vars_dict(plots_conf_fill, **batch_conf_this) + dynamic_options_conf_fill = copy.copy(dynamic_options_conf) + dynamic_options_conf_fill = replace_vars_dict(dynamic_options_conf_fill, + **batch_conf_this) + + # 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) + timing.stop('Graphics Loop') + + +# -------------------------------------------------------------------------------------------------- + + +def make_figure(figure_conf, plots, dynamic_options, data_collections, logger): + """ + Generates a figure based on the provided configuration and plots. + + Args: + figure_conf (dict): A dictionary containing the configuration for the figure layout + and appearance. + plots (list): A list of dictionaries containing plot configurations. + dynamic_options (list): A list of dictionaries containing dynamic configuration options. + data_collections (DataCollections): An instance of the DataCollections class containing + input data. + logger (Logger): An instance of the logger for logging messages. + + This function generates a figure based on the provided configuration and plot settings. It + processes the specified plots, applies dynamic options, and saves the generated figure. + """ + + # Adjust the plots configs if there are dynamic options + # ----------------------------------------------------- + for dynamic_option in dynamic_options: + dynamic_option_module = im.import_module("eva.plotting.emcpy.plot_tools.dynamic_config") + dynamic_option_method = getattr(dynamic_option_module, dynamic_option['type']) + plots = dynamic_option_method(logger, dynamic_option, plots, data_collections) + + # Grab some figure configuration + # ------------------- + figure_layout = figure_conf.get("layout") + file_type = figure_conf.get("figure file type", "png") + output_file = get_output_file(figure_conf) + + # Set up layers and plots + plot_list = [] + for plot in plots: + layer_list = [] + for layer in plot.get("layers"): + eva_class_name = layer.get("type") + eva_class_name = 'Emcpy' + eva_class_name + eva_module_name = camelcase_to_underscore(eva_class_name) + full_module = "eva.plotting.batch.emcpy.diagnostics."+eva_module_name + layer_class = getattr(im.import_module(full_module), eva_class_name) + layer = layer_class(layer, logger, data_collections) + layer.data_prep() + # use the translator class to go from eva to declarative plotting + #layer_list.append(layer_class(layer, logger, data_collections).plotobj) + layer_list.append(layer.configure_plot()) + # get mapping dictionary + proj = None + domain = None + if 'mapping' in plot.keys(): + mapoptions = plot.get('mapping') + # TODO make this configurable and not hard coded + proj = mapoptions['projection'] + domain = mapoptions['domain'] + + # create a subplot based on specified layers + plotobj = CreatePlot(plot_layers=layer_list, projection=proj, domain=domain) + # make changes to subplot based on YAML configuration + for key, value in plot.items(): + if key not in ['layers', 'mapping', 'statistics']: + if isinstance(value, dict): + getattr(plotobj, key)(**value) + elif value is None: + getattr(plotobj, key)() + else: + getattr(plotobj, key)(value) + if key in ['statistics']: + # call the stats helper + 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], + figsize=tuple(figure_conf['figure size'])) + fig.plot_list = plot_list + fig.create_figure() + + if 'title' in figure_conf: + fig.add_suptitle(figure_conf['title']) + if 'tight layout' in figure_conf: + if isinstance(figure_conf['tight layout'], dict): + fig.tight_layout(**figure_conf['tight layout']) + else: + fig.tight_layout() + if 'plot logo' in figure_conf: + fig.plot_logo(**figure_conf['plot logo']) + + saveargs = get_saveargs(figure_conf) + fig.save_figure(output_file, **saveargs) + + fig.close_figure() + + +# -------------------------------------------------------------------------------------------------- + + +def get_saveargs(figure_conf): + """ + Gets arguments for saving a figure based on the provided configuration. + + Args: + figure_conf (dict): A dictionary containing the figure configuration. + + Returns: + out_conf (dict): A dictionary containing arguments for saving the figure. + + This function extracts relevant arguments from the provided figure configuration to be used + for saving the generated figure. + + Example: + :: + + figure_conf = { + "layout": [2, 2], + "figure file type": "png", + "output path": "./output_folder", + "output name": "example_figure" + } + save_args = get_saveargs(figure_conf) + """ + + out_conf = figure_conf + delvars = ['layout', 'figure file type', 'output path', 'figure size', 'title'] + out_conf['format'] = figure_conf['figure file type'] + for d in delvars: + del out_conf[d] + return out_conf + + +# -------------------------------------------------------------------------------------------------- + + +def get_output_file(figure_conf): + """ + Gets the output file path for saving the figure. + + Args: + figure_conf (dict): A dictionary containing the figure configuration. + + Returns: + output_file (str): The complete path for saving the figure. + + This function constructs the complete file path for saving the generated figure based on the + provided figure configuration. + + Example: + :: + + figure_conf = { + "output path": "./output_folder", + "output name": "example_figure" + } + output_file = get_output_file(figure_conf) + """ + + file_path = figure_conf.get("output path", "./") + output_name = figure_conf.get("output name", "") + output_file = os.path.join(file_path, output_name) + return output_file + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/emcpy/plot_tools/__init__.py b/src/eva/plotting/batch/emcpy/__init__.py similarity index 100% rename from src/eva/plotting/emcpy/plot_tools/__init__.py rename to src/eva/plotting/batch/emcpy/__init__.py diff --git a/src/eva/plotting/emcpy/defaults/density.yaml b/src/eva/plotting/batch/emcpy/defaults/density.yaml similarity index 100% rename from src/eva/plotting/emcpy/defaults/density.yaml rename to src/eva/plotting/batch/emcpy/defaults/density.yaml diff --git a/src/eva/plotting/emcpy/defaults/figure.yaml b/src/eva/plotting/batch/emcpy/defaults/figure.yaml similarity index 100% rename from src/eva/plotting/emcpy/defaults/figure.yaml rename to src/eva/plotting/batch/emcpy/defaults/figure.yaml diff --git a/src/eva/plotting/emcpy/defaults/histogram.yaml b/src/eva/plotting/batch/emcpy/defaults/histogram.yaml similarity index 100% rename from src/eva/plotting/emcpy/defaults/histogram.yaml rename to src/eva/plotting/batch/emcpy/defaults/histogram.yaml diff --git a/src/eva/plotting/emcpy/defaults/horizontal_line.yaml b/src/eva/plotting/batch/emcpy/defaults/horizontal_line.yaml similarity index 100% rename from src/eva/plotting/emcpy/defaults/horizontal_line.yaml rename to src/eva/plotting/batch/emcpy/defaults/horizontal_line.yaml diff --git a/src/eva/plotting/emcpy/defaults/line_plot.yaml b/src/eva/plotting/batch/emcpy/defaults/line_plot.yaml similarity index 100% rename from src/eva/plotting/emcpy/defaults/line_plot.yaml rename to src/eva/plotting/batch/emcpy/defaults/line_plot.yaml diff --git a/src/eva/plotting/emcpy/defaults/map_gridded.yaml b/src/eva/plotting/batch/emcpy/defaults/map_gridded.yaml similarity index 100% rename from src/eva/plotting/emcpy/defaults/map_gridded.yaml rename to src/eva/plotting/batch/emcpy/defaults/map_gridded.yaml diff --git a/src/eva/plotting/emcpy/defaults/map_scatter.yaml b/src/eva/plotting/batch/emcpy/defaults/map_scatter.yaml similarity index 100% rename from src/eva/plotting/emcpy/defaults/map_scatter.yaml rename to src/eva/plotting/batch/emcpy/defaults/map_scatter.yaml diff --git a/src/eva/plotting/emcpy/defaults/scatter.yaml b/src/eva/plotting/batch/emcpy/defaults/scatter.yaml similarity index 100% rename from src/eva/plotting/emcpy/defaults/scatter.yaml rename to src/eva/plotting/batch/emcpy/defaults/scatter.yaml diff --git a/src/eva/plotting/emcpy/defaults/scatter_density.yaml b/src/eva/plotting/batch/emcpy/defaults/scatter_density.yaml similarity index 100% rename from src/eva/plotting/emcpy/defaults/scatter_density.yaml rename to src/eva/plotting/batch/emcpy/defaults/scatter_density.yaml diff --git a/src/eva/plotting/emcpy/defaults/vertical_line.yaml b/src/eva/plotting/batch/emcpy/defaults/vertical_line.yaml similarity index 100% rename from src/eva/plotting/emcpy/defaults/vertical_line.yaml rename to src/eva/plotting/batch/emcpy/defaults/vertical_line.yaml diff --git a/src/eva/plotting/batch/emcpy/diagnostics/__init__.py b/src/eva/plotting/batch/emcpy/diagnostics/__init__.py new file mode 100644 index 00000000..ac1c0bc4 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2021-2022 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +import os + +repo_directory = os.path.dirname(__file__) diff --git a/src/eva/plotting/batch/emcpy/diagnostics/density.py b/src/eva/plotting/batch/emcpy/diagnostics/density.py new file mode 100644 index 00000000..612444db --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/density.py @@ -0,0 +1,90 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object, slice_var_from_str +import emcpy.plots.plots +import os +import numpy as np + + +# -------------------------------------------------------------------------------------------------- + + +class Density(): + + """Base class for creating density plots.""" + + def __init__(self, config, logger, dataobj): + + """ + Creates a density plot based on the provided configuration and data. + + Args: + config (dict): A dictionary containing the configuration for the density plot. + logger (Logger): An instance of the logger for logging messages. + dataobj: An instance of the data object containing input data. + + This class initializes and configures a density plot based on the provided configuration and + data. The density plot is created using a declarative plotting library from EMCPy + (https://github.com/NOAA-EMC/emcpy). + + Example: + + :: + + config = { + "data": { + "variable": "collection::group::variable", + "channel": "channel_name", + "slicing": "slice expression" + }, + "plot_property": "property_value", + "plot_option": "option_value", + "schema": "path_to_schema_file.yaml" + } + logger = Logger() + dataobj = DataObject() + density_plot = Density(config, logger, dataobj) + """ + + # Get the data to plot from the data_collection + # --------------------------------------------- + varstr = config['data']['variable'] + var_cgv = varstr.split('::') + + if len(var_cgv) != 3: + logger.abort('In Density the variable \'var_cgv\' does not appear to ' + + 'be in the required format of collection::group::variable.') + + # Optionally get the channel to plot + channel = None + if 'channel' in config['data']: + channel = config['data'].get('channel') + + data = dataobj.get_variable_data(var_cgv[0], var_cgv[1], var_cgv[2], channel) + + # See if we need to slice data + data = slice_var_from_str(config['data'], data, logger) + + # Density data should be flattened + data = data.flatten() + + # Missing data should also be removed + mask = ~np.isnan(data) + data = data[mask] + + # Create declarative plotting density object + # -------------------------------------------- + self.plotobj = emcpy.plots.plots.Density(data) + + # Get defaults from schema + # ------------------------ + layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'emcpy', 'defaults', 'density.yaml')) + config = get_schema(layer_schema, config, logger) + delvars = ['type', 'schema', 'data'] + for d in delvars: + config.pop(d, None) + self.plotobj = update_object(self.plotobj, config, logger) + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py new file mode 100644 index 00000000..c4c23aef --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py @@ -0,0 +1,33 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object, slice_var_from_str +import emcpy.plots.plots +import os +import numpy as np + +from eva.plotting.batch.base.diagnostics.scatter import Scatter + +# -------------------------------------------------------------------------------------------------- + + +class EmcpyScatter(Scatter): + + def configure_plot(self): + + # Create declarative plotting Scatter object + # ------------------------------------------ + self.plotobj = emcpy.plots.plots.Scatter(self.xdata, self.ydata) + + # Get defaults from schema + # ------------------------ + layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'emcpy', 'defaults', 'scatter.yaml')) + new_config = get_schema(layer_schema, self.config, self.logger) + delvars = ['x', 'y', 'type', 'schema'] + for d in delvars: + new_config.pop(d, None) + self.plotobj = update_object(self.plotobj, new_config, self.logger) + + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/histogram.py b/src/eva/plotting/batch/emcpy/diagnostics/histogram.py new file mode 100644 index 00000000..009d79a4 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/histogram.py @@ -0,0 +1,90 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object, slice_var_from_str +import emcpy.plots.plots +import os +import numpy as np + + +# -------------------------------------------------------------------------------------------------- + + +class Histogram(): + + """Base class for creating histogram plots.""" + + def __init__(self, config, logger, dataobj): + + """ + Creates a histogram plot based on the provided configuration and data. + + Args: + config (dict): A dictionary containing the configuration for the histogram plot. + logger (Logger): An instance of the logger for logging messages. + dataobj: An instance of the data object containing input data. + + This class initializes and configures a histogram plot based on the provided configuration + and data. The histogram plot is created using a declarative plotting library from EMCPy + (https://github.com/NOAA-EMC/emcpy). + + Example: + + :: + + config = { + "data": { + "variable": "collection::group::variable", + "channel": "channel_name", + "slicing": "slice expression" + }, + "plot_property": "property_value", + "plot_option": "option_value", + "schema": "path_to_schema_file.yaml" + } + logger = Logger() + dataobj = DataObject() + histogram_plot = Histogram(config, logger, dataobj) + """ + + # Get the data to plot from the data_collection + # --------------------------------------------- + varstr = config['data']['variable'] + var_cgv = varstr.split('::') + + if len(var_cgv) != 3: + logger.abort('In Histogram the variable \'var_cgv\' does not appear to ' + + 'be in the required format of collection::group::variable.') + + # Optionally get the channel to plot + channel = None + if 'channel' in config['data']: + channel = config['data'].get('channel') + + data = dataobj.get_variable_data(var_cgv[0], var_cgv[1], var_cgv[2], channel) + + # See if we need to slice data + data = slice_var_from_str(config['data'], data, logger) + + # Histogram data should be flattened + data = data.flatten() + + # Missing data should also be removed + mask = ~np.isnan(data) + data = data[mask] + + # Create declarative plotting histogram object + # -------------------------------------------- + self.plotobj = emcpy.plots.plots.Histogram(data) + + # Get defaults from schema + # ------------------------ + layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'emcpy', 'defaults', 'histogram.yaml')) + config = get_schema(layer_schema, config, logger) + delvars = ['type', 'schema', 'data'] + for d in delvars: + config.pop(d, None) + self.plotobj = update_object(self.plotobj, config, logger) + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py b/src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py new file mode 100644 index 00000000..41544572 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py @@ -0,0 +1,61 @@ +from eva.eva_path import return_eva_path +from eva.utilities.utils import get_schema, update_object +import emcpy.plots.plots +import os + + +# -------------------------------------------------------------------------------------------------- + + +class HorizontalLine(): + + """Base class for creating horizontal line plots.""" + + def __init__(self, config, logger, dataobj): + + """ + Creates a horizontal line plot based on the provided configuration. + + Args: + config (dict): A dictionary containing the configuration for the horizontal line plot. + logger (Logger): An instance of the logger for logging messages. + + This class initializes and configures a horizontal line plot based on the provided + configuration. The horizontal line plot is created using a declarative plotting library from + EMCPy (https://github.com/NOAA-EMC/emcpy). + + Example: + + :: + + config = { + "y": 0.5, + "plot_property": "property_value", + "plot_option": "option_value", + "schema": "path_to_schema_file.yaml" + } + logger = Logger() + horizontal_line_plot = HorizontalLine(config, logger) + """ + + # Get the y value to plot + # ----------------------- + yval = config['y'] + + # Create declarative plotting HorizontalLine object + # ------------------------------------------- + self.plotobj = emcpy.plots.plots.HorizontalLine(yval) + + # Get defaults from schema + # ------------------------ + layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'emcpy', 'defaults', + 'horizontal_line.yaml')) + config = get_schema(layer_schema, config, logger) + delvars = ['type', 'schema'] + for d in delvars: + config.pop(d, None) + self.plotobj = update_object(self.plotobj, config, logger) + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/emcpy/diagnostics/scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py similarity index 72% rename from src/eva/plotting/emcpy/diagnostics/scatter.py rename to src/eva/plotting/batch/emcpy/diagnostics/line_plot.py index 8426172f..1ea34603 100644 --- a/src/eva/plotting/emcpy/diagnostics/scatter.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py @@ -9,22 +9,22 @@ # -------------------------------------------------------------------------------------------------- -class Scatter(): +class LinePlot(): - """Base class for creating scatter plots.""" + """Base class for creating line plots.""" def __init__(self, config, logger, dataobj): """ - Creates a scatter plot on a map based on the provided configuration. + Creates a line plot based on the provided configuration. Args: - config (dict): A dictionary containing the configuration for the scatter plot on a map. + config (dict): A dictionary containing the configuration for the line plot. logger (Logger): An instance of the logger for logging messages. dataobj: An instance of the data object containing input data. - This class initializes and configures a scatter plot on a map based on the provided - configuration. The scatter plot is created using a declarative plotting library from EMCPy + This class initializes and configures a line plot based on the provided configuration. + The line plot is created using a declarative plotting library from EMCPy (https://github.com/NOAA-EMC/emcpy). Example: @@ -32,16 +32,15 @@ def __init__(self, config, logger, dataobj): :: config = { - "longitude": {"variable": "collection::group::variable"}, - "latitude": {"variable": "collection::group::variable"}, - "data": {"variable": "collection::group::variable", - "channel": "channel_name"}, + "x": {"variable": "collection::group::variable"}, + "y": {"variable": "collection::group::variable"}, + "channel": "channel_name", "plot_property": "property_value", "plot_option": "option_value", "schema": "path_to_schema_file.yaml" } logger = Logger() - map_scatter_plot = MapScatter(config, logger, None) + line_plot = LinePlot(config, logger, None) """ # Get the data to plot from the data_collection @@ -65,14 +64,13 @@ def __init__(self, config, logger, dataobj): channel = config.get('channel') xdata = dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2], channel) - xdata1 = dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2]) ydata = dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], var1_cgv[2], channel) # see if we need to slice data xdata = slice_var_from_str(config['x'], xdata, logger) ydata = slice_var_from_str(config['y'], ydata, logger) - # scatter data should be flattened + # line plot data should be flattened xdata = xdata.flatten() ydata = ydata.flatten() @@ -86,16 +84,16 @@ def __init__(self, config, logger, dataobj): xdata = xdata[mask] ydata = ydata[mask] - # Create declarative plotting Scatter object - # ------------------------------------------ - self.plotobj = emcpy.plots.plots.Scatter(xdata, ydata) + # Create declarative plotting LinePlot object + # ------------------------------------------- + self.plotobj = emcpy.plots.plots.LinePlot(xdata, ydata) # Get defaults from schema # ------------------------ layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'scatter.yaml')) + 'emcpy', 'defaults', 'line_plot.yaml')) config = get_schema(layer_schema, config, logger) - delvars = ['x', 'y', 'type', 'schema'] + delvars = ['x', 'y', 'type', 'schema', 'channel'] for d in delvars: config.pop(d, None) self.plotobj = update_object(self.plotobj, config, logger) diff --git a/src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py b/src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py new file mode 100644 index 00000000..6668f5fe --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py @@ -0,0 +1,70 @@ +from eva.eva_path import return_eva_path +from eva.utilities.utils import get_schema, update_object, slice_var_from_str +import emcpy.plots.map_plots +import os + + +# -------------------------------------------------------------------------------------------------- + + +class MapGridded(): + + """Base class for creating map gridded plots.""" + + def __init__(self, config, logger, dataobj): + + """ + Creates a gridded map plot based on the provided configuration. + + Args: + config (dict): A dictionary containing the configuration for the gridded map plot. + logger (Logger): An instance of the logger for logging messages. + dataobj: An instance of the data object containing input data. + + This class initializes and configures a gridded map plot based on the provided + configuration. The gridded map plot is created using a declarative plotting library from + EMCPy (https://github.com/NOAA-EMC/emcpy). + + Example: + + :: + + config = { + "longitude": {"variable": "collection::group::variable"}, + "latitude": {"variable": "collection::group::variable"}, + "data": {"variable": "collection::group::variable"}, + "plot_property": "property_value", + "plot_option": "option_value", + "schema": "path_to_schema_file.yaml" + } + logger = Logger() + map_plot = MapGridded(config, logger, None) + """ + + # prepare data based on config + lonvar_cgv = config['longitude']['variable'].split('::') + lonvar = dataobj.get_variable_data(lonvar_cgv[0], lonvar_cgv[1], lonvar_cgv[2], None) + lonvar = slice_var_from_str(config['longitude'], lonvar, logger) + latvar_cgv = config['latitude']['variable'].split('::') + latvar = dataobj.get_variable_data(latvar_cgv[0], latvar_cgv[1], latvar_cgv[2], None) + latvar = slice_var_from_str(config['latitude'], latvar, logger) + datavar_cgv = config['data']['variable'].split('::') + datavar = dataobj.get_variable_data(datavar_cgv[0], datavar_cgv[1], datavar_cgv[2], None) + datavar = slice_var_from_str(config['data'], datavar, logger) + + # create declarative plotting MapGridded object + self.plotobj = emcpy.plots.map_plots.MapGridded(latvar, lonvar, datavar) + # get defaults from schema + layer_schema = config.get("schema", + os.path.join(return_eva_path(), + 'plotting', + 'emcpy', 'defaults', + 'map_gridded.yaml')) + config = get_schema(layer_schema, config, logger) + delvars = ['longitude', 'latitude', 'data', 'type', 'schema'] + for d in delvars: + config.pop(d, None) + self.plotobj = update_object(self.plotobj, config, logger) + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py new file mode 100644 index 00000000..81356d66 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py @@ -0,0 +1,83 @@ +from eva.eva_path import return_eva_path +from eva.utilities.utils import get_schema, update_object, slice_var_from_str +import emcpy.plots.map_plots +import os +import numpy as np + + +# -------------------------------------------------------------------------------------------------- + + +class MapScatter(): + + """Base class for creating map scatter plots.""" + + def __init__(self, config, logger, dataobj): + + """ + Creates a scatter plot on a map based on the provided configuration. + + Args: + config (dict): A dictionary containing the configuration for the scatter plot on a map. + logger (Logger): An instance of the logger for logging messages. + dataobj: An instance of the data object containing input data. + + This class initializes and configures a scatter plot on a map based on the provided + configuration. The scatter plot is created using a declarative plotting library from EMCPy + (https://github.com/NOAA-EMC/emcpy). + + Example: + :: + + config = { + "longitude": {"variable": "collection::group::variable"}, + "latitude": {"variable": "collection::group::variable"}, + "data": {"variable": "collection::group::variable", + "channel": "channel_name"}, + "plot_property": "property_value", + "plot_option": "option_value", + "schema": "path_to_schema_file.yaml" + } + logger = Logger() + map_scatter_plot = MapScatter(config, logger, None) + """ + + # prepare data based on config + # Optionally get the channel to plot + channel = None + if 'channel' in config['data']: + channel = config['data'].get('channel') + lonvar_cgv = config['longitude']['variable'].split('::') + lonvar = dataobj.get_variable_data(lonvar_cgv[0], lonvar_cgv[1], lonvar_cgv[2], None) + lonvar = slice_var_from_str(config['longitude'], lonvar, logger) + latvar_cgv = config['latitude']['variable'].split('::') + latvar = dataobj.get_variable_data(latvar_cgv[0], latvar_cgv[1], latvar_cgv[2], None) + latvar = slice_var_from_str(config['latitude'], latvar, logger) + datavar_cgv = config['data']['variable'].split('::') + datavar = dataobj.get_variable_data(datavar_cgv[0], datavar_cgv[1], datavar_cgv[2], channel) + datavar = slice_var_from_str(config['data'], datavar, logger) + # scatter data should be flattened + lonvar = lonvar.flatten() + latvar = latvar.flatten() + datavar = datavar.flatten() + + # If everything is nan plotting will fail so just plot some large values + if np.isnan(datavar).all(): + datavar[np.isnan(datavar)] = 1.0e38 + + # create declarative plotting MapScatter object + self.plotobj = emcpy.plots.map_plots.MapScatter(latvar, lonvar, datavar) + # get defaults from schema + layer_schema = config.get("schema", + os.path.join(return_eva_path(), + 'plotting', + 'emcpy', 'defaults', + 'map_scatter.yaml')) + config = get_schema(layer_schema, config, logger) + delvars = ['longitude', 'latitude', 'data', 'type', 'schema'] + for d in delvars: + config.pop(d, None) + self.plotobj = update_object(self.plotobj, config, logger) + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py b/src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py new file mode 100644 index 00000000..6bcf94d3 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py @@ -0,0 +1,61 @@ +from eva.eva_path import return_eva_path +from eva.utilities.utils import get_schema, update_object +import emcpy.plots.plots +import os + + +# -------------------------------------------------------------------------------------------------- + + +class VerticalLine(): + + """Base class for creating vertical line plots.""" + + def __init__(self, config, logger, dataobj): + + """ + Creates a vertical line plot based on the provided configuration. + + Args: + config (dict): A dictionary containing the configuration for the vertical line plot. + logger (Logger): An instance of the logger for logging messages. + dataobj: Not used in this context. + + This class initializes and configures a vertical line plot based on the provided + configuration. The vertical line plot is created using a declarative plotting library from + EMCPy (https://github.com/NOAA-EMC/emcpy). + + Example: + + :: + + config = { + "x": 10, + "plot_property": "property_value", + "plot_option": "option_value", + "schema": "path_to_schema_file.yaml" + } + logger = Logger() + vertical_line_plot = VerticalLine(config, logger, None) + """ + + # Get the x value to plot + # ----------------------- + xval = config['x'] + + # Create declarative plotting HorizontalLine object + # ------------------------------------------- + self.plotobj = emcpy.plots.plots.VerticalLine(xval) + + # Get defaults from schema + # ------------------------ + layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'emcpy', 'defaults', 'vertical_line.yaml')) + config = get_schema(layer_schema, config, logger) + delvars = ['type', 'schema'] + for d in delvars: + config.pop(d, None) + self.plotobj = update_object(self.plotobj, config, logger) + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/plot_tools/__init__.py b/src/eva/plotting/batch/emcpy/plot_tools/__init__.py new file mode 100644 index 00000000..ac1c0bc4 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/plot_tools/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2021-2022 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +import os + +repo_directory = os.path.dirname(__file__) diff --git a/src/eva/plotting/batch/emcpy/plot_tools/dynamic_config.py b/src/eva/plotting/batch/emcpy/plot_tools/dynamic_config.py new file mode 100644 index 00000000..5421f118 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/plot_tools/dynamic_config.py @@ -0,0 +1,203 @@ +# (C) Copyright 2021-2022 NOAA/NWS/EMC +# +# (C) Copyright 2021-2022 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + + +# -------------------------------------------------------------------------------------------------- + + +import math +import numpy as np +from scipy.stats import skew + +from eva.utilities.utils import replace_vars_dict + + +# -------------------------------------------------------------------------------------------------- + + +def vminvmaxcmap(logger, option_dict, plots_dict, data_collections): + """ + Computes colormap limits and adjustments based on provided data variable. + + Args: + logger (Logger): An instance of the logger for logging messages. + option_dict (dict): A dictionary containing various options for the transformation. + plots_dict (dict): A dictionary containing plot-related information. + data_collections (DataCollections): An instance of the DataCollections class containing + input data. + + Returns: + dict: A dictionary with adjusted values for colormap limits and colormap. + + This function computes colormap limits and adjustments based on the provided data variable. It + calculates the minimum and maximum values for colormap limits, determines whether a sequential + or diverging colormap should be used, and updates the provided plots dictionary with the + adjusted values. + + Example: + :: + + option_dict = { + 'percentage capture': 95, + 'data variable': 'my_collection::my_variable', + 'sequential colormap': 'plasma', + 'diverging colormap': 'coolwarm' + } + plots_dict = { + 'dynamic_vmax': '1.0', + 'dynamic_vmin': '-1.0', + 'dynamic_cmap': 'viridis' + } + adjusted_plots_dict = vminvmaxcmap(logger, option_dict, plots_dict, + data_collections) + """ + + # Get percentage of data to use in computing limits. Code will sort the data + # by size and then trim the minimum and maximum until this percentage of data + # is kept + percentage_capture = option_dict.get('percentage capture', 100) + + # Optionally the data might have a channel. + channel = option_dict.get('channel', None) + + # Get the data variable to use for determining cbar limits + varname = option_dict.get('data variable') + varname_cgv = varname.split('::') + datavar = data_collections.get_variable_data(varname_cgv[0], varname_cgv[1], + varname_cgv[2], channel) + + # Reorder the data array + datavar = np.sort(datavar) + + # Decide how many values to throw out on each end of the dataset + n = datavar.size + n_throw_out = ((100-percentage_capture) * n / 100) / 2 + + # The value needs to be an integer + n_throw_out = np.floor(n_throw_out) + + # Create new array with only the data to consider + if n_throw_out == 0: + datavar_check = datavar + else: + datavar_check = datavar[n_throw_out:-n_throw_out] + + # Find minimum and maximum values + cmap = option_dict.get('sequential colormap', 'viridis') + + # If everything is nan plot some large min/max (plotting code should do the same) + if np.isnan(datavar_check).all(): + vmax = 1.0e38 + vmin = 1.0e38 + else: + vmax = np.nanmax(datavar_check) + vmin = np.nanmin(datavar_check) + + # If positive and negative values are present then a diverging colormap centered on zero should + # be used. + if vmin < 0.0 and vmax > 0.0: + vmax = np.nanmax(np.abs(datavar_check)) + vmin = -vmax + cmap = option_dict.get('diverging colormap', 'seismic') + + # Check for nan + if np.isnan(vmax) or np.isnan(vmin): + vmax = 0.0 + vmin = 0.0 + cmap = option_dict.get('diverging colormap', 'seismic') + + # Prepare dictionary with values to be overwritten in other dictionaries + overwrite_dict = {} + overwrite_dict['dynamic_vmax'] = str(vmax) + overwrite_dict['dynamic_vmin'] = str(vmin) + overwrite_dict['dynamic_cmap'] = cmap + + # Perform the overwrite of the plots_dict dictionary and return new dictionary + return replace_vars_dict(plots_dict, **overwrite_dict) + + +# -------------------------------------------------------------------------------------------------- + + +def histogram_bins(logger, option_dict, plots_dict, data_collections): + """ + Computes the number of bins for a histogram based on the provided data variable. + + Args: + logger (Logger): An instance of the logger for logging messages. + option_dict (dict): A dictionary containing various options for the transformation. + plots_dict (dict): A dictionary containing plot-related information. + data_collections (DataCollections): An instance of the DataCollections class containing + input data. + + Returns: + dict: A dictionary with the number of bins for the histogram. + + This function computes the number of bins for a histogram based on the provided data variable. + It applies various rules to calculate the appropriate number of bins and updates the provided + plots dictionary with the calculated value. + + Example: + :: + + option_dict = { + 'data variable': 'my_collection::my_variable', + 'number of bins rule': 'sturges' + } + plots_dict = { + 'dynamic_bins': '10' + } + adjusted_plots_dict = histogram_bins(logger, option_dict, plots_dict, + data_collections) + """ + + # Optionally the data might have a channel. + channel = option_dict.get('channel', None) + + # Get the data variable to use for determining cbar limits + varname = option_dict.get('data variable') + varname_cgv = varname.split('::') + datavar = data_collections.get_variable_data(varname_cgv[0], varname_cgv[1], + varname_cgv[2], channel) + + # Compute size of the array of data + n = np.count_nonzero(~np.isnan(datavar)) + + # Check for zero data, set to 3 as a reasonable minimum + if n == 0: + n = 3 + + # Compute number of bins using standard rule + rule = option_dict.get('number of bins rule', 'sturges') + rule = rule.lower() # User might capitalize names so convert to lowercase + + # Allow for some standard rules for computing the number of bins for the histogram + if rule == 'square root': + nbins = math.sqrt(n) + elif rule == 'sturges': + nbins = 1 + math.log2(n) + elif rule == 'rice': + nbins = 2 * math.pow(n, 1/3) + elif rule == 'doane': + if n < 3: + logger.abort(f'Rule \'doane\' is not valid for data with fewer than 3 samples.') + g1 = skew(datavar, nan_policy='omit') + sig_g1 = math.sqrt(6*(n-2)/((n+1)*(n+3))) + nbins = 1 + math.log2(n) + math.log2(1 + abs(g1)/sig_g1) + else: + logger.abort(f'Rule {rule} for computing the histogram bins is not valid.') + + # Prepare dictionary with values to be overwritten in other dictionaries + overwrite_dict = {} + overwrite_dict['dynamic_bins'] = str(round(nbins)) + + # Perform the overwrite of the plots_dict dictionary and return new dictionary + return replace_vars_dict(plots_dict, **overwrite_dict) + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/emcpy/plot_tools/figure_driver.py b/src/eva/plotting/batch/emcpy/plot_tools/figure_driver.py similarity index 100% rename from src/eva/plotting/emcpy/plot_tools/figure_driver.py rename to src/eva/plotting/batch/emcpy/plot_tools/figure_driver.py diff --git a/src/eva/plotting/bokeh/diagonistics/__init__.py b/src/eva/plotting/batch/hvplot/__init__.py similarity index 100% rename from src/eva/plotting/bokeh/diagonistics/__init__.py rename to src/eva/plotting/batch/hvplot/__init__.py diff --git a/src/eva/plotting/bokeh/plot_tools/__init__.py b/src/eva/plotting/batch/hvplot/defaults/__init__.py similarity index 100% rename from src/eva/plotting/bokeh/plot_tools/__init__.py rename to src/eva/plotting/batch/hvplot/defaults/__init__.py diff --git a/src/eva/plotting/batch/hvplot/diagonistics/__init__.py b/src/eva/plotting/batch/hvplot/diagonistics/__init__.py new file mode 100644 index 00000000..ebda1763 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/diagonistics/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2023 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +import os + +repo_directory = os.path.dirname(__file__) diff --git a/src/eva/plotting/batch/hvplot/plot_tools/__init__.py b/src/eva/plotting/batch/hvplot/plot_tools/__init__.py new file mode 100644 index 00000000..ebda1763 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/plot_tools/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2023 United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + +import os + +repo_directory = os.path.dirname(__file__) diff --git a/src/eva/plotting/bokeh/plot_tools/figure_driver.py b/src/eva/plotting/batch/hvplot/plot_tools/figure_driver.py similarity index 100% rename from src/eva/plotting/bokeh/plot_tools/figure_driver.py rename to src/eva/plotting/batch/hvplot/plot_tools/figure_driver.py From 5713088e16537588a5e8cc407029c0de174c9a99 Mon Sep 17 00:00:00 2001 From: asewnath Date: Mon, 18 Sep 2023 16:57:14 -0400 Subject: [PATCH 04/17] adding figure handler --- .../batch/base/plot_tools/figure_driver.py | 37 ++- .../batch/emcpy/diagnostics/emcpy_scatter.py | 3 +- .../batch/emcpy/plot_tools/dynamic_config.py | 203 ------------- .../emcpy/plot_tools/emcpy_figure_handler.py | 20 ++ .../batch/emcpy/plot_tools/figure_driver.py | 270 ------------------ 5 files changed, 43 insertions(+), 490 deletions(-) delete mode 100644 src/eva/plotting/batch/emcpy/plot_tools/dynamic_config.py create mode 100644 src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py delete mode 100644 src/eva/plotting/batch/emcpy/plot_tools/figure_driver.py diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index 4f2c5b9c..5d2ab215 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -14,7 +14,8 @@ from eva.utilities.stats import stats_helper from eva.utilities.utils import get_schema, camelcase_to_underscore, parse_channel_list from eva.utilities.utils import replace_vars_dict -from emcpy.plots.create_plots import CreatePlot, CreateFigure +#from emcpy.plots.create_plots import CreatePlot, CreateFigure +from eva.plotting.batch.emcpy.plot_tools.emcpy_figure_handler import EmcpyFigureHandler import copy import importlib as im import os @@ -57,8 +58,11 @@ def figure_driver(config, data_collections, timing, logger): # update figure conf based on schema # ---------------------------------- - fig_schema = figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'figure.yaml')) + #fig_schema = figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', + # 'emcpy', 'defaults', 'figure.yaml')) + + handler = EmcpyFigureHandler() + fig_schema = handler.find_schema(figure_conf) figure_conf = get_schema(fig_schema, figure_conf, logger) # pass configurations and make graphic(s) @@ -99,18 +103,18 @@ def figure_driver(config, data_collections, timing, logger): **batch_conf_this) # Make plot - make_figure(figure_conf_fill, plots_conf_fill, + make_figure(handler, 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) + make_figure(handler, figure_conf, plots_conf, dynamic_options_conf, data_collections, logger) timing.stop('Graphics Loop') # -------------------------------------------------------------------------------------------------- -def make_figure(figure_conf, plots, dynamic_options, data_collections, logger): +def make_figure(handler, figure_conf, plots, dynamic_options, data_collections, logger): """ Generates a figure based on the provided configuration and plots. @@ -130,7 +134,7 @@ def make_figure(figure_conf, plots, dynamic_options, data_collections, logger): # Adjust the plots configs if there are dynamic options # ----------------------------------------------------- for dynamic_option in dynamic_options: - dynamic_option_module = im.import_module("eva.plotting.emcpy.plot_tools.dynamic_config") + dynamic_option_module = im.import_module("eva.plotting.batch.base.plot_tools.dynamic_config") dynamic_option_method = getattr(dynamic_option_module, dynamic_option['type']) plots = dynamic_option_method(logger, dynamic_option, plots, data_collections) @@ -145,15 +149,13 @@ def make_figure(figure_conf, plots, dynamic_options, data_collections, logger): for plot in plots: layer_list = [] for layer in plot.get("layers"): - eva_class_name = layer.get("type") - eva_class_name = 'Emcpy' + eva_class_name + eva_class_name = handler.BACKEND_NAME + layer.get("type") eva_module_name = camelcase_to_underscore(eva_class_name) - full_module = "eva.plotting.batch.emcpy.diagnostics."+eva_module_name + full_module = handler.MODULE_NAME + eva_module_name layer_class = getattr(im.import_module(full_module), eva_class_name) layer = layer_class(layer, logger, data_collections) layer.data_prep() # use the translator class to go from eva to declarative plotting - #layer_list.append(layer_class(layer, logger, data_collections).plotobj) layer_list.append(layer.configure_plot()) # get mapping dictionary proj = None @@ -165,7 +167,8 @@ def make_figure(figure_conf, plots, dynamic_options, data_collections, logger): domain = mapoptions['domain'] # create a subplot based on specified layers - plotobj = CreatePlot(plot_layers=layer_list, projection=proj, domain=domain) + #plotobj = CreatePlot(plot_layers=layer_list, projection=proj, domain=domain) + plotobj = handler.create_plot(layer_list, proj, domain) # make changes to subplot based on YAML configuration for key, value in plot.items(): if key not in ['layers', 'mapping', 'statistics']: @@ -181,9 +184,13 @@ def make_figure(figure_conf, plots, dynamic_options, data_collections, logger): plot_list.append(plotobj) # create figure - fig = CreateFigure(nrows=figure_conf['layout'][0], - ncols=figure_conf['layout'][1], - figsize=tuple(figure_conf['figure size'])) + nrows = figure_conf['layout'][0] + ncols = figure_conf['layout'][1] + figsize = tuple(figure_conf['figure size']) + #fig = CreateFigure(nrows=figure_conf['layout'][0], + # ncols=figure_conf['layout'][1], + # figsize=tuple(figure_conf['figure size'])) + fig = handler.create_figure(nrows, ncols, figsize) fig.plot_list = plot_list fig.create_figure() diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py index c4c23aef..9236042f 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py @@ -1,9 +1,8 @@ from eva.eva_path import return_eva_path from eva.utilities.config import get -from eva.utilities.utils import get_schema, update_object, slice_var_from_str +from eva.utilities.utils import get_schema, update_object import emcpy.plots.plots import os -import numpy as np from eva.plotting.batch.base.diagnostics.scatter import Scatter diff --git a/src/eva/plotting/batch/emcpy/plot_tools/dynamic_config.py b/src/eva/plotting/batch/emcpy/plot_tools/dynamic_config.py deleted file mode 100644 index 5421f118..00000000 --- a/src/eva/plotting/batch/emcpy/plot_tools/dynamic_config.py +++ /dev/null @@ -1,203 +0,0 @@ -# (C) Copyright 2021-2022 NOAA/NWS/EMC -# -# (C) Copyright 2021-2022 United States Government as represented by the Administrator of the -# National Aeronautics and Space Administration. All Rights Reserved. -# -# This software is licensed under the terms of the Apache Licence Version 2.0 -# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. - - -# -------------------------------------------------------------------------------------------------- - - -import math -import numpy as np -from scipy.stats import skew - -from eva.utilities.utils import replace_vars_dict - - -# -------------------------------------------------------------------------------------------------- - - -def vminvmaxcmap(logger, option_dict, plots_dict, data_collections): - """ - Computes colormap limits and adjustments based on provided data variable. - - Args: - logger (Logger): An instance of the logger for logging messages. - option_dict (dict): A dictionary containing various options for the transformation. - plots_dict (dict): A dictionary containing plot-related information. - data_collections (DataCollections): An instance of the DataCollections class containing - input data. - - Returns: - dict: A dictionary with adjusted values for colormap limits and colormap. - - This function computes colormap limits and adjustments based on the provided data variable. It - calculates the minimum and maximum values for colormap limits, determines whether a sequential - or diverging colormap should be used, and updates the provided plots dictionary with the - adjusted values. - - Example: - :: - - option_dict = { - 'percentage capture': 95, - 'data variable': 'my_collection::my_variable', - 'sequential colormap': 'plasma', - 'diverging colormap': 'coolwarm' - } - plots_dict = { - 'dynamic_vmax': '1.0', - 'dynamic_vmin': '-1.0', - 'dynamic_cmap': 'viridis' - } - adjusted_plots_dict = vminvmaxcmap(logger, option_dict, plots_dict, - data_collections) - """ - - # Get percentage of data to use in computing limits. Code will sort the data - # by size and then trim the minimum and maximum until this percentage of data - # is kept - percentage_capture = option_dict.get('percentage capture', 100) - - # Optionally the data might have a channel. - channel = option_dict.get('channel', None) - - # Get the data variable to use for determining cbar limits - varname = option_dict.get('data variable') - varname_cgv = varname.split('::') - datavar = data_collections.get_variable_data(varname_cgv[0], varname_cgv[1], - varname_cgv[2], channel) - - # Reorder the data array - datavar = np.sort(datavar) - - # Decide how many values to throw out on each end of the dataset - n = datavar.size - n_throw_out = ((100-percentage_capture) * n / 100) / 2 - - # The value needs to be an integer - n_throw_out = np.floor(n_throw_out) - - # Create new array with only the data to consider - if n_throw_out == 0: - datavar_check = datavar - else: - datavar_check = datavar[n_throw_out:-n_throw_out] - - # Find minimum and maximum values - cmap = option_dict.get('sequential colormap', 'viridis') - - # If everything is nan plot some large min/max (plotting code should do the same) - if np.isnan(datavar_check).all(): - vmax = 1.0e38 - vmin = 1.0e38 - else: - vmax = np.nanmax(datavar_check) - vmin = np.nanmin(datavar_check) - - # If positive and negative values are present then a diverging colormap centered on zero should - # be used. - if vmin < 0.0 and vmax > 0.0: - vmax = np.nanmax(np.abs(datavar_check)) - vmin = -vmax - cmap = option_dict.get('diverging colormap', 'seismic') - - # Check for nan - if np.isnan(vmax) or np.isnan(vmin): - vmax = 0.0 - vmin = 0.0 - cmap = option_dict.get('diverging colormap', 'seismic') - - # Prepare dictionary with values to be overwritten in other dictionaries - overwrite_dict = {} - overwrite_dict['dynamic_vmax'] = str(vmax) - overwrite_dict['dynamic_vmin'] = str(vmin) - overwrite_dict['dynamic_cmap'] = cmap - - # Perform the overwrite of the plots_dict dictionary and return new dictionary - return replace_vars_dict(plots_dict, **overwrite_dict) - - -# -------------------------------------------------------------------------------------------------- - - -def histogram_bins(logger, option_dict, plots_dict, data_collections): - """ - Computes the number of bins for a histogram based on the provided data variable. - - Args: - logger (Logger): An instance of the logger for logging messages. - option_dict (dict): A dictionary containing various options for the transformation. - plots_dict (dict): A dictionary containing plot-related information. - data_collections (DataCollections): An instance of the DataCollections class containing - input data. - - Returns: - dict: A dictionary with the number of bins for the histogram. - - This function computes the number of bins for a histogram based on the provided data variable. - It applies various rules to calculate the appropriate number of bins and updates the provided - plots dictionary with the calculated value. - - Example: - :: - - option_dict = { - 'data variable': 'my_collection::my_variable', - 'number of bins rule': 'sturges' - } - plots_dict = { - 'dynamic_bins': '10' - } - adjusted_plots_dict = histogram_bins(logger, option_dict, plots_dict, - data_collections) - """ - - # Optionally the data might have a channel. - channel = option_dict.get('channel', None) - - # Get the data variable to use for determining cbar limits - varname = option_dict.get('data variable') - varname_cgv = varname.split('::') - datavar = data_collections.get_variable_data(varname_cgv[0], varname_cgv[1], - varname_cgv[2], channel) - - # Compute size of the array of data - n = np.count_nonzero(~np.isnan(datavar)) - - # Check for zero data, set to 3 as a reasonable minimum - if n == 0: - n = 3 - - # Compute number of bins using standard rule - rule = option_dict.get('number of bins rule', 'sturges') - rule = rule.lower() # User might capitalize names so convert to lowercase - - # Allow for some standard rules for computing the number of bins for the histogram - if rule == 'square root': - nbins = math.sqrt(n) - elif rule == 'sturges': - nbins = 1 + math.log2(n) - elif rule == 'rice': - nbins = 2 * math.pow(n, 1/3) - elif rule == 'doane': - if n < 3: - logger.abort(f'Rule \'doane\' is not valid for data with fewer than 3 samples.') - g1 = skew(datavar, nan_policy='omit') - sig_g1 = math.sqrt(6*(n-2)/((n+1)*(n+3))) - nbins = 1 + math.log2(n) + math.log2(1 + abs(g1)/sig_g1) - else: - logger.abort(f'Rule {rule} for computing the histogram bins is not valid.') - - # Prepare dictionary with values to be overwritten in other dictionaries - overwrite_dict = {} - overwrite_dict['dynamic_bins'] = str(round(nbins)) - - # Perform the overwrite of the plots_dict dictionary and return new dictionary - return replace_vars_dict(plots_dict, **overwrite_dict) - - -# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py b/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py new file mode 100644 index 00000000..c93e7c38 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py @@ -0,0 +1,20 @@ +from emcpy.plots.create_plots import CreatePlot, CreateFigure +from eva.eva_path import return_eva_path +import os + +class EmcpyFigureHandler(): + + def __init__(self): + # Define default paths to modules + self.BACKEND_NAME = "Emcpy" + self.MODULE_NAME = "eva.plotting.batch.emcpy.diagnostics." + + def find_schema(self, figure_conf): + return figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', + 'emcpy', 'defaults', 'figure.yaml')) + + def create_plot(self, layer_list, proj, domain): + return CreatePlot(plot_layers=layer_list, projection=proj, domain=domain) + + def create_figure(self, nrows, ncols, figsize): + return CreateFigure(nrows=nrows, ncols=ncols, figsize=figsize) diff --git a/src/eva/plotting/batch/emcpy/plot_tools/figure_driver.py b/src/eva/plotting/batch/emcpy/plot_tools/figure_driver.py deleted file mode 100644 index d78323ba..00000000 --- a/src/eva/plotting/batch/emcpy/plot_tools/figure_driver.py +++ /dev/null @@ -1,270 +0,0 @@ -# (C) Copyright 2021-2022 NOAA/NWS/EMC -# -# (C) Copyright 2021-2022 United States Government as represented by the Administrator of the -# National Aeronautics and Space Administration. All Rights Reserved. -# -# This software is licensed under the terms of the Apache Licence Version 2.0 -# This software is licensed under the terms of the Apache Licence Version 2.0 -# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. - - -# -------------------------------------------------------------------------------------------------- - -from eva.eva_path import return_eva_path -from eva.utilities.stats import stats_helper -from eva.utilities.utils import get_schema, camelcase_to_underscore, parse_channel_list -from eva.utilities.utils import replace_vars_dict -from emcpy.plots.create_plots import CreatePlot, CreateFigure -import copy -import importlib as im -import os - -# -------------------------------------------------------------------------------------------------- - - -def emcpy_figure_driver(config, data_collections, timing, logger): - """ - Generates and saves multiple figures based on the provided configuration. - - Args: - config (dict): A dictionary containing the configuration for generating figures. - data_collections (DataCollections): An instance of the DataCollections class containing - input data. - timing (Timing): A timing instance to measure the execution time. - logger (Logger): An instance of the logger for logging messages. - - This function generates and saves multiple figures based on the provided configuration. It - processes each graphic specified in the configuration and creates corresponding figures with - plots. - """ - - # Get list of graphics from configuration - # ------------------- - graphics_section = config.get("graphics") - graphics = graphics_section.get("figure_list") - - # Loop through specified graphics - # ------------------- - timing.start('Graphics Loop') - for graphic in graphics: - - # Parse configuration for this graphic - # ------------------- - batch_conf = graphic.get("batch figure", {}) # batch configuration (default nothing) - figure_conf = graphic.get("figure") # figure configuration - plots_conf = graphic.get("plots") # list of plots/subplots - dynamic_options_conf = graphic.get("dynamic options", []) # Dynamic overwrites - - # update figure conf based on schema - # ---------------------------------- - fig_schema = figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'figure.yaml')) - figure_conf = get_schema(fig_schema, figure_conf, logger) - - # pass configurations and make graphic(s) - # --------------------------------------- - if batch_conf: - # Get potential variables - variables = batch_conf.get('variables', []) - # Get list of channels - channels_str_or_list = batch_conf.get('channels', []) - channels = parse_channel_list(channels_str_or_list, logger) - - # Set some fake values to ensure the loops are entered - if 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: - 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 - batch_conf_this['variable_title'] = var_title - - # Replace templated variables in figure and plots config - figure_conf_fill = copy.copy(figure_conf) - figure_conf_fill = replace_vars_dict(figure_conf_fill, **batch_conf_this) - plots_conf_fill = copy.copy(plots_conf) - plots_conf_fill = replace_vars_dict(plots_conf_fill, **batch_conf_this) - dynamic_options_conf_fill = copy.copy(dynamic_options_conf) - dynamic_options_conf_fill = replace_vars_dict(dynamic_options_conf_fill, - **batch_conf_this) - - # 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) - timing.stop('Graphics Loop') - - -# -------------------------------------------------------------------------------------------------- - - -def make_figure(figure_conf, plots, dynamic_options, data_collections, logger): - """ - Generates a figure based on the provided configuration and plots. - - Args: - figure_conf (dict): A dictionary containing the configuration for the figure layout - and appearance. - plots (list): A list of dictionaries containing plot configurations. - dynamic_options (list): A list of dictionaries containing dynamic configuration options. - data_collections (DataCollections): An instance of the DataCollections class containing - input data. - logger (Logger): An instance of the logger for logging messages. - - This function generates a figure based on the provided configuration and plot settings. It - processes the specified plots, applies dynamic options, and saves the generated figure. - """ - - # Adjust the plots configs if there are dynamic options - # ----------------------------------------------------- - for dynamic_option in dynamic_options: - dynamic_option_module = im.import_module("eva.plotting.emcpy.plot_tools.dynamic_config") - dynamic_option_method = getattr(dynamic_option_module, dynamic_option['type']) - plots = dynamic_option_method(logger, dynamic_option, plots, data_collections) - - # Grab some figure configuration - # ------------------- - figure_layout = figure_conf.get("layout") - file_type = figure_conf.get("figure file type", "png") - output_file = get_output_file(figure_conf) - - # Set up layers and plots - plot_list = [] - for plot in plots: - layer_list = [] - for layer in plot.get("layers"): - eva_class_name = layer.get("type") - eva_module_name = camelcase_to_underscore(eva_class_name) - full_module = "eva.plotting.emcpy.diagnostics."+eva_module_name - layer_class = getattr(im.import_module(full_module), eva_class_name) - # use the translator class to go from eva to declarative plotting - layer_list.append(layer_class(layer, logger, data_collections).plotobj) - # get mapping dictionary - proj = None - domain = None - if 'mapping' in plot.keys(): - mapoptions = plot.get('mapping') - # TODO make this configurable and not hard coded - proj = mapoptions['projection'] - domain = mapoptions['domain'] - - # create a subplot based on specified layers - plotobj = CreatePlot(plot_layers=layer_list, projection=proj, domain=domain) - # make changes to subplot based on YAML configuration - for key, value in plot.items(): - if key not in ['layers', 'mapping', 'statistics']: - if isinstance(value, dict): - getattr(plotobj, key)(**value) - elif value is None: - getattr(plotobj, key)() - else: - getattr(plotobj, key)(value) - if key in ['statistics']: - # call the stats helper - 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], - figsize=tuple(figure_conf['figure size'])) - fig.plot_list = plot_list - fig.create_figure() - - if 'title' in figure_conf: - fig.add_suptitle(figure_conf['title']) - if 'tight layout' in figure_conf: - if isinstance(figure_conf['tight layout'], dict): - fig.tight_layout(**figure_conf['tight layout']) - else: - fig.tight_layout() - if 'plot logo' in figure_conf: - fig.plot_logo(**figure_conf['plot logo']) - - saveargs = get_saveargs(figure_conf) - fig.save_figure(output_file, **saveargs) - - fig.close_figure() - - -# -------------------------------------------------------------------------------------------------- - - -def get_saveargs(figure_conf): - """ - Gets arguments for saving a figure based on the provided configuration. - - Args: - figure_conf (dict): A dictionary containing the figure configuration. - - Returns: - out_conf (dict): A dictionary containing arguments for saving the figure. - - This function extracts relevant arguments from the provided figure configuration to be used - for saving the generated figure. - - Example: - :: - - figure_conf = { - "layout": [2, 2], - "figure file type": "png", - "output path": "./output_folder", - "output name": "example_figure" - } - save_args = get_saveargs(figure_conf) - """ - - out_conf = figure_conf - delvars = ['layout', 'figure file type', 'output path', 'figure size', 'title'] - out_conf['format'] = figure_conf['figure file type'] - for d in delvars: - del out_conf[d] - return out_conf - - -# -------------------------------------------------------------------------------------------------- - - -def get_output_file(figure_conf): - """ - Gets the output file path for saving the figure. - - Args: - figure_conf (dict): A dictionary containing the figure configuration. - - Returns: - output_file (str): The complete path for saving the figure. - - This function constructs the complete file path for saving the generated figure based on the - provided figure configuration. - - Example: - :: - - figure_conf = { - "output path": "./output_folder", - "output name": "example_figure" - } - output_file = get_output_file(figure_conf) - """ - - file_path = figure_conf.get("output path", "./") - output_name = figure_conf.get("output name", "") - output_file = os.path.join(file_path, output_name) - return output_file - - -# -------------------------------------------------------------------------------------------------- From e401068e54530b247dc2cd2755b6f872999ec3dd Mon Sep 17 00:00:00 2001 From: asewnath Date: Tue, 19 Sep 2023 17:05:35 -0400 Subject: [PATCH 05/17] bokeh scatter plot --- src/eva/eva_driver.py | 2 - .../handlers}/__init__.py | 0 .../handlers}/emcpy_figure_handler.py | 2 +- .../base/handlers/hvplot_figure_handler.py | 20 +++ .../batch/base/plot_tools/figure_driver.py | 23 ++- .../batch/hvplot/defaults/figure.yaml | 6 + .../diagnostics}/__init__.py | 2 +- .../hvplot/diagnostics/hvplot_scatter.py | 24 +++ .../batch/hvplot/plot_tools/create_plots.py | 126 ++++++++++++++ .../batch/hvplot/plot_tools/figure_driver.py | 157 ------------------ 10 files changed, 192 insertions(+), 170 deletions(-) rename src/eva/plotting/batch/{hvplot/diagonistics => base/handlers}/__init__.py (100%) rename src/eva/plotting/batch/{emcpy/plot_tools => base/handlers}/emcpy_figure_handler.py (96%) create mode 100644 src/eva/plotting/batch/base/handlers/hvplot_figure_handler.py create mode 100644 src/eva/plotting/batch/hvplot/defaults/figure.yaml rename src/eva/plotting/batch/{emcpy/plot_tools => hvplot/diagnostics}/__init__.py (74%) create mode 100644 src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py create mode 100644 src/eva/plotting/batch/hvplot/plot_tools/create_plots.py delete mode 100644 src/eva/plotting/batch/hvplot/plot_tools/figure_driver.py diff --git a/src/eva/eva_driver.py b/src/eva/eva_driver.py index 2f8d3945..c9e79470 100644 --- a/src/eva/eva_driver.py +++ b/src/eva/eva_driver.py @@ -80,8 +80,6 @@ def eva(eva_config, eva_logger=None): timing.stop('TransformDriverExecute') # Generate figure(s) - # May want a separate function handling the "inner working" of figure driver selection - plotting_backend = eva_dict['graphics']['plotting_backend'] logger.info(f'Running figure driver') timing.start('FigureDriverExecute') figure_driver(eva_dict, data_collections, timing, logger) diff --git a/src/eva/plotting/batch/hvplot/diagonistics/__init__.py b/src/eva/plotting/batch/base/handlers/__init__.py similarity index 100% rename from src/eva/plotting/batch/hvplot/diagonistics/__init__.py rename to src/eva/plotting/batch/base/handlers/__init__.py diff --git a/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py b/src/eva/plotting/batch/base/handlers/emcpy_figure_handler.py similarity index 96% rename from src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py rename to src/eva/plotting/batch/base/handlers/emcpy_figure_handler.py index c93e7c38..fa7cd23b 100644 --- a/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py +++ b/src/eva/plotting/batch/base/handlers/emcpy_figure_handler.py @@ -10,7 +10,7 @@ def __init__(self): self.MODULE_NAME = "eva.plotting.batch.emcpy.diagnostics." def find_schema(self, figure_conf): - return figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', + return figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', 'batch', 'emcpy', 'defaults', 'figure.yaml')) def create_plot(self, layer_list, proj, domain): diff --git a/src/eva/plotting/batch/base/handlers/hvplot_figure_handler.py b/src/eva/plotting/batch/base/handlers/hvplot_figure_handler.py new file mode 100644 index 00000000..cfec1b0f --- /dev/null +++ b/src/eva/plotting/batch/base/handlers/hvplot_figure_handler.py @@ -0,0 +1,20 @@ +from eva.eva_path import return_eva_path +from eva.plotting.batch.hvplot.plot_tools.create_plots import CreatePlot, CreateFigure +import os + +class HvplotFigureHandler(): + + def __init__(self): + # Define default paths to modules + self.BACKEND_NAME = "Hvplot" + self.MODULE_NAME = "eva.plotting.batch.hvplot.diagnostics." + + def find_schema(self, figure_conf): + return figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', 'batch', + 'hvplot', 'defaults', 'figure.yaml')) + + def create_plot(self, layer_list, proj, domain): + return CreatePlot(plot_layers=layer_list, projection=proj, domain=domain) + + def create_figure(self, nrows, ncols, figsize): + return CreateFigure(nrows=nrows, ncols=ncols, figsize=figsize) diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index 5d2ab215..b55867d6 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -14,8 +14,6 @@ from eva.utilities.stats import stats_helper from eva.utilities.utils import get_schema, camelcase_to_underscore, parse_channel_list from eva.utilities.utils import replace_vars_dict -#from emcpy.plots.create_plots import CreatePlot, CreateFigure -from eva.plotting.batch.emcpy.plot_tools.emcpy_figure_handler import EmcpyFigureHandler import copy import importlib as im import os @@ -41,8 +39,20 @@ def figure_driver(config, data_collections, timing, logger): # Get list of graphics from configuration # ------------------- - graphics_section = config.get("graphics") - graphics = graphics_section.get("figure_list") + graphics_section = config.get('graphics') + graphics = graphics_section.get('figure_list') + + # Get plotting backend + # -------------------- + backend = graphics_section.get('plotting_backend') + + # Create handler + # -------------- + handler_class_name = backend + "FigureHandler" + handler_module_name = camelcase_to_underscore(handler_class_name) + handler_full_module = "eva.plotting.batch.base.handlers." + handler_module_name + handler_class = getattr(im.import_module(handler_full_module), handler_class_name) + handler = handler_class() # Loop through specified graphics # ------------------- @@ -58,10 +68,6 @@ def figure_driver(config, data_collections, timing, logger): # update figure conf based on schema # ---------------------------------- - #fig_schema = figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', - # 'emcpy', 'defaults', 'figure.yaml')) - - handler = EmcpyFigureHandler() fig_schema = handler.find_schema(figure_conf) figure_conf = get_schema(fig_schema, figure_conf, logger) @@ -155,7 +161,6 @@ def make_figure(handler, figure_conf, plots, dynamic_options, data_collections, layer_class = getattr(im.import_module(full_module), eva_class_name) layer = layer_class(layer, logger, data_collections) layer.data_prep() - # use the translator class to go from eva to declarative plotting layer_list.append(layer.configure_plot()) # get mapping dictionary proj = None diff --git a/src/eva/plotting/batch/hvplot/defaults/figure.yaml b/src/eva/plotting/batch/hvplot/defaults/figure.yaml new file mode 100644 index 00000000..62157f54 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/defaults/figure.yaml @@ -0,0 +1,6 @@ +# figure default schema +layout: [1,1] +figure file type: html +output path: ./ +output name: 'myfig' +figure size: [8,8] diff --git a/src/eva/plotting/batch/emcpy/plot_tools/__init__.py b/src/eva/plotting/batch/hvplot/diagnostics/__init__.py similarity index 74% rename from src/eva/plotting/batch/emcpy/plot_tools/__init__.py rename to src/eva/plotting/batch/hvplot/diagnostics/__init__.py index ac1c0bc4..ebda1763 100644 --- a/src/eva/plotting/batch/emcpy/plot_tools/__init__.py +++ b/src/eva/plotting/batch/hvplot/diagnostics/__init__.py @@ -1,4 +1,4 @@ -# (C) Copyright 2021-2022 United States Government as represented by the Administrator of the +# (C) Copyright 2023 United States Government as represented by the Administrator of the # National Aeronautics and Space Administration. All Rights Reserved. # # This software is licensed under the terms of the Apache Licence Version 2.0 diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py new file mode 100644 index 00000000..2b9e0f52 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py @@ -0,0 +1,24 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object +import os +#import hvplot +import pandas as pd +import hvplot.pandas + +from eva.plotting.batch.base.diagnostics.scatter import Scatter + +# -------------------------------------------------------------------------------------------------- + + +class HvplotScatter(Scatter): + + def configure_plot(self): + # Save and access name of variables + df = pd.DataFrame() + df['xdata'] = self.xdata + df['ydata'] = self.ydata + self.plotobj = df.hvplot.scatter('xdata', 'ydata', s=20, c='b', height=400, width=400) + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py b/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py new file mode 100644 index 00000000..f9722442 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py @@ -0,0 +1,126 @@ +from bokeh.plotting import save +import hvplot as hv +import os + +class CreatePlot: + + def __init__(self, plot_layers=[], projection=None, domain=None): + self.plot_layers = plot_layers + if projection: + self.projection = projection + if domain: + self.domain = domain + + def add_grid(self): + pass + + def add_legend(self, **kwargs): + self.legend = { + **kwargs + } + + def add_title(self, label, loc='center', + pad=None, **kwargs): + + self.title = { + 'label': label, + 'loc': loc, + 'pad': pad, + **kwargs + } + + def add_xlabel(self, xlabel, labelpad=None, + loc='center', **kwargs): + + self.xlabel = { + 'xlabel': xlabel, + 'labelpad': labelpad, + 'loc': loc, + **kwargs + } + + def add_ylabel(self, ylabel, labelpad=None, + loc='center', **kwargs): + + self.ylabel = { + 'ylabel': ylabel, + 'labelpad': labelpad, + 'loc': loc, + **kwargs + } + + def add_colorbar(self, label=None, fontsize=12, single_cbar=False, + cbar_location=None, **kwargs): + + kwargs.setdefault('orientation', 'horizontal') + + pad = 0.15 if kwargs['orientation'] == 'horizontal' else 0.1 + fraction = 0.065 if kwargs['orientation'] == 'horizontal' else 0.085 + + kwargs.setdefault('pad', pad) + kwargs.setdefault('fraction', fraction) + + if not cbar_location: + h_loc = [0.14, -0.1, 0.8, 0.04] + v_loc = [1.02, 0.12, 0.04, 0.8] + cbar_location = h_loc if kwargs['orientation'] == 'horizontal' else v_loc + + self.colorbar = { + 'label': label, + 'fontsize': fontsize, + 'single_cbar': single_cbar, + 'cbar_loc': cbar_location, + 'kwargs': kwargs + } + + def add_stats_dict(self, stats_dict={}, xloc=0.5, + yloc=-0.1, ha='center', **kwargs): + + self.stats = { + 'stats': stats_dict, + 'xloc': xloc, + 'yloc': yloc, + 'ha': ha, + 'kwargs': kwargs + } + + def add_text(self, xloc, yloc, text, transform='datacoords', + **kwargs): + + if not hasattr(self, 'text'): + self.text = [] + + self.text.append({ + 'xloc': xloc, + 'yloc': yloc, + 'text': text, + 'transform': transform, + 'kwargs': kwargs + }) + +class CreateFigure: + + def __init__(self, nrows=1, ncols=1, figsize=(8, 6)): + self.nrows = nrows + self.ncols = ncols + self.figsize = figsize + self.plot_list = [] + self.fig = None + + def create_figure(self): + self.fig = self.plot_list[0].plot_layers[0] + print(type(self.fig)) + + + def add_suptitle(self, text, **kwargs): + pass + + def save_figure(self, pathfile, **kwargs): + pathfile_dir = os.path.dirname(pathfile) + if not os.path.exists(pathfile_dir): + os.makedirs(pathfile_dir) + bokeh_fig = hv.render(self.fig, backend='bokeh') + save(bokeh_fig, pathfile) + + def close_figure(self): + pass diff --git a/src/eva/plotting/batch/hvplot/plot_tools/figure_driver.py b/src/eva/plotting/batch/hvplot/plot_tools/figure_driver.py deleted file mode 100644 index 025df6ac..00000000 --- a/src/eva/plotting/batch/hvplot/plot_tools/figure_driver.py +++ /dev/null @@ -1,157 +0,0 @@ -# (C) Copyright 2023 NOAA/NWS/EMC -# -# (C) Copyright 2023 United States Government as represented by the Administrator of the -# National Aeronautics and Space Administration. All Rights Reserved. -# -# This software is licensed under the terms of the Apache Licence Version 2.0 -# This software is licensed under the terms of the Apache Licence Version 2.0 -# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. - - -# -------------------------------------------------------------------------------------------------- - -from eva.utilities.utils import replace_vars_dict, parse_channel_list -from bokeh.plotting import figure, output_file, save -from bokeh.embed import components -import copy -import os -import pickle as pkl - -# -------------------------------------------------------------------------------------------------- - -def bokeh_figure_driver(config, data_collections, timing, logger): - """ - Generates and saves multiple figures based on the provided configuration. - - Args: - config (dict): A dictionary containing the configuration for generating figures. - data_collections (DataCollections): An instance of the DataCollections class containing - input data. - timing (Timing): A timing instance to measure the execution time. - logger (Logger): An instance of the logger for logging messages. - - This function generates and saves multiple figures based on the provided configuration. It - processes each graphic specified in the configuration and creates corresponding figures with - plots. This figure driver uses the bokeh backend to create interactive plots. - """ - - # Get list of graphics from configuration - # ------------------- - graphics_section = config.get("graphics") - graphics = graphics_section.get("figure_list") - - # Loop through specified graphics - # ------------------- - timing.start('Graphics Loop') - for graphic in graphics: - - # Parse configuration for this graphic - # ------------------- - batch_conf = graphic.get("batch figure", {}) # batch configuration (default nothing) - figure_conf = graphic.get("figure") # figure configuration - plot_conf = graphic.get("plot") - #dynamic_options_conf = graphic.get("dynamic options", []) # Dynamic overwrites - - # update figure conf based on schema - later - # ---------------------------------- - - # --------------------------------------- - if batch_conf: - # Get potential variables - variables = batch_conf.get('variables', []) - # Get list of channels - channels_str_or_list = batch_conf.get('channels', []) - channels = parse_channel_list(channels_str_or_list, logger) - - # Set some fake values to ensure the loops are entered - if 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: - 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 - batch_conf_this['variable_title'] = var_title - - # Replace templated variables in figure and plots config - figure_conf_fill = copy.copy(figure_conf) - figure_conf_fill = replace_vars_dict(figure_conf_fill, **batch_conf_this) - plot_conf_fill = copy.copy(plot_conf) - plot_conf_fill = replace_vars_dict(plot_conf_fill, **batch_conf_this) - #dynamic_options_conf_fill = copy.copy(dynamic_options_conf) - #dynamic_options_conf_fill = replace_vars_dict(dynamic_options_conf_fill, - # **batch_conf_this) - - # Make plot - #make_figure(figure_conf_fill, plots_conf_fill, - # dynamic_options_conf_fill, data_collections, logger) - make_figure(figure_conf_fill, plot_conf_fill, data_collections, logger) - else: - # make just one figure per configuration - #make_figure(figure_conf, plots_conf, dynamic_options_conf, data_collections, logger) - make_figure(figure_conf, plot_conf, data_collections, logger) - timing.stop('Graphics Loop') - -# -------------------------------------------------------------------------------------------------- - -def make_figure(figure_conf, plot, data_collections, logger): - """ - Generates a figure based on the provided configuration and plots. - - Args: - figure_conf (dict): A dictionary containing the configuration for the figure layout - and appearance. - plots (list): A list of dictionaries containing plot configurations. - data_collections (DataCollections): An instance of the DataCollections class containing - input data. - logger (Logger): An instance of the logger for logging messages. - - This function generates a figure based on the provided configuration and plot settings. It - processes the specified plots, applies dynamic options, and saves the generated figure. - """ - - # set up figure - fig = figure(title = figure_conf['title'], - x_axis_label = plot['x_axis_label'], - y_axis_label = plot['y_axis_label']) - - # retrieve variables from data collection - x_args = plot['x']['variable'].split('::') - y_args = plot['y']['variable'].split('::') - x = data_collections.get_variable_data(x_args[0], x_args[1], x_args[2]) - y = data_collections.get_variable_data(y_args[0], y_args[1], y_args[2]) - - # TODO handle this in a case/factory - fig.scatter(x, y) - - comp_name = figure_conf['components name'] - html_name = figure_conf['html name'] - - # create directories if they don't exist - html_dir_path = os.path.dirname(html_name) - if not os.path.exists(html_dir_path): - os.makedirs(html_dir_path) - - comp_dir_path = os.path.dirname(comp_name) - if not os.path.exists(comp_dir_path): - os.makedirs(comp_dir_path) - - # save html, div, script to file - script, div = components(fig) - dictionary = {'script': script, - 'div': div} - - with open(comp_name, 'wb') as f: - pkl.dump(dictionary, f) - - output_file(html_name) - save(fig) From 9bb34a373df5b877fec0b6eb0c45b40c3e6bf7df Mon Sep 17 00:00:00 2001 From: asewnath Date: Fri, 29 Sep 2023 12:19:41 -0400 Subject: [PATCH 06/17] more hvplot changes --- src/eva/data/data_collections.py | 61 ++++++---- .../base/plot_tools/.figure_driver.py.swo | Bin 0 -> 24576 bytes .../batch/base/plot_tools/figure_driver.py | 69 ++++++++---- .../batch/emcpy/diagnostics/line_plot.py | 11 +- .../handlers => emcpy/plot_tools}/__init__.py | 2 +- .../plot_tools}/emcpy_figure_handler.py | 4 - .../hvplot/diagnostics/hvplot_scatter.py | 26 ++++- .../batch/hvplot/plot_tools/create_plots.py | 105 +++++++----------- .../plot_tools}/hvplot_figure_handler.py | 4 - 9 files changed, 161 insertions(+), 121 deletions(-) create mode 100644 src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo rename src/eva/plotting/batch/{base/handlers => emcpy/plot_tools}/__init__.py (74%) rename src/eva/plotting/batch/{base/handlers => emcpy/plot_tools}/emcpy_figure_handler.py (71%) rename src/eva/plotting/batch/{base/handlers => hvplot/plot_tools}/hvplot_figure_handler.py (72%) 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/batch/base/plot_tools/.figure_driver.py.swo b/src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo new file mode 100644 index 0000000000000000000000000000000000000000..626a2e3175d8cb20d0e69d1719102c0c28406431 GIT binary patch literal 24576 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 literal 0 HcmV?d00001 diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index b55867d6..dbd6634e 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -44,13 +44,17 @@ def figure_driver(config, data_collections, timing, logger): # Get plotting backend # -------------------- - backend = graphics_section.get('plotting_backend') + if 'plotting_backend' in graphics_section: + backend = graphics_section.get('plotting_backend') + else: + # use emcpy as default + backend = 'Emcpy' # Create handler # -------------- - handler_class_name = backend + "FigureHandler" + handler_class_name = backend + 'FigureHandler' handler_module_name = camelcase_to_underscore(handler_class_name) - handler_full_module = "eva.plotting.batch.base.handlers." + handler_module_name + handler_full_module = 'eva.plotting.batch.' + backend.lower() + '.plot_tools.' + handler_module_name handler_class = getattr(im.import_module(handler_full_module), handler_class_name) handler = handler_class() @@ -68,7 +72,8 @@ def figure_driver(config, data_collections, timing, logger): # update figure conf based on schema # ---------------------------------- - fig_schema = handler.find_schema(figure_conf) + fig_schema = figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', 'batch', + backend.lower(), 'defaults', 'figure.yaml')) figure_conf = get_schema(fig_schema, figure_conf, logger) # pass configurations and make graphic(s) @@ -76,27 +81,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 @@ -111,6 +129,7 @@ def figure_driver(config, data_collections, timing, logger): # Make plot make_figure(handler, figure_conf_fill, plots_conf_fill, dynamic_options_conf_fill, data_collections, logger) + else: # make just one figure per configuration make_figure(handler, figure_conf, plots_conf, dynamic_options_conf, data_collections, logger) @@ -155,13 +174,23 @@ def make_figure(handler, figure_conf, plots, dynamic_options, data_collections, for plot in plots: layer_list = [] for layer in plot.get("layers"): - eva_class_name = handler.BACKEND_NAME + layer.get("type") - eva_module_name = camelcase_to_underscore(eva_class_name) - full_module = handler.MODULE_NAME + eva_module_name - layer_class = getattr(im.import_module(full_module), eva_class_name) - layer = layer_class(layer, logger, data_collections) - layer.data_prep() - layer_list.append(layer.configure_plot()) + + #Temporary case to handle different diagnostics + if handler.BACKEND_NAME == 'Emcpy': + eva_class_name = layer.get("type") + eva_module_name = camelcase_to_underscore(eva_class_name) + full_module = "eva.plotting.emcpy.diagnostics."+eva_module_name + layer_class = getattr(im.import_module(full_module), eva_class_name) + layer_list.append(layer_class(layer, logger, data_collections).plotobj) + else: + eva_class_name = handler.BACKEND_NAME + layer.get("type") + eva_module_name = camelcase_to_underscore(eva_class_name) + full_module = handler.MODULE_NAME + eva_module_name + layer_class = getattr(im.import_module(full_module), eva_class_name) + layer = layer_class(layer, logger, data_collections) + layer.data_prep() + layer_list.append(layer.configure_plot()) + # get mapping dictionary proj = None domain = None diff --git a/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py b/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py index 1ea34603..8b8c2d14 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py +++ b/src/eva/plotting/batch/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/batch/base/handlers/__init__.py b/src/eva/plotting/batch/emcpy/plot_tools/__init__.py similarity index 74% rename from src/eva/plotting/batch/base/handlers/__init__.py rename to src/eva/plotting/batch/emcpy/plot_tools/__init__.py index ebda1763..ac1c0bc4 100644 --- a/src/eva/plotting/batch/base/handlers/__init__.py +++ b/src/eva/plotting/batch/emcpy/plot_tools/__init__.py @@ -1,4 +1,4 @@ -# (C) Copyright 2023 United States Government as represented by the Administrator of the +# (C) Copyright 2021-2022 United States Government as represented by the Administrator of the # National Aeronautics and Space Administration. All Rights Reserved. # # This software is licensed under the terms of the Apache Licence Version 2.0 diff --git a/src/eva/plotting/batch/base/handlers/emcpy_figure_handler.py b/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py similarity index 71% rename from src/eva/plotting/batch/base/handlers/emcpy_figure_handler.py rename to src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py index fa7cd23b..30f5343c 100644 --- a/src/eva/plotting/batch/base/handlers/emcpy_figure_handler.py +++ b/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py @@ -9,10 +9,6 @@ def __init__(self): self.BACKEND_NAME = "Emcpy" self.MODULE_NAME = "eva.plotting.batch.emcpy.diagnostics." - def find_schema(self, figure_conf): - return figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', 'batch', - 'emcpy', 'defaults', 'figure.yaml')) - def create_plot(self, layer_list, proj, domain): return CreatePlot(plot_layers=layer_list, projection=proj, domain=domain) diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py index 2b9e0f52..b9391b71 100644 --- a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py @@ -5,6 +5,8 @@ #import hvplot import pandas as pd import hvplot.pandas +import holoviews as hv +from scipy.stats import linregress from eva.plotting.batch.base.diagnostics.scatter import Scatter @@ -18,7 +20,27 @@ def configure_plot(self): df = pd.DataFrame() df['xdata'] = self.xdata df['ydata'] = self.ydata - self.plotobj = df.hvplot.scatter('xdata', 'ydata', s=20, c='b', height=400, width=400) - return self.plotobj + color = self.config['color'] + size = self.config['markersize'] + label = self.config['label'] + + #add line statistics to legend label + try: + plot_for_slope = df.hvplot.scatter('xdata', 'ydata') + slope = hv.Slope.from_scatter(plot_for_slope) + slope_attrs = linregress(self.xdata, self.ydata) + slope_expression = 'y='+f'{slope.slope:.3f}'+"x+"+f'{slope.y_intercept:.3f}' + r_sq = ', r^2: ' + f'{slope_attrs.rvalue**2:.3f}' + slope_label = "y="+f'{slope.slope:.3f}'+"x+"+f'{slope.y_intercept:.3f}'+r_sq + plot = df.hvplot.scatter('xdata', 'ydata', s=size, c=color, label=label+", "+slope_label) + new_plot = hv.Overlay([plot, plot]) + plotobj = new_plot * slope + plotobj.opts(show_legend=False) + except: + plot = df.hvplot.scatter('xdata', 'ydata', s=size, c=color, label=label) + plotobj = hv.Overlay([plot, plot]) + plotobj.opts(show_legend=False) + + return plotobj # -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py b/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py index f9722442..5016e3a2 100644 --- a/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py +++ b/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py @@ -1,4 +1,4 @@ -from bokeh.plotting import save +from bokeh.plotting import save, output_file import hvplot as hv import os @@ -11,27 +11,18 @@ def __init__(self, plot_layers=[], projection=None, domain=None): if domain: self.domain = domain - def add_grid(self): - pass - - def add_legend(self, **kwargs): - self.legend = { + def add_grid(self, **kwargs): + self.grid = { **kwargs } - def add_title(self, label, loc='center', - pad=None, **kwargs): - - self.title = { - 'label': label, - 'loc': loc, - 'pad': pad, + def add_legend(self, **kwargs): + self.legend = { **kwargs } def add_xlabel(self, xlabel, labelpad=None, loc='center', **kwargs): - self.xlabel = { 'xlabel': xlabel, 'labelpad': labelpad, @@ -41,7 +32,6 @@ def add_xlabel(self, xlabel, labelpad=None, def add_ylabel(self, ylabel, labelpad=None, loc='center', **kwargs): - self.ylabel = { 'ylabel': ylabel, 'labelpad': labelpad, @@ -49,54 +39,6 @@ def add_ylabel(self, ylabel, labelpad=None, **kwargs } - def add_colorbar(self, label=None, fontsize=12, single_cbar=False, - cbar_location=None, **kwargs): - - kwargs.setdefault('orientation', 'horizontal') - - pad = 0.15 if kwargs['orientation'] == 'horizontal' else 0.1 - fraction = 0.065 if kwargs['orientation'] == 'horizontal' else 0.085 - - kwargs.setdefault('pad', pad) - kwargs.setdefault('fraction', fraction) - - if not cbar_location: - h_loc = [0.14, -0.1, 0.8, 0.04] - v_loc = [1.02, 0.12, 0.04, 0.8] - cbar_location = h_loc if kwargs['orientation'] == 'horizontal' else v_loc - - self.colorbar = { - 'label': label, - 'fontsize': fontsize, - 'single_cbar': single_cbar, - 'cbar_loc': cbar_location, - 'kwargs': kwargs - } - - def add_stats_dict(self, stats_dict={}, xloc=0.5, - yloc=-0.1, ha='center', **kwargs): - - self.stats = { - 'stats': stats_dict, - 'xloc': xloc, - 'yloc': yloc, - 'ha': ha, - 'kwargs': kwargs - } - - def add_text(self, xloc, yloc, text, transform='datacoords', - **kwargs): - - if not hasattr(self, 'text'): - self.text = [] - - self.text.append({ - 'xloc': xloc, - 'yloc': yloc, - 'text': text, - 'transform': transform, - 'kwargs': kwargs - }) class CreateFigure: @@ -108,19 +50,50 @@ def __init__(self, nrows=1, ncols=1, figsize=(8, 6)): self.fig = None def create_figure(self): + # Needs work, need to combine all the layers + # and figure out how subplots will work self.fig = self.plot_list[0].plot_layers[0] - print(type(self.fig)) + plot_obj = self.plot_list[0] + # Add all features to the figure + for feat in vars(plot_obj).keys(): + self._plot_features(plot_obj, feat) def add_suptitle(self, text, **kwargs): - pass + self.fig.opts(title=text) def save_figure(self, pathfile, **kwargs): pathfile_dir = os.path.dirname(pathfile) if not os.path.exists(pathfile_dir): os.makedirs(pathfile_dir) bokeh_fig = hv.render(self.fig, backend='bokeh') - save(bokeh_fig, pathfile) + #save(bokeh_fig, pathfile) + output_file(pathfile) + save(bokeh_fig) def close_figure(self): pass + + def _plot_features(self, plot_obj, feature): + + feature_dict = { + 'xlabel': self._plot_xlabel, + 'ylabel': self._plot_ylabel, + 'legend': self._plot_legend, + 'grid': self._plot_grid, + } + + if feature in feature_dict: + feature_dict[feature](vars(plot_obj)[feature]) + + def _plot_grid(self, grid): + self.fig.opts(show_grid=True) + + def _plot_xlabel(self, xlabel): + self.fig.opts(xlabel=xlabel['xlabel']) + + def _plot_ylabel(self, ylabel): + self.fig.opts(ylabel=ylabel['ylabel']) + + def _plot_legend(self, legend): + self.fig.opts(legend_position='top_left', show_legend=True) diff --git a/src/eva/plotting/batch/base/handlers/hvplot_figure_handler.py b/src/eva/plotting/batch/hvplot/plot_tools/hvplot_figure_handler.py similarity index 72% rename from src/eva/plotting/batch/base/handlers/hvplot_figure_handler.py rename to src/eva/plotting/batch/hvplot/plot_tools/hvplot_figure_handler.py index cfec1b0f..e3b5ab33 100644 --- a/src/eva/plotting/batch/base/handlers/hvplot_figure_handler.py +++ b/src/eva/plotting/batch/hvplot/plot_tools/hvplot_figure_handler.py @@ -9,10 +9,6 @@ def __init__(self): self.BACKEND_NAME = "Hvplot" self.MODULE_NAME = "eva.plotting.batch.hvplot.diagnostics." - def find_schema(self, figure_conf): - return figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', 'batch', - 'hvplot', 'defaults', 'figure.yaml')) - def create_plot(self, layer_list, proj, domain): return CreatePlot(plot_layers=layer_list, projection=proj, domain=domain) From 0214ed85fa45bf9f0000f937f20bb8ef9be47355 Mon Sep 17 00:00:00 2001 From: asewnath Date: Fri, 29 Sep 2023 12:28:51 -0400 Subject: [PATCH 07/17] adding figure_list to tests --- src/eva/tests/config/testCubedSphereRestart.yaml | 1 + src/eva/tests/config/testGsiObsSpaceAmsuaMetop-A.yaml | 2 ++ src/eva/tests/config/testGsiObsSpaceConvT.yaml | 2 ++ src/eva/tests/config/testIodaObsSpaceAircraft.yaml | 1 + src/eva/tests/config/testIodaObsSpaceAmsuaN19.yaml | 1 + src/eva/tests/config/testIodaObsSpaceIASI_Metop-A.yaml | 1 + src/eva/tests/config/testJediLog.yaml | 2 ++ src/eva/tests/config/testLatLon.yaml | 2 ++ src/eva/tests/config/testMonDataSpaceHirs4Metop-A.yaml | 2 ++ src/eva/tests/config/testMonSummary.yaml | 2 ++ src/eva/tests/config/testSocaRestart.yaml | 2 ++ src/eva/tests/config/testTwoDatasetsOnePlot.yaml | 2 ++ 12 files changed, 20 insertions(+) diff --git a/src/eva/tests/config/testCubedSphereRestart.yaml b/src/eva/tests/config/testCubedSphereRestart.yaml index b8e8950e..7f039961 100644 --- a/src/eva/tests/config/testCubedSphereRestart.yaml +++ b/src/eva/tests/config/testCubedSphereRestart.yaml @@ -19,6 +19,7 @@ datasets: orography variables: [geolon, geolat] graphics: + figure_list: # Map plots # --------- diff --git a/src/eva/tests/config/testGsiObsSpaceAmsuaMetop-A.yaml b/src/eva/tests/config/testGsiObsSpaceAmsuaMetop-A.yaml index 2740b5f1..42c1d28b 100644 --- a/src/eva/tests/config/testGsiObsSpaceAmsuaMetop-A.yaml +++ b/src/eva/tests/config/testGsiObsSpaceAmsuaMetop-A.yaml @@ -23,6 +23,8 @@ transforms: variable: *variables graphics: + figure_list: + # Histogram plots # --------------- diff --git a/src/eva/tests/config/testGsiObsSpaceConvT.yaml b/src/eva/tests/config/testGsiObsSpaceConvT.yaml index 9cee4552..ec317fe9 100644 --- a/src/eva/tests/config/testGsiObsSpaceConvT.yaml +++ b/src/eva/tests/config/testGsiObsSpaceConvT.yaml @@ -12,6 +12,8 @@ datasets: Longitude] graphics: + figure_list: + # Map plots # --------- diff --git a/src/eva/tests/config/testIodaObsSpaceAircraft.yaml b/src/eva/tests/config/testIodaObsSpaceAircraft.yaml index 8aad999f..9be67a61 100644 --- a/src/eva/tests/config/testIodaObsSpaceAircraft.yaml +++ b/src/eva/tests/config/testIodaObsSpaceAircraft.yaml @@ -72,6 +72,7 @@ transforms: variable: *variables graphics: + figure_list: # Observation correlation scatter plots # ------------------------------------- diff --git a/src/eva/tests/config/testIodaObsSpaceAmsuaN19.yaml b/src/eva/tests/config/testIodaObsSpaceAmsuaN19.yaml index 8c25d2a8..47798830 100644 --- a/src/eva/tests/config/testIodaObsSpaceAmsuaN19.yaml +++ b/src/eva/tests/config/testIodaObsSpaceAmsuaN19.yaml @@ -75,6 +75,7 @@ transforms: variable: *variables graphics: + figure_list: # Correlation scatter plots # ------------------------- diff --git a/src/eva/tests/config/testIodaObsSpaceIASI_Metop-A.yaml b/src/eva/tests/config/testIodaObsSpaceIASI_Metop-A.yaml index b2f319eb..a723e1e0 100644 --- a/src/eva/tests/config/testIodaObsSpaceIASI_Metop-A.yaml +++ b/src/eva/tests/config/testIodaObsSpaceIASI_Metop-A.yaml @@ -51,6 +51,7 @@ transforms: variable: *variables graphics: + figure_list: # ---------- Statistical Plot ---------- # JEDI h(x) vs GSI h(x) diff --git a/src/eva/tests/config/testJediLog.yaml b/src/eva/tests/config/testJediLog.yaml index 2ace22d0..5d4e5651 100644 --- a/src/eva/tests/config/testJediLog.yaml +++ b/src/eva/tests/config/testJediLog.yaml @@ -13,6 +13,8 @@ datasets: # Make plots graphics: + figure_list: + - figure: layout: [3,1] figure size: [12,10] diff --git a/src/eva/tests/config/testLatLon.yaml b/src/eva/tests/config/testLatLon.yaml index 6cc0cf20..3bc6de48 100644 --- a/src/eva/tests/config/testLatLon.yaml +++ b/src/eva/tests/config/testLatLon.yaml @@ -6,6 +6,8 @@ datasets: variables: [T_inc, lat, lon] graphics: + figure_list: + # Map plots # --------- diff --git a/src/eva/tests/config/testMonDataSpaceHirs4Metop-A.yaml b/src/eva/tests/config/testMonDataSpaceHirs4Metop-A.yaml index 8f235818..97437851 100644 --- a/src/eva/tests/config/testMonDataSpaceHirs4Metop-A.yaml +++ b/src/eva/tests/config/testMonDataSpaceHirs4Metop-A.yaml @@ -19,6 +19,8 @@ datasets: variables: &variables [count, cycle] graphics: + figure_list: + # Time series plots # --------------- - figure: diff --git a/src/eva/tests/config/testMonSummary.yaml b/src/eva/tests/config/testMonSummary.yaml index afaa04db..56d9b4ee 100644 --- a/src/eva/tests/config/testMonSummary.yaml +++ b/src/eva/tests/config/testMonSummary.yaml @@ -81,6 +81,8 @@ transforms: graphics: + figure_list: + # Summary plots # --------------- - figure: diff --git a/src/eva/tests/config/testSocaRestart.yaml b/src/eva/tests/config/testSocaRestart.yaml index ed751eb9..e551c62b 100644 --- a/src/eva/tests/config/testSocaRestart.yaml +++ b/src/eva/tests/config/testSocaRestart.yaml @@ -10,6 +10,8 @@ datasets: coordinate variables: [lon, lat] graphics: + figure_list: + # Map plots # --------- diff --git a/src/eva/tests/config/testTwoDatasetsOnePlot.yaml b/src/eva/tests/config/testTwoDatasetsOnePlot.yaml index 1d3eca7c..d25e74c7 100644 --- a/src/eva/tests/config/testTwoDatasetsOnePlot.yaml +++ b/src/eva/tests/config/testTwoDatasetsOnePlot.yaml @@ -27,6 +27,8 @@ datasets: orography variables: [geolon, geolat] graphics: + figure_list: + # Map plots # --------- From 6e48b505f1190ab651026e6d996213e66fc521ec Mon Sep 17 00:00:00 2001 From: asewnath Date: Fri, 29 Sep 2023 14:36:52 -0400 Subject: [PATCH 08/17] adding in new test for hvplot --- .../testHvplotIodaObsSpaceAmsuaN19.yaml | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 src/eva/tests/config/testHvplotIodaObsSpaceAmsuaN19.yaml diff --git a/src/eva/tests/config/testHvplotIodaObsSpaceAmsuaN19.yaml b/src/eva/tests/config/testHvplotIodaObsSpaceAmsuaN19.yaml new file mode 100644 index 00000000..08fb915e --- /dev/null +++ b/src/eva/tests/config/testHvplotIodaObsSpaceAmsuaN19.yaml @@ -0,0 +1,159 @@ +datasets: + - name: experiment + type: IodaObsSpace + filenames: + - ${data_input_path}/ioda_obs_space.amsua_n19.hofx.2020-12-14T210000Z.nc4 + channels: &channels 3,8 + # Note: channelNumber is automatically added to the output and should not + # be listed below + groups: + - name: ObsValue + variables: &variables [brightnessTemperature] + - name: GsiHofXBc + #- name: GsiEffectiveQC + - name: hofx + - name: EffectiveQC + - name: MetaData + +transforms: + + # Generate omb for GSI + - transform: arithmetic + new name: experiment::ObsValueMinusGsiHofXBc::${variable} + equals: experiment::ObsValue::${variable}-experiment::GsiHofXBc::${variable} + for: + variable: *variables + + # Generate omb for JEDI + - transform: arithmetic + new name: experiment::ObsValueMinusHofx::${variable} + equals: experiment::ObsValue::${variable}-experiment::hofx::${variable} + for: + variable: *variables + + # Generate hofx difference + - transform: arithmetic + new name: experiment::HofxMinusGsiHofXBc::${variable} + equals: experiment::hofx::${variable}-experiment::GsiHofXBc::${variable} + for: + variable: *variables + + # Generate hofx that passed QC for JEDI + - transform: accept where + new name: experiment::hofxPassedQc::${variable} + starting field: experiment::hofx::${variable} + where: + - experiment::EffectiveQC::${variable} == 0 + for: + variable: *variables + + # Generate GSI hofx that passed JEDI QC + - transform: accept where + new name: experiment::GsiHofXBcPassedQc::${variable} + starting field: experiment::GsiHofXBc::${variable} + where: + - experiment::EffectiveQC::${variable} == 0 + for: + variable: *variables + + # Generate omb that passed QC for JEDI + - transform: accept where + new name: experiment::ObsValueMinushofxPassedQc::${variable} + starting field: experiment::ObsValueMinusHofx::${variable} + where: + - experiment::EffectiveQC::${variable} == 0 + for: + variable: *variables + + # Generate omb that passed QC for GSI + - transform: accept where + new name: experiment::ObsValueMinusGsiHofXBcPassedQc::${variable} + starting field: experiment::ObsValueMinusGsiHofXBc::${variable} + where: + - experiment::EffectiveQC::${variable} == 0 + for: + variable: *variables + +graphics: + plotting_backend: Hvplot + figure_list: + + # Correlation scatter plots + # ------------------------- + + + # GSI h(x) vs Observations + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'Observations vs. GSI h(x) | AMSU-A NOAA-19 | ${variable_title}' + output name: observation_hvplot_scatter_plots/amsua_n19/${variable}/${channel}/gsi_hofx_vs_obs_amsua_n19_${variable}_${channel}.html + plots: + - add_xlabel: 'Observation Value' + add_ylabel: 'GSI h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::ObsValue::${variable} + y: + variable: experiment::GsiHofXBc::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'GSI h(x) versus obs (all obs)' + + # JEDI h(x) vs GSI h(x) + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'JEDI h(x) vs. GSI h(x) | AMSU-A NOAA-19 | ${variable_title}' + output name: observation_hvplot_scatter_plots/amsua_n19/${variable}/${channel}/gsi_hofx_vs_jedi_hofx_amsua_n19_${variable}_${channel}.html + plots: + - add_xlabel: 'GSI h(x)' + add_ylabel: 'JEDI h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::GsiHofXBc::${variable} + y: + variable: experiment::hofx::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'JEDI h(x) versus GSI h(x)' + + # JEDI omb vs GSI omb + - batch figure: + variables: *variables + channels: *channels + figure: + layout: [1,1] + title: 'JEDI omb vs. GSI omb| AMSU-A NOAA-19 | ${variable_title}' + output name: observation_hvplot_scatter_plots/amsua_n19/${variable}/${channel}/gsi_omb_vs_jedi_omb_amsua_n19_${variable}_${channel}.html + plots: + - add_xlabel: 'GSI observation minus h(x)' + add_ylabel: 'JEDI observation minus h(x)' + add_grid: + add_legend: + loc: 'upper left' + layers: + - type: Scatter + x: + variable: experiment::ObsValueMinusGsiHofXBc::${variable} + y: + variable: experiment::ObsValueMinusHofx::${variable} + channel: ${channel} + markersize: 5 + color: 'black' + label: 'GSI omb vs JEDI omb (all obs)' + From 63d7112862673d4a517647ac77e66e9ef381e9a5 Mon Sep 17 00:00:00 2001 From: asewnath Date: Fri, 29 Sep 2023 15:45:53 -0400 Subject: [PATCH 09/17] python code style --- .../batch/base/diagnostics/scatter.py | 12 +- .../batch/base/plot_tools/figure_driver.py | 19 ++-- .../batch/emcpy/diagnostics/density.py | 3 +- .../batch/emcpy/diagnostics/emcpy_scatter.py | 2 +- .../batch/emcpy/diagnostics/histogram.py | 3 +- .../emcpy/diagnostics/horizontal_line.py | 2 +- .../batch/emcpy/diagnostics/line_plot.py | 3 +- .../batch/emcpy/diagnostics/map_gridded.py | 2 +- .../batch/emcpy/diagnostics/map_scatter.py | 2 +- .../batch/emcpy/diagnostics/scatter.py | 105 ++++++++++++++++++ .../batch/emcpy/diagnostics/vertical_line.py | 3 +- .../emcpy/plot_tools/emcpy_figure_handler.py | 3 +- .../hvplot/diagnostics/hvplot_scatter.py | 12 +- .../batch/hvplot/plot_tools/create_plots.py | 6 +- .../plot_tools/hvplot_figure_handler.py | 1 + 15 files changed, 143 insertions(+), 35 deletions(-) create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/scatter.py diff --git a/src/eva/plotting/batch/base/diagnostics/scatter.py b/src/eva/plotting/batch/base/diagnostics/scatter.py index beaacd40..f3d743e4 100644 --- a/src/eva/plotting/batch/base/diagnostics/scatter.py +++ b/src/eva/plotting/batch/base/diagnostics/scatter.py @@ -1,7 +1,6 @@ from eva.eva_path import return_eva_path from eva.utilities.config import get from eva.utilities.utils import get_schema, update_object, slice_var_from_str -#import emcpy.plots.plots import os import numpy as np @@ -53,7 +52,7 @@ def __init__(self, config, logger, dataobj): # -------------------------------------------------------------------------------------------------- - def data_prep(self): + def data_prep(self): # Get the data to plot from the data_collection # --------------------------------------------- @@ -64,11 +63,11 @@ def data_prep(self): var1_cgv = var1.split('::') if len(var0_cgv) != 3: - self.logger.abort('In Scatter comparison the first variable \'var0\' does not appear to ' + - 'be in the required format of collection::group::variable.') + self.logger.abort('Scatter: comparison first var \'var0\' does not appear to ' + + 'be in the required format of collection::group::variable.') if len(var1_cgv) != 3: - self.logger.abort('In Scatter comparison the first variable \'var1\' does not appear to ' + - 'be in the required format of collection::group::variable.') + self.logger.abort('Scatter: comparison first var \'var1\' does not appear to ' + + 'be in the required format of collection::group::variable.') # Optionally get the channel to plot channel = None @@ -97,7 +96,6 @@ def data_prep(self): self.xdata = xdata[mask] self.ydata = ydata[mask] - @abstractmethod def configure_plot(self): pass diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index dbd6634e..91d2e1d1 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -54,7 +54,8 @@ def figure_driver(config, data_collections, timing, logger): # -------------- handler_class_name = backend + 'FigureHandler' handler_module_name = camelcase_to_underscore(handler_class_name) - handler_full_module = 'eva.plotting.batch.' + backend.lower() + '.plot_tools.' + handler_module_name + handler_full_module = 'eva.plotting.batch.' + \ + backend.lower() + '.plot_tools.' + handler_module_name handler_class = getattr(im.import_module(handler_full_module), handler_class_name) handler = handler_class() @@ -73,7 +74,7 @@ def figure_driver(config, data_collections, timing, logger): # update figure conf based on schema # ---------------------------------- fig_schema = figure_conf.get('schema', os.path.join(return_eva_path(), 'plotting', 'batch', - backend.lower(), 'defaults', 'figure.yaml')) + backend.lower(), 'defaults', 'figure.yaml')) figure_conf = get_schema(fig_schema, figure_conf, logger) # pass configurations and make graphic(s) @@ -132,7 +133,8 @@ def figure_driver(config, data_collections, timing, logger): else: # make just one figure per configuration - make_figure(handler, figure_conf, plots_conf, dynamic_options_conf, data_collections, logger) + make_figure(handler, figure_conf, plots_conf, + dynamic_options_conf, data_collections, logger) timing.stop('Graphics Loop') @@ -159,7 +161,8 @@ def make_figure(handler, figure_conf, plots, dynamic_options, data_collections, # Adjust the plots configs if there are dynamic options # ----------------------------------------------------- for dynamic_option in dynamic_options: - dynamic_option_module = im.import_module("eva.plotting.batch.base.plot_tools.dynamic_config") + mod_name = "eva.plotting.batch.base.plot_tools.dynamic_config" + dynamic_option_module = im.import_module(mod_name) dynamic_option_method = getattr(dynamic_option_module, dynamic_option['type']) plots = dynamic_option_method(logger, dynamic_option, plots, data_collections) @@ -175,11 +178,11 @@ def make_figure(handler, figure_conf, plots, dynamic_options, data_collections, layer_list = [] for layer in plot.get("layers"): - #Temporary case to handle different diagnostics + # Temporary case to handle different diagnostics if handler.BACKEND_NAME == 'Emcpy': eva_class_name = layer.get("type") eva_module_name = camelcase_to_underscore(eva_class_name) - full_module = "eva.plotting.emcpy.diagnostics."+eva_module_name + full_module = "eva.plotting.batch.emcpy.diagnostics."+eva_module_name layer_class = getattr(im.import_module(full_module), eva_class_name) layer_list.append(layer_class(layer, logger, data_collections).plotobj) else: @@ -201,7 +204,6 @@ def make_figure(handler, figure_conf, plots, dynamic_options, data_collections, domain = mapoptions['domain'] # create a subplot based on specified layers - #plotobj = CreatePlot(plot_layers=layer_list, projection=proj, domain=domain) plotobj = handler.create_plot(layer_list, proj, domain) # make changes to subplot based on YAML configuration for key, value in plot.items(): @@ -221,9 +223,6 @@ def make_figure(handler, figure_conf, plots, dynamic_options, data_collections, nrows = figure_conf['layout'][0] ncols = figure_conf['layout'][1] figsize = tuple(figure_conf['figure size']) - #fig = CreateFigure(nrows=figure_conf['layout'][0], - # ncols=figure_conf['layout'][1], - # figsize=tuple(figure_conf['figure size'])) fig = handler.create_figure(nrows, ncols, figsize) fig.plot_list = plot_list fig.create_figure() diff --git a/src/eva/plotting/batch/emcpy/diagnostics/density.py b/src/eva/plotting/batch/emcpy/diagnostics/density.py index 612444db..e5b12da3 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/density.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/density.py @@ -79,7 +79,8 @@ def __init__(self, config, logger, dataobj): # Get defaults from schema # ------------------------ layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'density.yaml')) + 'batch', 'emcpy', 'defaults', + 'density.yaml')) config = get_schema(layer_schema, config, logger) delvars = ['type', 'schema', 'data'] for d in delvars: diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py index 9236042f..121d4bff 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py @@ -20,7 +20,7 @@ def configure_plot(self): # Get defaults from schema # ------------------------ layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'scatter.yaml')) + 'emcpy', 'defaults', 'scatter.yaml')) new_config = get_schema(layer_schema, self.config, self.logger) delvars = ['x', 'y', 'type', 'schema'] for d in delvars: diff --git a/src/eva/plotting/batch/emcpy/diagnostics/histogram.py b/src/eva/plotting/batch/emcpy/diagnostics/histogram.py index 009d79a4..bf9f0905 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/histogram.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/histogram.py @@ -79,7 +79,8 @@ def __init__(self, config, logger, dataobj): # Get defaults from schema # ------------------------ layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'histogram.yaml')) + 'batch', 'emcpy', 'defaults', + 'histogram.yaml')) config = get_schema(layer_schema, config, logger) delvars = ['type', 'schema', 'data'] for d in delvars: diff --git a/src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py b/src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py index 41544572..b89ddc71 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py @@ -49,7 +49,7 @@ def __init__(self, config, logger, dataobj): # Get defaults from schema # ------------------------ layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', + 'batch', 'emcpy', 'defaults', 'horizontal_line.yaml')) config = get_schema(layer_schema, config, logger) delvars = ['type', 'schema'] diff --git a/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py b/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py index 8b8c2d14..1738cf8b 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py @@ -94,7 +94,8 @@ def __init__(self, config, logger, dataobj): # Get defaults from schema # ------------------------ layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'line_plot.yaml')) + 'batch', 'emcpy', 'defaults', + 'line_plot.yaml')) config = get_schema(layer_schema, config, logger) delvars = ['x', 'y', 'type', 'schema', 'channel', 'level'] for d in delvars: diff --git a/src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py b/src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py index 6668f5fe..1e09057c 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py @@ -58,7 +58,7 @@ def __init__(self, config, logger, dataobj): layer_schema = config.get("schema", os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', + 'batch', 'emcpy', 'defaults', 'map_gridded.yaml')) config = get_schema(layer_schema, config, logger) delvars = ['longitude', 'latitude', 'data', 'type', 'schema'] diff --git a/src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py index 81356d66..9544aba1 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py @@ -71,7 +71,7 @@ def __init__(self, config, logger, dataobj): layer_schema = config.get("schema", os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', + 'batch', 'emcpy', 'defaults', 'map_scatter.yaml')) config = get_schema(layer_schema, config, logger) delvars = ['longitude', 'latitude', 'data', 'type', 'schema'] diff --git a/src/eva/plotting/batch/emcpy/diagnostics/scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/scatter.py new file mode 100644 index 00000000..f5e084ae --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/scatter.py @@ -0,0 +1,105 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object, slice_var_from_str +import emcpy.plots.plots +import os +import numpy as np + + +# -------------------------------------------------------------------------------------------------- + + +class Scatter(): + + """Base class for creating scatter plots.""" + + def __init__(self, config, logger, dataobj): + + """ + Creates a scatter plot on a map based on the provided configuration. + + Args: + config (dict): A dictionary containing the configuration for the scatter plot on a map. + logger (Logger): An instance of the logger for logging messages. + dataobj: An instance of the data object containing input data. + + This class initializes and configures a scatter plot on a map based on the provided + configuration. The scatter plot is created using a declarative plotting library from EMCPy + (https://github.com/NOAA-EMC/emcpy). + + Example: + + :: + + config = { + "longitude": {"variable": "collection::group::variable"}, + "latitude": {"variable": "collection::group::variable"}, + "data": {"variable": "collection::group::variable", + "channel": "channel_name"}, + "plot_property": "property_value", + "plot_option": "option_value", + "schema": "path_to_schema_file.yaml" + } + logger = Logger() + map_scatter_plot = MapScatter(config, logger, None) + """ + + # Get the data to plot from the data_collection + # --------------------------------------------- + var0 = config['x']['variable'] + var1 = config['y']['variable'] + + var0_cgv = var0.split('::') + var1_cgv = var1.split('::') + + if len(var0_cgv) != 3: + logger.abort('In Scatter comparison the first variable \'var0\' does not appear to ' + + 'be in the required format of collection::group::variable.') + if len(var1_cgv) != 3: + 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 + channel = None + if 'channel' in config: + channel = config.get('channel') + + xdata = dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2], channel) + xdata1 = dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2]) + ydata = dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], var1_cgv[2], channel) + + # see if we need to slice data + xdata = slice_var_from_str(config['x'], xdata, logger) + ydata = slice_var_from_str(config['y'], ydata, logger) + + # scatter data should be flattened + xdata = xdata.flatten() + ydata = ydata.flatten() + + # Remove NaN values to enable regression + # -------------------------------------- + mask = ~np.isnan(xdata) + xdata = xdata[mask] + ydata = ydata[mask] + + mask = ~np.isnan(ydata) + xdata = xdata[mask] + ydata = ydata[mask] + + # Create declarative plotting Scatter object + # ------------------------------------------ + self.plotobj = emcpy.plots.plots.Scatter(xdata, ydata) + + # Get defaults from schema + # ------------------------ + layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'batch', 'emcpy', 'defaults', + 'scatter.yaml')) + config = get_schema(layer_schema, config, logger) + delvars = ['x', 'y', 'type', 'schema'] + for d in delvars: + config.pop(d, None) + self.plotobj = update_object(self.plotobj, config, logger) + + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py b/src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py index 6bcf94d3..a16c67a9 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py @@ -50,7 +50,8 @@ def __init__(self, config, logger, dataobj): # Get defaults from schema # ------------------------ layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'vertical_line.yaml')) + 'batch', 'emcpy', 'defaults', + 'vertical_line.yaml')) config = get_schema(layer_schema, config, logger) delvars = ['type', 'schema'] for d in delvars: diff --git a/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py b/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py index 30f5343c..2787f340 100644 --- a/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py +++ b/src/eva/plotting/batch/emcpy/plot_tools/emcpy_figure_handler.py @@ -2,6 +2,7 @@ from eva.eva_path import return_eva_path import os + class EmcpyFigureHandler(): def __init__(self): @@ -11,6 +12,6 @@ def __init__(self): def create_plot(self, layer_list, proj, domain): return CreatePlot(plot_layers=layer_list, projection=proj, domain=domain) - + def create_figure(self, nrows, ncols, figsize): return CreateFigure(nrows=nrows, ncols=ncols, figsize=figsize) diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py index b9391b71..71abc662 100644 --- a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py @@ -2,7 +2,6 @@ from eva.utilities.config import get from eva.utilities.utils import get_schema, update_object import os -#import hvplot import pandas as pd import hvplot.pandas import holoviews as hv @@ -24,20 +23,21 @@ def configure_plot(self): size = self.config['markersize'] label = self.config['label'] - #add line statistics to legend label + # add line statistics to legend label try: plot_for_slope = df.hvplot.scatter('xdata', 'ydata') slope = hv.Slope.from_scatter(plot_for_slope) slope_attrs = linregress(self.xdata, self.ydata) slope_expression = 'y='+f'{slope.slope:.3f}'+"x+"+f'{slope.y_intercept:.3f}' r_sq = ', r^2: ' + f'{slope_attrs.rvalue**2:.3f}' - slope_label = "y="+f'{slope.slope:.3f}'+"x+"+f'{slope.y_intercept:.3f}'+r_sq - plot = df.hvplot.scatter('xdata', 'ydata', s=size, c=color, label=label+", "+slope_label) + slope_label = "y="+f'{slope.slope:.3f}'+"x+"+f'{slope.y_intercept:.3f}'+r_sq + plot = df.hvplot.scatter('xdata', 'ydata', + s=size, c=color, label=label+", "+slope_label) new_plot = hv.Overlay([plot, plot]) plotobj = new_plot * slope plotobj.opts(show_legend=False) - except: - plot = df.hvplot.scatter('xdata', 'ydata', s=size, c=color, label=label) + except Exception: + plot = df.hvplot.scatter('xdata', 'ydata', s=size, c=color, label=label) plotobj = hv.Overlay([plot, plot]) plotobj.opts(show_legend=False) diff --git a/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py b/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py index 5016e3a2..0c9971e7 100644 --- a/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py +++ b/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py @@ -2,6 +2,7 @@ import hvplot as hv import os + class CreatePlot: def __init__(self, plot_layers=[], projection=None, domain=None): @@ -52,7 +53,7 @@ def __init__(self, nrows=1, ncols=1, figsize=(8, 6)): def create_figure(self): # Needs work, need to combine all the layers # and figure out how subplots will work - self.fig = self.plot_list[0].plot_layers[0] + self.fig = self.plot_list[0].plot_layers[0] plot_obj = self.plot_list[0] # Add all features to the figure @@ -67,12 +68,11 @@ def save_figure(self, pathfile, **kwargs): if not os.path.exists(pathfile_dir): os.makedirs(pathfile_dir) bokeh_fig = hv.render(self.fig, backend='bokeh') - #save(bokeh_fig, pathfile) output_file(pathfile) save(bokeh_fig) def close_figure(self): - pass + pass def _plot_features(self, plot_obj, feature): diff --git a/src/eva/plotting/batch/hvplot/plot_tools/hvplot_figure_handler.py b/src/eva/plotting/batch/hvplot/plot_tools/hvplot_figure_handler.py index e3b5ab33..b9990662 100644 --- a/src/eva/plotting/batch/hvplot/plot_tools/hvplot_figure_handler.py +++ b/src/eva/plotting/batch/hvplot/plot_tools/hvplot_figure_handler.py @@ -2,6 +2,7 @@ from eva.plotting.batch.hvplot.plot_tools.create_plots import CreatePlot, CreateFigure import os + class HvplotFigureHandler(): def __init__(self): From 7d891ae5b192eab7652c318afb83dc075db31611 Mon Sep 17 00:00:00 2001 From: asewnath Date: Mon, 2 Oct 2023 12:56:31 -0400 Subject: [PATCH 10/17] enforcing plotting backend in yamls --- src/eva/plotting/batch/base/plot_tools/figure_driver.py | 6 +----- src/eva/tests/config/testCubedSphereRestart.yaml | 2 ++ src/eva/tests/config/testGsiObsSpaceAmsuaMetop-A.yaml | 2 ++ src/eva/tests/config/testGsiObsSpaceConvT.yaml | 2 ++ src/eva/tests/config/testIodaObsSpaceAircraft.yaml | 2 ++ src/eva/tests/config/testIodaObsSpaceAmsuaN19.yaml | 2 ++ src/eva/tests/config/testIodaObsSpaceIASI_Metop-A.yaml | 2 ++ src/eva/tests/config/testJediLog.yaml | 2 ++ src/eva/tests/config/testLatLon.yaml | 2 ++ src/eva/tests/config/testMonDataSpaceHirs4Metop-A.yaml | 2 ++ src/eva/tests/config/testMonSummary.yaml | 2 ++ src/eva/tests/config/testSocaRestart.yaml | 2 ++ src/eva/tests/config/testTwoDatasetsOnePlot.yaml | 2 ++ 13 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index 9ed55675..fdc5cc1e 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -44,11 +44,7 @@ def figure_driver(config, data_collections, timing, logger): # Get plotting backend # -------------------- - if 'plotting_backend' in graphics_section: - backend = graphics_section.get('plotting_backend') - else: - # use emcpy as default - backend = 'Emcpy' + backend = graphics_section.get('plotting_backend') # Create handler # -------------- diff --git a/src/eva/tests/config/testCubedSphereRestart.yaml b/src/eva/tests/config/testCubedSphereRestart.yaml index 7f039961..660bd7b1 100644 --- a/src/eva/tests/config/testCubedSphereRestart.yaml +++ b/src/eva/tests/config/testCubedSphereRestart.yaml @@ -19,6 +19,8 @@ datasets: orography variables: [geolon, geolat] graphics: + + plotting_backend: Emcpy figure_list: # Map plots # --------- diff --git a/src/eva/tests/config/testGsiObsSpaceAmsuaMetop-A.yaml b/src/eva/tests/config/testGsiObsSpaceAmsuaMetop-A.yaml index 42c1d28b..9f37ddd7 100644 --- a/src/eva/tests/config/testGsiObsSpaceAmsuaMetop-A.yaml +++ b/src/eva/tests/config/testGsiObsSpaceAmsuaMetop-A.yaml @@ -23,6 +23,8 @@ transforms: variable: *variables graphics: + + plotting_backend: Emcpy figure_list: # Histogram plots diff --git a/src/eva/tests/config/testGsiObsSpaceConvT.yaml b/src/eva/tests/config/testGsiObsSpaceConvT.yaml index ec317fe9..fa05a98f 100644 --- a/src/eva/tests/config/testGsiObsSpaceConvT.yaml +++ b/src/eva/tests/config/testGsiObsSpaceConvT.yaml @@ -12,6 +12,8 @@ datasets: Longitude] graphics: + + plotting_backend: Emcpy figure_list: # Map plots diff --git a/src/eva/tests/config/testIodaObsSpaceAircraft.yaml b/src/eva/tests/config/testIodaObsSpaceAircraft.yaml index 9be67a61..ec1ba627 100644 --- a/src/eva/tests/config/testIodaObsSpaceAircraft.yaml +++ b/src/eva/tests/config/testIodaObsSpaceAircraft.yaml @@ -72,6 +72,8 @@ transforms: variable: *variables graphics: + + plotting_backend: Emcpy figure_list: # Observation correlation scatter plots diff --git a/src/eva/tests/config/testIodaObsSpaceAmsuaN19.yaml b/src/eva/tests/config/testIodaObsSpaceAmsuaN19.yaml index 47798830..110f65f1 100644 --- a/src/eva/tests/config/testIodaObsSpaceAmsuaN19.yaml +++ b/src/eva/tests/config/testIodaObsSpaceAmsuaN19.yaml @@ -75,6 +75,8 @@ transforms: variable: *variables graphics: + + plotting_backend: Emcpy figure_list: # Correlation scatter plots diff --git a/src/eva/tests/config/testIodaObsSpaceIASI_Metop-A.yaml b/src/eva/tests/config/testIodaObsSpaceIASI_Metop-A.yaml index a723e1e0..b6ebd7cd 100644 --- a/src/eva/tests/config/testIodaObsSpaceIASI_Metop-A.yaml +++ b/src/eva/tests/config/testIodaObsSpaceIASI_Metop-A.yaml @@ -51,6 +51,8 @@ transforms: variable: *variables graphics: + + plotting_backend: Emcpy figure_list: # ---------- Statistical Plot ---------- diff --git a/src/eva/tests/config/testJediLog.yaml b/src/eva/tests/config/testJediLog.yaml index 5d4e5651..5121397d 100644 --- a/src/eva/tests/config/testJediLog.yaml +++ b/src/eva/tests/config/testJediLog.yaml @@ -13,6 +13,8 @@ datasets: # Make plots graphics: + + plotting_backend: Emcpy figure_list: - figure: diff --git a/src/eva/tests/config/testLatLon.yaml b/src/eva/tests/config/testLatLon.yaml index 3bc6de48..dd85923d 100644 --- a/src/eva/tests/config/testLatLon.yaml +++ b/src/eva/tests/config/testLatLon.yaml @@ -6,6 +6,8 @@ datasets: variables: [T_inc, lat, lon] graphics: + + plotting_backend: Emcpy figure_list: # Map plots diff --git a/src/eva/tests/config/testMonDataSpaceHirs4Metop-A.yaml b/src/eva/tests/config/testMonDataSpaceHirs4Metop-A.yaml index 97437851..692e0c2b 100644 --- a/src/eva/tests/config/testMonDataSpaceHirs4Metop-A.yaml +++ b/src/eva/tests/config/testMonDataSpaceHirs4Metop-A.yaml @@ -19,6 +19,8 @@ datasets: variables: &variables [count, cycle] graphics: + + plotting_backend: Emcpy figure_list: # Time series plots diff --git a/src/eva/tests/config/testMonSummary.yaml b/src/eva/tests/config/testMonSummary.yaml index 56d9b4ee..87d1de5c 100644 --- a/src/eva/tests/config/testMonSummary.yaml +++ b/src/eva/tests/config/testMonSummary.yaml @@ -81,6 +81,8 @@ transforms: graphics: + + plotting_backend: Emcpy figure_list: # Summary plots diff --git a/src/eva/tests/config/testSocaRestart.yaml b/src/eva/tests/config/testSocaRestart.yaml index e551c62b..2bb9d57d 100644 --- a/src/eva/tests/config/testSocaRestart.yaml +++ b/src/eva/tests/config/testSocaRestart.yaml @@ -10,6 +10,8 @@ datasets: coordinate variables: [lon, lat] graphics: + + plotting_backend: Emcpy figure_list: # Map plots diff --git a/src/eva/tests/config/testTwoDatasetsOnePlot.yaml b/src/eva/tests/config/testTwoDatasetsOnePlot.yaml index d25e74c7..6f04acd6 100644 --- a/src/eva/tests/config/testTwoDatasetsOnePlot.yaml +++ b/src/eva/tests/config/testTwoDatasetsOnePlot.yaml @@ -27,6 +27,8 @@ datasets: orography variables: [geolon, geolat] graphics: + + plotting_backend: Emcpy figure_list: # Map plots From 05bbe217b476702b3b75ac5797021a6696aaea7a Mon Sep 17 00:00:00 2001 From: asewnath Date: Tue, 3 Oct 2023 14:04:51 -0400 Subject: [PATCH 11/17] requirements --- requirements.txt | 16 +++++++++++----- requirements_emc.txt | 7 +++++++ .../batch/base/plot_tools/figure_driver.py | 7 +++++++ 3 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 requirements_emc.txt diff --git a/requirements.txt b/requirements.txt index 9813251c..3760be65 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,13 @@ pyyaml>=6.0 -pycodestyle>=2.8.0 -netCDF4>=1.5.3 +pycodestyle>=2.9.1 +netCDF4>=1.6.1 matplotlib>=3.5.2 -cartopy>=0.20.2 -scikit-learn>=1.0.2 -xarray>=0.11.3 +cartopy>=0.21.1 +scikit-learn>=1.1.2 +xarray>=2022.6.0 +seaborn>=0.12.2 +hvplot>=0.8.2 +nbconvert>=6.5.4 +bokeh>=3.1.1 +geopandas>=0.13.2 +geoviews>=1.10.0 diff --git a/requirements_emc.txt b/requirements_emc.txt new file mode 100644 index 00000000..9813251c --- /dev/null +++ b/requirements_emc.txt @@ -0,0 +1,7 @@ +pyyaml>=6.0 +pycodestyle>=2.8.0 +netCDF4>=1.5.3 +matplotlib>=3.5.2 +cartopy>=0.20.2 +scikit-learn>=1.0.2 +xarray>=0.11.3 diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index fdc5cc1e..771233ca 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -46,6 +46,13 @@ def figure_driver(config, data_collections, timing, logger): # -------------------- backend = graphics_section.get('plotting_backend') + if backend == 'Hvplot': + try: + import hvplot + except ImportError: + logger.abort("The hvplot backend is not available since hvplot is not in the environment.") + backend = 'Emcpy' + # Create handler # -------------- handler_class_name = backend + 'FigureHandler' From 4c8a783e4263d6ec06a0d206333ef5b4cd972906 Mon Sep 17 00:00:00 2001 From: asewnath Date: Tue, 3 Oct 2023 17:03:40 -0400 Subject: [PATCH 12/17] coding norms --- src/eva/plotting/batch/base/plot_tools/figure_driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index 771233ca..6fe991b7 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -50,7 +50,8 @@ def figure_driver(config, data_collections, timing, logger): try: import hvplot except ImportError: - logger.abort("The hvplot backend is not available since hvplot is not in the environment.") + logger.abort("The hvplot backend is not available since \ + hvplot is not in the environment.") backend = 'Emcpy' # Create handler From 971fd1dff4f0ecb8b5e3467f86ca2e33c219bfec Mon Sep 17 00:00:00 2001 From: danholdaway Date: Thu, 5 Oct 2023 11:24:19 -0400 Subject: [PATCH 13/17] Sort out requirements files a bit --- requirements-spackstack1.4.txt | 25 +++++++++++++++++++++++++ requirements.txt | 28 +++++++++++++++++----------- requirements_emc.txt | 14 ++++++++++---- setup.py | 15 +-------------- 4 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 requirements-spackstack1.4.txt diff --git a/requirements-spackstack1.4.txt b/requirements-spackstack1.4.txt new file mode 100644 index 00000000..3cb461b3 --- /dev/null +++ b/requirements-spackstack1.4.txt @@ -0,0 +1,25 @@ +# Packages in spack stack +setuptools==59.4.0 +pyyaml==6.0 +pycodestyle==2.8.0 +netCDF4==1.5.3 +matplotlib==3.7.1 +cartopy==0.21.1 +scipy==1.9.3 +xarray==2022.3.0 +pandas==1.4.0 +numpy==1.22.3 + +# Not explicitly part of eva but dependcies of eva dependencies already in spack-stack +# versions need to be set to avoid other versions being picked +pyproj==3.1.0 +importlib-metadata==4.8.2 + +# Additional packages +git+https://github.com/NOAA-EMC/emcpy.git@9b6756341e9ae963baa7d3a256b8ada3da688d04#egg=emcpy +scikit-learn +seaborn +hvplot +nbconvert +bokeh +geopandas diff --git a/requirements.txt b/requirements.txt index 3760be65..e46c37ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,19 @@ +setuptools>=59.4.0 pyyaml>=6.0 -pycodestyle>=2.9.1 -netCDF4>=1.6.1 -matplotlib>=3.5.2 +pycodestyle>=2.8.0 +netCDF4>=1.5.3 +matplotlib>=3.7.1 cartopy>=0.21.1 -scikit-learn>=1.1.2 -xarray>=2022.6.0 -seaborn>=0.12.2 -hvplot>=0.8.2 -nbconvert>=6.5.4 -bokeh>=3.1.1 -geopandas>=0.13.2 -geoviews>=1.10.0 +scipy>=1.9.3 +xarray>=2022.3.0 +pandas>=1.4.0 +numpy>=1.22.3 + +# Additional packages +git+https://github.com/NOAA-EMC/emcpy.git@9b6756341e9ae963baa7d3a256b8ada3da688d04#egg=emcpy +scikit-learn +seaborn +hvplot +nbconvert +bokeh +geopandas diff --git a/requirements_emc.txt b/requirements_emc.txt index 9813251c..7b52934d 100644 --- a/requirements_emc.txt +++ b/requirements_emc.txt @@ -1,7 +1,13 @@ +setuptools>=59.4.0 pyyaml>=6.0 pycodestyle>=2.8.0 netCDF4>=1.5.3 -matplotlib>=3.5.2 -cartopy>=0.20.2 -scikit-learn>=1.0.2 -xarray>=0.11.3 +matplotlib>=3.7.1 +cartopy>=0.21.1 +scipy>=1.9.3 +xarray>=2022.3.0 +pandas>=1.4.0 +numpy>=1.22.3 + +# Additional packages +git+https://github.com/NOAA-EMC/emcpy.git@9b6756341e9ae963baa7d3a256b8ada3da688d04#egg=emcpy diff --git a/setup.py b/setup.py index 8f0f52da..a0fe66f8 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,7 @@ import setuptools + setuptools.setup( name='eva', version='1.5.0', @@ -31,20 +32,6 @@ 'Natural Language :: English', 'Operating System :: OS Independent'], python_requires='>=3.6', - install_requires=[ - 'pyyaml>=6.0', - 'pycodestyle>=2.8.0', - 'netCDF4>=1.5.3', - 'matplotlib>=3.5.2', - 'cartopy>=0.18.0', - 'scikit-learn>=1.0.2', - 'xarray>=0.11.3', - 'emcpy @ git+https://github.com/NOAA-EMC/' + - 'emcpy@9b6756341e9ae963baa7d3a256b8ada3da688d04#egg=emcpy', - - # Option dependency for making density plots - # 'seaborn>=0.12', - ], package_data={ '': [ 'tests/config/*', From b644d46b98b78551a8fcf6ee293b288e8144b4a0 Mon Sep 17 00:00:00 2001 From: danholdaway Date: Thu, 5 Oct 2023 12:25:07 -0400 Subject: [PATCH 14/17] emcpy in github requirements --- requirements-github.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-github.txt b/requirements-github.txt index 449c5ad0..881fcd31 100644 --- a/requirements-github.txt +++ b/requirements-github.txt @@ -12,3 +12,4 @@ bokeh>=3.1.1 geopandas>=0.13.2 geoviews>=1.10.0 nbsite +git+https://github.com/NOAA-EMC/emcpy.git@9b6756341e9ae963baa7d3a256b8ada3da688d04#egg=emcpy From 92142907b71e9c29072c9512d918b3dca27fb4ae Mon Sep 17 00:00:00 2001 From: asewnath Date: Thu, 5 Oct 2023 13:55:12 -0400 Subject: [PATCH 15/17] review changes --- .github/workflows/.eva-docs_dry_run.yml.swp | Bin 12288 -> 0 bytes .gitignore | 4 ++++ .../batch/base/plot_tools/.figure_driver.py.swo | Bin 24576 -> 0 bytes .../batch/base/plot_tools/figure_driver.py | 9 ++++++--- .../tests/config/testCubedSphereRestart.yaml | 1 + 5 files changed, 11 insertions(+), 3 deletions(-) delete mode 100644 .github/workflows/.eva-docs_dry_run.yml.swp delete mode 100644 src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo diff --git a/.github/workflows/.eva-docs_dry_run.yml.swp b/.github/workflows/.eva-docs_dry_run.yml.swp deleted file mode 100644 index 4224ce25e0b621452a84ab02e57376c92db88fb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2%WvaE9LHz(#H-8e#9^f$O{KJ2>}7zI^zj=LT##Pj5 zTs_#jNza&98PX$+h3r@N_vb#k_{0^)Y{`4VR_&zdtmJyq3A#L5UOp>|u~I8+%1+4@ zjg{3>)_AGqsdn5b@vdDm)8}3gz1H-Do=kELH*jR#jr#Q{7G}TWpWTTr)dsYIX#=rp z8c)yduUuOm9`(Zfll1sw`_pkcM;p)vv;l2E8_)){0c}7V&<6gu22^-~y@lCbOlSCf z`g!5hXZlrtXam}SHlPh?1KNN#pbcmP+JH8o4QK<}z<41HXW8!6)E7@Gdw8eef!{1m?gW zbBuilz6PIxPr>`(5Y)jYxC|Zze?7$5=ioLF;1JZo6>#@K#=Zi5@CtYdY=a*kVC+lq z4!8wYz@M1!58w{?04UG|iH8=@NgL1xv;l2E8_)){fq!R!#%ZwhEE|6rKMZE%5j7%P zIPIF<;TDBa;BYBTF1qIC&PMI}je2e4W_3nAPlU0O7M;u}i+IzbqRYG755nS8Ga|C9 zmvzszDEqP(>_uLy#Us;mXH|-L7@*zkvgXW&gP!0Kg0`>kRyGVzC>{yhp9r;kwx?)x z2h_A32XvcVe-apO}M?z~`nPv=*mEyOf(>5~!vpMCEawG_wYz+4O`v zZjZ`q;~-ODHK<%J!(#4QY7wiN>+JL*=SNvI3?m7rT*{XpW>ckIg^63VaFUJ7lu6Fu z@LaXGDic*b_nh@c+b!2=GUHQqvjI&g3!d^(GzlQW#8MX zk_K4QnLEk%q}stw%>>KbPLfXKh03Bg(^2$rDcMS&W65!x(*2lTS2ji!*1N02AcGQ1 z7Ny68vM#duOE*Fl+W!4ap2wZO9vbDtNz;Rk$ zE&k(>C0B81Bo)`Y*Cx8!<%;5vYA7;~#+?jD%bj*W^SM_|d0}yww{Y^}*4~Z%&H7ID zg#unEvef3bD@%i=d0Jbeg|xI}-h*igf-svly9q9lxczaH_|V~DdDT;ezKa9>sr PHmH&}MTyLvSg^kV(^y6i diff --git a/.gitignore b/.gitignore index b6e47617..4830b678 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,7 @@ dmypy.json # Pyre type checker .pyre/ + +# Swap files +*.swp +*.swo diff --git a/src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo b/src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo deleted file mode 100644 index 626a2e3175d8cb20d0e69d1719102c0c28406431..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 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 diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index 6fe991b7..900091a3 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -46,13 +46,16 @@ def figure_driver(config, data_collections, timing, logger): # -------------------- backend = graphics_section.get('plotting_backend') + if backend not in ['Emcpy', 'Hvplot']: + logger.abort('Backend not found. \ + Available backends: Emcpy, Hvplot') + if backend == 'Hvplot': try: import hvplot except ImportError: - logger.abort("The hvplot backend is not available since \ - hvplot is not in the environment.") - backend = 'Emcpy' + logger.abort('The hvplot backend is not available since \ + hvplot is not in the environment.') # Create handler # -------------- diff --git a/src/eva/tests/config/testCubedSphereRestart.yaml b/src/eva/tests/config/testCubedSphereRestart.yaml index 660bd7b1..2f00b47c 100644 --- a/src/eva/tests/config/testCubedSphereRestart.yaml +++ b/src/eva/tests/config/testCubedSphereRestart.yaml @@ -22,6 +22,7 @@ graphics: plotting_backend: Emcpy figure_list: + # Map plots # --------- From 9d20f0883be6c40497e2312d4a9b97793fa13868 Mon Sep 17 00:00:00 2001 From: asewnath Date: Thu, 5 Oct 2023 14:10:25 -0400 Subject: [PATCH 16/17] Revert "review changes" This reverts commit 92142907b71e9c29072c9512d918b3dca27fb4ae. --- .github/workflows/.eva-docs_dry_run.yml.swp | Bin 0 -> 12288 bytes .gitignore | 4 ---- .../batch/base/plot_tools/.figure_driver.py.swo | Bin 0 -> 24576 bytes .../batch/base/plot_tools/figure_driver.py | 9 +++------ .../tests/config/testCubedSphereRestart.yaml | 1 - 5 files changed, 3 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/.eva-docs_dry_run.yml.swp create mode 100644 src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo diff --git a/.github/workflows/.eva-docs_dry_run.yml.swp b/.github/workflows/.eva-docs_dry_run.yml.swp new file mode 100644 index 0000000000000000000000000000000000000000..4224ce25e0b621452a84ab02e57376c92db88fb0 GIT binary patch literal 12288 zcmeI2%WvaE9LHz(#H-8e#9^f$O{KJ2>}7zI^zj=LT##Pj5 zTs_#jNza&98PX$+h3r@N_vb#k_{0^)Y{`4VR_&zdtmJyq3A#L5UOp>|u~I8+%1+4@ zjg{3>)_AGqsdn5b@vdDm)8}3gz1H-Do=kELH*jR#jr#Q{7G}TWpWTTr)dsYIX#=rp z8c)yduUuOm9`(Zfll1sw`_pkcM;p)vv;l2E8_)){0c}7V&<6gu22^-~y@lCbOlSCf z`g!5hXZlrtXam}SHlPh?1KNN#pbcmP+JH8o4QK<}z<41HXW8!6)E7@Gdw8eef!{1m?gW zbBuilz6PIxPr>`(5Y)jYxC|Zze?7$5=ioLF;1JZo6>#@K#=Zi5@CtYdY=a*kVC+lq z4!8wYz@M1!58w{?04UG|iH8=@NgL1xv;l2E8_)){fq!R!#%ZwhEE|6rKMZE%5j7%P zIPIF<;TDBa;BYBTF1qIC&PMI}je2e4W_3nAPlU0O7M;u}i+IzbqRYG755nS8Ga|C9 zmvzszDEqP(>_uLy#Us;mXH|-L7@*zkvgXW&gP!0Kg0`>kRyGVzC>{yhp9r;kwx?)x z2h_A32XvcVe-apO}M?z~`nPv=*mEyOf(>5~!vpMCEawG_wYz+4O`v zZjZ`q;~-ODHK<%J!(#4QY7wiN>+JL*=SNvI3?m7rT*{XpW>ckIg^63VaFUJ7lu6Fu z@LaXGDic*b_nh@c+b!2=GUHQqvjI&g3!d^(GzlQW#8MX zk_K4QnLEk%q}stw%>>KbPLfXKh03Bg(^2$rDcMS&W65!x(*2lTS2ji!*1N02AcGQ1 z7Ny68vM#duOE*Fl+W!4ap2wZO9vbDtNz;Rk$ zE&k(>C0B81Bo)`Y*Cx8!<%;5vYA7;~#+?jD%bj*W^SM_|d0}yww{Y^}*4~Z%&H7ID zg#unEvef3bD@%i=d0Jbeg|xI}-h*igf-svly9q9lxczaH_|V~DdDT;ezKa9>sr PHmH&}MTyLvSg^kV(^y6i literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore index 4830b678..b6e47617 100644 --- a/.gitignore +++ b/.gitignore @@ -127,7 +127,3 @@ dmypy.json # Pyre type checker .pyre/ - -# Swap files -*.swp -*.swo diff --git a/src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo b/src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo new file mode 100644 index 0000000000000000000000000000000000000000..626a2e3175d8cb20d0e69d1719102c0c28406431 GIT binary patch literal 24576 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 literal 0 HcmV?d00001 diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index 900091a3..6fe991b7 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -46,16 +46,13 @@ def figure_driver(config, data_collections, timing, logger): # -------------------- backend = graphics_section.get('plotting_backend') - if backend not in ['Emcpy', 'Hvplot']: - logger.abort('Backend not found. \ - Available backends: Emcpy, Hvplot') - if backend == 'Hvplot': try: import hvplot except ImportError: - logger.abort('The hvplot backend is not available since \ - hvplot is not in the environment.') + logger.abort("The hvplot backend is not available since \ + hvplot is not in the environment.") + backend = 'Emcpy' # Create handler # -------------- diff --git a/src/eva/tests/config/testCubedSphereRestart.yaml b/src/eva/tests/config/testCubedSphereRestart.yaml index 2f00b47c..660bd7b1 100644 --- a/src/eva/tests/config/testCubedSphereRestart.yaml +++ b/src/eva/tests/config/testCubedSphereRestart.yaml @@ -22,7 +22,6 @@ graphics: plotting_backend: Emcpy figure_list: - # Map plots # --------- From 797a005133efaf48a55683de880c1ad7f6559c11 Mon Sep 17 00:00:00 2001 From: asewnath Date: Thu, 5 Oct 2023 16:19:17 -0400 Subject: [PATCH 17/17] review changes again --- .github/workflows/.eva-docs_dry_run.yml.swp | Bin 12288 -> 0 bytes .gitignore | 4 ++++ .../batch/base/plot_tools/.figure_driver.py.swo | Bin 24576 -> 0 bytes .../batch/base/plot_tools/figure_driver.py | 5 ++++- .../tests/config/testCubedSphereRestart.yaml | 1 + 5 files changed, 9 insertions(+), 1 deletion(-) delete mode 100644 .github/workflows/.eva-docs_dry_run.yml.swp delete mode 100644 src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo diff --git a/.github/workflows/.eva-docs_dry_run.yml.swp b/.github/workflows/.eva-docs_dry_run.yml.swp deleted file mode 100644 index 4224ce25e0b621452a84ab02e57376c92db88fb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2%WvaE9LHz(#H-8e#9^f$O{KJ2>}7zI^zj=LT##Pj5 zTs_#jNza&98PX$+h3r@N_vb#k_{0^)Y{`4VR_&zdtmJyq3A#L5UOp>|u~I8+%1+4@ zjg{3>)_AGqsdn5b@vdDm)8}3gz1H-Do=kELH*jR#jr#Q{7G}TWpWTTr)dsYIX#=rp z8c)yduUuOm9`(Zfll1sw`_pkcM;p)vv;l2E8_)){0c}7V&<6gu22^-~y@lCbOlSCf z`g!5hXZlrtXam}SHlPh?1KNN#pbcmP+JH8o4QK<}z<41HXW8!6)E7@Gdw8eef!{1m?gW zbBuilz6PIxPr>`(5Y)jYxC|Zze?7$5=ioLF;1JZo6>#@K#=Zi5@CtYdY=a*kVC+lq z4!8wYz@M1!58w{?04UG|iH8=@NgL1xv;l2E8_)){fq!R!#%ZwhEE|6rKMZE%5j7%P zIPIF<;TDBa;BYBTF1qIC&PMI}je2e4W_3nAPlU0O7M;u}i+IzbqRYG755nS8Ga|C9 zmvzszDEqP(>_uLy#Us;mXH|-L7@*zkvgXW&gP!0Kg0`>kRyGVzC>{yhp9r;kwx?)x z2h_A32XvcVe-apO}M?z~`nPv=*mEyOf(>5~!vpMCEawG_wYz+4O`v zZjZ`q;~-ODHK<%J!(#4QY7wiN>+JL*=SNvI3?m7rT*{XpW>ckIg^63VaFUJ7lu6Fu z@LaXGDic*b_nh@c+b!2=GUHQqvjI&g3!d^(GzlQW#8MX zk_K4QnLEk%q}stw%>>KbPLfXKh03Bg(^2$rDcMS&W65!x(*2lTS2ji!*1N02AcGQ1 z7Ny68vM#duOE*Fl+W!4ap2wZO9vbDtNz;Rk$ zE&k(>C0B81Bo)`Y*Cx8!<%;5vYA7;~#+?jD%bj*W^SM_|d0}yww{Y^}*4~Z%&H7ID zg#unEvef3bD@%i=d0Jbeg|xI}-h*igf-svly9q9lxczaH_|V~DdDT;ezKa9>sr PHmH&}MTyLvSg^kV(^y6i diff --git a/.gitignore b/.gitignore index b6e47617..4830b678 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,7 @@ dmypy.json # Pyre type checker .pyre/ + +# Swap files +*.swp +*.swo diff --git a/src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo b/src/eva/plotting/batch/base/plot_tools/.figure_driver.py.swo deleted file mode 100644 index 626a2e3175d8cb20d0e69d1719102c0c28406431..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 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 diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index 6fe991b7..28464d38 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -46,13 +46,16 @@ def figure_driver(config, data_collections, timing, logger): # -------------------- backend = graphics_section.get('plotting_backend') + if backend not in ['Emcpy', 'Hvplot']: + logger.abort('Backend not found. \ + Available backends: Emcpy, Hvplot') + if backend == 'Hvplot': try: import hvplot except ImportError: logger.abort("The hvplot backend is not available since \ hvplot is not in the environment.") - backend = 'Emcpy' # Create handler # -------------- diff --git a/src/eva/tests/config/testCubedSphereRestart.yaml b/src/eva/tests/config/testCubedSphereRestart.yaml index 660bd7b1..2f00b47c 100644 --- a/src/eva/tests/config/testCubedSphereRestart.yaml +++ b/src/eva/tests/config/testCubedSphereRestart.yaml @@ -22,6 +22,7 @@ graphics: plotting_backend: Emcpy figure_list: + # Map plots # ---------