diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 81629b2..ac291c6 100755 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.11.9 +current_version = 0.11.10 commit = True tag = True diff --git a/.zenodo.json b/.zenodo.json index d651833..456607c 100755 --- a/.zenodo.json +++ b/.zenodo.json @@ -2,7 +2,7 @@ "license": "other-open", "title": "Snow and Water Model Analysis and Visualization (SNOWAV)", - "version": "v0.11.9", + "version": "v0.11.10", "upload_type": "software", "keywords": [ "snow modeling", diff --git a/setup.py b/setup.py index 45b6e05..4a36ed4 100755 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ setup( name='snowav', - version='0.11.9', + version='0.11.10', description="Snow and Water Model Analysis and Visualization ", author="Mark Robertson", author_email='mark.robertson@usda.gov', diff --git a/snowav/__init__.py b/snowav/__init__.py index 736e1ae..fac17e1 100755 --- a/snowav/__init__.py +++ b/snowav/__init__.py @@ -1,5 +1,5 @@ import os -__version__ = '0.11.9' +__version__ = '0.11.10' __core_config__ = os.path.abspath(os.path.dirname(__file__) + '/config/CoreConfig.ini') __recipes__ = os.path.abspath(os.path.dirname(__file__) + '/config/recipes.ini') __config_titles__ = {'snowav':'Overview', diff --git a/snowav/config/CoreConfig.ini b/snowav/config/CoreConfig.ini index 5af6104..ac5f6a1 100755 --- a/snowav/config/CoreConfig.ini +++ b/snowav/config/CoreConfig.ini @@ -231,15 +231,14 @@ point_values: default = False, supply point_values_csv and point_values_heading fields. point_values_csv: default = None, - type = Filename list, + type = CriticalFilename, description = Path to csv file of snow course locations. Column headings should include name latitude longitude. point_values_heading: default = None, - type = password list, + type = password, description = Column heading in the point_values_csv file to - use for comparison. If left as None the model values output csv - will be created but no figures will be made. + use for comparison. point_values_properties: default = [swe_z], options = [swe_z depth density], diff --git a/snowav/config/config.py b/snowav/config/config.py index 622d78d..75b5393 100755 --- a/snowav/config/config.py +++ b/snowav/config/config.py @@ -1,4 +1,3 @@ - import calendar import coloredlogs from datetime import datetime @@ -35,13 +34,13 @@ class UserConfig(object): end_date: string """ - def __init__(self, config_file, external_logger = None, awsm = None, - end_date = None): + def __init__(self, config_file, external_logger=None, awsm=None, + end_date=None): print('Reading {} and loading files...'.format(config_file)) self.config_file = config_file - snowav_mcfg = MasterConfig(modules = 'snowav') + snowav_mcfg = MasterConfig(modules='snowav') ucfg = get_user_config(self.config_file, mcfg=snowav_mcfg) ucfg.apply_recipes() ucfg = cast_all_variables(ucfg, ucfg.mcfg) @@ -84,7 +83,7 @@ def __init__(self, config_file, external_logger = None, awsm = None, if self.end_date <= self.start_date: raise Exception('end_date {} earlier than start_date {}'.format( - self.end_date, self.start_date)) + self.end_date, self.start_date)) if self.start_date is not None and self.end_date is not None: self.start_date = self.start_date @@ -104,8 +103,8 @@ def __init__(self, config_file, external_logger = None, awsm = None, if (ucfg.cfg['run']['directory'] is None) and (awsm is not None): if self.all_subdirs is True: self.run_dirs = ([awsm.pathr + s for s in - os.listdir(awsm.pathr) - if (os.path.isdir(awsm.pathr + s)) ]) + os.listdir(awsm.pathr) + if (os.path.isdir(awsm.pathr + s))]) else: self.run_dirs = awsm.pathr if type(self.run_dirs) != list: @@ -119,7 +118,7 @@ def __init__(self, config_file, external_logger = None, awsm = None, if self.all_subdirs is True: self.run_dirs = ([directory + s for s in os.listdir(directory) - if (os.path.isdir(directory + s))]) + if (os.path.isdir(directory + s))]) else: self.run_dirs = ucfg.cfg['run']['directory'] if type(self.run_dirs) != list: @@ -141,26 +140,26 @@ def __init__(self, config_file, external_logger = None, awsm = None, self.properties = ucfg.cfg['database']['properties'] self.sqlite = ucfg.cfg['database']['sqlite'] - base_bands = ['swi_z','evap_z','swe_z','depth','density', - 'coldcont', 'precip_z'] + base_bands = ['swi_z', 'evap_z', 'swe_z', 'depth', 'density', + 'coldcont', 'precip_z'] f = False for band in base_bands: if band not in self.properties: self.tmp_log.append(' WARNING! Config option [database] ' - 'properties does not contain {}'.format(band)) + 'properties does not contain {}'.format(band)) f = True if f: self.tmp_log.append(' WARNING! Suggest config option [database] ' - 'properties contain at least {} for most functionality to ' - 'run'.format(base_bands)) + 'properties contain at least {} for most functionality to ' + 'run'.format(base_bands)) if ((self.mysql is not None) and - ((self.db_user is None) or - (self.db_password is None) or - (self.db_host is None) or - (self.db_port is None)) ): + ((self.db_user is None) or + (self.db_password is None) or + (self.db_host is None) or + (self.db_port is None))): raise Exception('If using config option [database] mysql, must ' 'also supply user, password, host, and port') @@ -172,7 +171,7 @@ def __init__(self, config_file, external_logger = None, awsm = None, if self.mysql is not None: raise Exception('Config option [database] section contains ' - 'both "mysql" and "sqlite" entries, pick one.') + 'both "mysql" and "sqlite" entries, pick one.') #################################################### # validate # @@ -193,23 +192,9 @@ def __init__(self, config_file, external_logger = None, awsm = None, self.point_values_heading = ucfg.cfg['validate']['point_values_heading'] self.point_values_settings = ucfg.cfg['validate']['point_values_settings'] - if len(self.point_values_settings) != 14: - self.tmp_log.append(' Expected [validate] point_values_settings to ' - 'have 14 values, point_values being set to False') - self.point_values = False - - for n in range(0,10): + for n in range(0, 10): self.point_values_settings[n] = int(self.point_values_settings[n]) - if type(self.point_values_properties) != list: - self.point_values_properties = [self.point_values_properties] - - if type(self.point_values_heading) != list: - self.point_values_heading = [self.point_values_heading] - - if type(self.point_values_csv) != list: - self.point_values_csv = [self.point_values_csv] - if self.point_values and self.point_values_csv is None: self.point_values = False self.tmp_log.append(' Config option [validate] point_values_csv ' @@ -222,13 +207,6 @@ def __init__(self, config_file, external_logger = None, awsm = None, 'was not supplied, point_values being set ' 'to False') - if not (len(self.point_values_csv) == len(self.point_values_properties) == - len(self.point_values_heading)): - self.tmp_log.append(' Must supply the same number of [validate] ' - 'point_values_csv, point_values_properties, ' - 'and point_values_heading values, point_values ' - 'being set to False') - #################################################### # diagnostics # #################################################### @@ -241,9 +219,9 @@ def __init__(self, config_file, external_logger = None, awsm = None, for basin in self.diag_basins: if basin not in self.plotorder: self.tmp_log.append(' Config option [diagnostics] basin: ' - '{} does not match what was supplied in ' - '[snowav] masks: {}, diagnostics set ' - 'to False'.format(basin, self.plotorder)) + '{} does not match what was supplied in ' + '[snowav] masks: {}, diagnostics set ' + 'to False'.format(basin, self.plotorder)) self.diagnostics_flag = False self.inputs_flag = ucfg.cfg['diagnostics']['inputs_table'] @@ -307,7 +285,7 @@ def __init__(self, config_file, external_logger = None, awsm = None, self.point_values_flag = ucfg.cfg['plots']['point_values'] if (self.write_properties is not None and - type(self.write_properties) != list): + type(self.write_properties) != list): self.write_properties = [self.write_properties] numbers = ucfg.cfg['plots']['update_numbers'] @@ -328,7 +306,7 @@ def __init__(self, config_file, external_logger = None, awsm = None, self.compare_runs_flag = False if (self.compare_runs_flag and - (len(self.compare_run_names) != len(self.compare_run_labels))): + (len(self.compare_run_names) != len(self.compare_run_labels))): self.tmp_log.append(' Config option [plots] compare_runs set to True, ' 'must supply equal length compare_run_names and ' 'compare_run_labels, resetting compare_runs to False') @@ -340,20 +318,25 @@ def __init__(self, config_file, external_logger = None, awsm = None, self.flt_flag = False if (self.precip_validate_flag and ((self.val_client is None) or - (self.pre_val_stns is None) or (self.pre_val_lbls is None))): + (self.pre_val_stns is None) or (self.pre_val_lbls is None))): self.tmp_log.append(' Config option [plots] precip_validate is being ' 'set to False') self.precip_validate_flag = False if (self.stn_validate_flag and (self.val_client is None) or - (self.val_stns is None) or (self.val_lbls is None) or - (self.wxdb_user is None) or (self.wxdb_password is None) ): + (self.val_stns is None) or (self.val_lbls is None) or + (self.wxdb_user is None) or (self.wxdb_password is None)): self.tmp_log.append(' Config option [plots] stn_validate is being ' 'set to False') self.stn_validate_flag = False + if len(self.point_values_settings) != 14: + self.tmp_log.append(' Expected [validate] point_values_settings ' + 'to have 14 values, point_values set to False') + self.point_values_flag = False + for var in self.plots_inputs_variables: if var not in self.inputs_variables: self.plots_inputs_variables.remove(var) @@ -382,8 +365,8 @@ def __init__(self, config_file, external_logger = None, awsm = None, if self.report_diagnostics and (not self.inputs_fig_flag or not self.diagnostics_flag): self.tmp_log.append(" [report] diagnostics: True, but must also have " - "[plots] inputs: True and [diagnostics] diagnostics: True, " - "setting to False") + "[plots] inputs: True and [diagnostics] diagnostics: True, " + "setting to False") self.report_diagnostics = False if self.report_diagnostics and self.report_diagnostics_day[0] != 'any': @@ -391,7 +374,7 @@ def __init__(self, config_file, external_logger = None, awsm = None, if calendar.day_name[datetime.now().weekday()] not in self.report_diagnostics_day: self.report_diagnostics = False self.tmp_log.append(" Per [report] diagnostics_day: {}, " - "setting diagnostics: False".format(self.report_diagnostics_day)) + "setting diagnostics: False".format(self.report_diagnostics_day)) self.rep_swi_flag = ucfg.cfg['report']['swi'] if not self.swi_flag: @@ -454,8 +437,8 @@ def __init__(self, config_file, external_logger = None, awsm = None, 'than end_date') self.for_run_dir = ([ucfg.cfg['forecast']['run_dir'] + s for s in - os.listdir(ucfg.cfg['forecast']['run_dir']) - if (os.path.isdir(ucfg.cfg['forecast']['run_dir'] + s)) ]) + os.listdir(ucfg.cfg['forecast']['run_dir']) + if (os.path.isdir(ucfg.cfg['forecast']['run_dir'] + s))]) self.for_run_dir.sort() @@ -509,7 +492,7 @@ def parse(self, external_logger=None): """ Parse config options. """ self.snowav_version = snowav.__version__ - self.cclimit = -5*1000*1000 + self.cclimit = -5 * 1000 * 1000 self.barcolors = ['xkcd:cobalt', 'xkcd:mustard green', @@ -522,8 +505,8 @@ def parse(self, external_logger=None): 'xkcd:burgundy', 'red'] - out = masks(self.dempath, self.db_convert, plotorder = self.plotorder, - plotlabels = self.plotlabels) + out = masks(self.dempath, self.db_convert, plotorder=self.plotorder, + plotlabels=self.plotlabels) self.dem = out['dem'] self.veg_type = out['veg_type'] @@ -537,11 +520,11 @@ def parse(self, external_logger=None): self.tmp_log.append(log) # Establish database connection - self.basins, cnx, out = connect(sqlite = self.sqlite, sql = self.mysql, - plotorder = self.plotorder, user = self.db_user, - password = self.db_password, host = self.db_host, - port = self.db_port, convert = self.db_convert, - add = self.add_basins) + self.basins, cnx, out = connect(sqlite=self.sqlite, sql=self.mysql, + plotorder=self.plotorder, user=self.db_user, + password=self.db_password, host=self.db_host, + port=self.db_port, convert=self.db_convert, + add=self.add_basins) self.connector = cnx for log in out: @@ -552,7 +535,7 @@ def parse(self, external_logger=None): self.tmp_log.append(' {}: {}'.format(basin, self.basins[basin])) # Check snow.nc file location, get topo stats and water year - sfile = os.path.join(self.run_dirs[0],'snow.nc') + sfile = os.path.join(self.run_dirs[0], 'snow.nc') if os.path.isfile(sfile): topo = get_topo_stats(sfile) @@ -561,7 +544,7 @@ def parse(self, external_logger=None): self.pixel = int(topo['dv']) ncf = nc.Dataset(sfile) - t = nc.num2date(ncf.variables['time'][0],ncf.variables['time'].units) + t = nc.num2date(ncf.variables['time'][0], ncf.variables['time'].units) ncf.close() self.wy = handle_year_stradling(t) + 1 @@ -573,11 +556,11 @@ def parse(self, external_logger=None): # make the bins edges = np.arange(self.elev_bins[0], - self.elev_bins[1]+self.elev_bins[2], + self.elev_bins[1] + self.elev_bins[2], self.elev_bins[2]) # use for definition - self.edges = np.arange(self.elev_bins[0]-self.elev_bins[2], + self.edges = np.arange(self.elev_bins[0] - self.elev_bins[2], self.elev_bins[1], self.elev_bins[2]) @@ -592,7 +575,7 @@ def parse(self, external_logger=None): self.masks.keys()) if self.units == 'TAF': - self.conversion_factor = ((self.pixel**2)*0.000000810713194*0.001) + self.conversion_factor = ((self.pixel ** 2) * 0.000000810713194 * 0.001) self.depth_factor = 0.03937 self.dem = self.dem * 3.28 self.depthlbl = 'in' @@ -601,11 +584,11 @@ def parse(self, external_logger=None): if max(self.edges) < 5000: self.tmp_log.append(" WARNING! Config options [snowav] units: TAF " - "and elev_bins: {} may not match! Consider changing elev_bins " - "values".format(self.elev_bins)) + "and elev_bins: {} may not match! Consider changing elev_bins " + "values".format(self.elev_bins)) if self.units == "SI": - self.conversion_factor = ((self.pixel**2)*0.000000810713194)*1233.48/1e9 + self.conversion_factor = ((self.pixel ** 2) * 0.000000810713194) * 1233.48 / 1e9 self.depth_factor = 0.01 self.depthlbl = 'cm' self.vollbl = 'M$M^3$' @@ -613,17 +596,17 @@ def parse(self, external_logger=None): if max(self.edges) > 5000: self.tmp_log.append(" WARNING! Config options [snowav] units: SI " - "and elev_bins: {} may not match! Consider changing elev_bins " - "values".format(self.elev_bins)) + "and elev_bins: {} may not match! Consider changing elev_bins " + "values".format(self.elev_bins)) - self.ixd = np.digitize(self.dem,edges) - self.xlims = (0,len(edges)) + self.ixd = np.digitize(self.dem, edges) + self.xlims = (0, len(edges)) if self.loglevel == 'DEBUG' and self.log_to_file is not True: print('Reading files in {}...'.format(self.run_dirs[0].split('runs')[0])) results = outputs(self.run_dirs, self.wy, self.properties, - self.start_date, self.end_date, None, self.loglevel) + self.start_date, self.end_date, None, self.loglevel) out = results['outputs'] all_dirs = results['dirs'] @@ -637,10 +620,10 @@ def parse(self, external_logger=None): self.tmp_log.append(log[-1]) if self.start_date is not None and self.end_date is not None: ext_shr = (self.directory + - '_' + - self.start_date.date().strftime("%Y%m%d") + - '_' + - self.end_date.date().strftime("%Y%m%d") ) + '_' + + self.start_date.date().strftime("%Y%m%d") + + '_' + + self.end_date.date().strftime("%Y%m%d")) self.figs_path = os.path.join(self.save_path, '{}/'.format(ext_shr)) if external_logger == None: @@ -655,7 +638,7 @@ def parse(self, external_logger=None): if out['dates'] == []: raise Exception('Supplied [run] directory, start_date, and end_date ' - 'give no valid snow files') + 'give no valid snow files') self.outputs = out self.run_dirs = dirs @@ -667,7 +650,7 @@ def parse(self, external_logger=None): self.end_date = self.outputs['dates'][-1] self.tmp_log.append(' Config options [run] end_date ' 'not specified, assigning ' - '{} and {}'.format(self.start_date,self.end_date)) + '{} and {}'.format(self.start_date, self.end_date)) self.ixs = 0 self.ixe = len(self.outputs['dates']) - 1 @@ -680,13 +663,13 @@ def parse(self, external_logger=None): self.ixe = len(self.outputs['dates']) - 1 if ((self.start_date.date() < self.outputs['dates'][0].date()) - or (self.end_date.date() > self.outputs['dates'][-1].date())): + or (self.end_date.date() > self.outputs['dates'][-1].date())): raise Exception('ERROR! Config option [run] start_date or end_date ' 'outside of date range found in [run] directory') # Since model outputs at 23:00, step the figure and report dates to # show 00:00 the next day (unless start of water year) - if self.start_date == datetime(self.wy-1,10,1,23,0,0): + if self.start_date == datetime(self.wy - 1, 10, 1, 23, 0, 0): self.report_start = self.start_date else: @@ -697,17 +680,16 @@ def parse(self, external_logger=None): # have start_date, end_date extf = os.path.splitext(os.path.split(self.config_file)[1]) ext_shr = (self.directory + - '_' + - self.start_date.date().strftime("%Y%m%d") + - '_' + - self.end_date.date().strftime("%Y%m%d") ) + '_' + + self.start_date.date().strftime("%Y%m%d") + + '_' + + self.end_date.date().strftime("%Y%m%d")) self.figs_path = os.path.join(self.save_path, '{}/'.format(ext_shr)) # get forecast outputs if self.forecast_flag: - results = outputs(self.for_run_dirs, self.wy, self.properties, - None, None, None, self.loglevel) + None, None, None, self.loglevel) self.for_outputs = results['outputs'] self.for_run_dirs = results['run_dirs'] @@ -741,16 +723,16 @@ def parse(self, external_logger=None): for time in times: wydate = calculate_date_from_wyhr(int(time), self.wy) - pre_wydate = calculate_date_from_wyhr(int(time-24), self.wy) - flight_dates = np.append(flight_dates,wydate) - pre_flight_dates = np.append(pre_flight_dates,pre_wydate) + pre_wydate = calculate_date_from_wyhr(int(time - 24), self.wy) + flight_dates = np.append(flight_dates, wydate) + pre_flight_dates = np.append(pre_flight_dates, pre_wydate) if self.loglevel == 'DEBUG' and self.log_to_file is not True: print('Reading files in {} for flight updates' '...'.format(self.run_dirs[0].split('runs')[0])) results = outputs(self.all_dirs_flt, self.wy, self.properties, - None, None, flight_dates, self.loglevel) + None, None, flight_dates, self.loglevel) self.flight_outputs = results['outputs'] self.run_dirs_flt = results['run_dirs'] @@ -759,7 +741,7 @@ def parse(self, external_logger=None): self.pre_flight_outputs = results['outputs'] results = outputs(self.all_dirs_flt, self.wy, self.properties, - None, None, pre_flight_dates, self.loglevel) + None, None, pre_flight_dates, self.loglevel) self.pre_flight_outputs = results['outputs'] @@ -775,7 +757,7 @@ def parse(self, external_logger=None): self.report_date = self.end_date + timedelta(hours=1) parts = self.report_name.split('.') self.report_name = (parts[0] + self.report_date.date().strftime("%Y%m%d") + - '.' + parts[1]) + '.' + parts[1]) if not os.path.exists(self.figs_path): os.makedirs(self.figs_path) @@ -807,11 +789,11 @@ def createLog(self): 'debug': {'color': 'green'}, 'warning': {'color': 'yellow'}} - field_styles = {'hostname': {'color': 'magenta'}, - 'programname': {'color': 'cyan'}, - 'name': {'color': 'white'}, - 'levelname': {'color': 'white', 'bold': True}, - 'asctime': {'color': 'green'}} + field_styles = {'hostname': {'color': 'magenta'}, + 'programname': {'color': 'cyan'}, + 'name': {'color': 'white'}, + 'levelname': {'color': 'white', 'bold': True}, + 'asctime': {'color': 'green'}} # start logging loglevel = self.loglevel diff --git a/snowav/framework/figures.py b/snowav/framework/figures.py index 416f6a9..8ffb169 100755 --- a/snowav/framework/figures.py +++ b/snowav/framework/figures.py @@ -19,7 +19,7 @@ from snowav.plotting.inputs import inputs from snowav.inflow.inflow import inflow from snowav.plotting.diagnostics import diagnostics -from snowav.plotting.point_values import point_values +from snowav.plotting.point_values import point_values_csv, point_values_figures from snowav.database.database import collect from snowav.plotting.plotlims import plotlims as plotlims import matplotlib as mpl @@ -315,80 +315,94 @@ def figures(cfg, process): # assign fig name to cfg for use in report.py cfg.assign_vars({'stn_validate_fig_name': ''}) - if cfg.point_values_flag and cfg.point_values: + if cfg.point_values: + cfg._logger.debug(" Beginning point values processing for " + "{}".format(cfg.point_values_csv)) - # check that cfg.point_values_date falls within options - pv_date = cfg.point_values_date - - if pv_date is None: - cfg._logger.info(' Value in [validate] point_values_date being ' - 'assigned to {}'.format( - cfg.end_date.date().strftime("%Y-%m-%d"))) - pv_date = cfg.end_date - - if pv_date < cfg.start_date or pv_date > cfg.end_date: - cfg._logger.info(' Value in [validate] point_values_date outside ' - 'of range in [run] start_date - end_date, ' - 'point_values_date being assigned to ' - '{}'.format(cfg.end_date.date().strftime("%Y-%m-%d"))) - # pv_date = cfg.end_date - idx = -1 - - else: - # get index for cfg.outputs for that date - x = np.abs([date - pv_date for date in cfg.outputs['dates']]) - idx = x.argmin(0) - - model_date = cfg.outputs['dates'][idx] - model_date = model_date.date().strftime('%Y-%m-%d') + flag = True + xy = (cfg.snow_x, cfg.snow_y) + headings = ['name', 'latitude', 'longitude', cfg.point_values_heading] + end_date_str = cfg.end_date.date().strftime("%Y-%m-%d") course_date = cfg.point_values_date.date().strftime('%Y-%m-%d') - nsubplots = (cfg.point_values_settings[3] * cfg.point_values_settings[4] - 1) - 1 - - for idxp, value in enumerate(cfg.point_values_properties): - pflag = True - df = pd.read_csv(cfg.point_values_csv[idxp]) + basin_name = cfg.plotorder[0].split(" ")[0].lower() + pv_date = cfg.point_values_date + nsubplots = (cfg.point_values_settings[3] * + cfg.point_values_settings[4] - 1) - 1 - if cfg.point_values_heading[idxp] is not None: - check_headings = [cfg.point_values_heading[idxp], 'name', - 'latitude', 'longitude'] - else: - check_headings = ['name', 'latitude', 'longitude'] + while flag: + df = pd.read_csv(cfg.point_values_csv) - for head in check_headings: + for head in headings: if head not in df.columns.tolist(): - cfg._logger.warn(' Config option [validate] ' - 'point_values_heading: "{}" not in ' - 'headings in {}, setting ' - 'point_values: ' - 'False'.format(head, - cfg.point_values_csv[idxp])) - pflag = False - - if not pflag: - continue - - if len(df.name.unique()) > nsubplots: - cfg._logger.warn(' Number of subplots that will be generated in ' - 'point_values() may not fit well with settings ' - 'in point_values_settings, consider changing ' - 'nrows and/or ncols in [validate] ' - 'point_values_settings') - - fig_name = '{}model_pixel_{}_{}.csv'.format( - cfg.figs_path, value, cfg.end_date.date().strftime("%Y%m%d")) - - if value == 'swe_z': - factor = cfg.depth_factor + cfg._logger.warn(' Required csv column "{}" not found, ' + 'exiting point values'.format(head)) + if head == cfg.point_values_heading: + cfg._logger.warning(' User specified [validate] ' + 'point_values_heading: {} not ' + 'found'.format(head)) + flag = False + + if pv_date is None: + cfg._logger.info(' Value in [validate] point_values_date ' + 'being assigned to {}'.format(end_date_str)) + pv_date = cfg.end_date + + if pv_date < cfg.start_date or pv_date > cfg.end_date: + cfg._logger.info(' Value in [validate] point_values_date ' + 'outside of range in [run] start_date - ' + 'end_date, point_values_date being assigned ' + 'to: {}'.format(end_date_str)) + idx = -1 else: - cfg._logger.warning(" point_values is currently only " - "configured for values=swe_z") - - array = cfg.outputs[value][idx] * factor + x = np.abs([date - pv_date for date in cfg.outputs['dates']]) + idx = x.argmin(0) + + model_date = cfg.outputs['dates'][idx].date().strftime('%Y-%m-%d') + + for value in cfg.point_values_properties: + filename = '{}_pixel_{}_{}.csv'.format(basin_name, value, + end_date_str) + csv_name = os.path.abspath(os.path.join(cfg.figs_path, + filename)) + + if len(df.name.unique()) > nsubplots: + cfg._logger.warn(' Number of subplots in ' + 'point_values() may not fit well with ' + 'given settings, consider changing ' + 'nrows and/or ncols in [validate] ' + 'point_values_settings') + flag = False + + if value == 'swe_z': + factor = cfg.depth_factor + elif value == 'depth': + factor = 39.37 + else: + factor = 1 + + array = cfg.outputs[value][idx] * factor + + df_res = point_values_csv(array, value, df, xy, csv_name, + model_date, cfg.plotorder[0], + cfg.point_values_heading, + cfg._logger) + + if cfg.point_values_flag: + head = cfg.point_values_heading + if head in df.columns.tolist(): + point_values_figures(array, value, df_res, cfg.dem, + cfg.figs_path, cfg.veg_type, + model_date, course_date, + cfg.point_values_settings, + cfg.pixel, head, cfg._logger) + else: + cfg._logger.warn(' [validate] point_values_heading: ' + '{} not in csv, skipping figures ' + ''.format(cfg.point_values_heading)) + cfg.point_values_flag = False - point_values(array, value, df, (cfg.snow_x, cfg.snow_y), fig_name, - cfg.dem, cfg.figs_path, cfg.veg_type, - cfg.point_values_heading[idxp], model_date, course_date, - cfg.point_values_settings, cfg.pixel, cfg._logger) + # if everything is successful, set to False at the end + flag = False if cfg.compare_runs_flag: args['variables'] = ['swe_vol', 'swi_vol'] diff --git a/snowav/plotting/point_values.py b/snowav/plotting/point_values.py index 59f1921..e4eb2b6 100755 --- a/snowav/plotting/point_values.py +++ b/snowav/plotting/point_values.py @@ -14,10 +14,10 @@ import snowav.framework.figures -def point_values(output, value, df, imgxy, filename, dem, figs_path, veg_type, - heading, model_date, course_date, settings, pixel, logger): - """Read in specified csv file of snow course validation locations, write out - model values at those points, and create figures. +def point_values_csv(output, value, df, imgxy, filename, model_date, basin, + heading, logger): + """Read in specified csv file of snow course validation locations, write + model values at those points. Args ------ @@ -26,58 +26,58 @@ def point_values(output, value, df, imgxy, filename, dem, figs_path, veg_type, df {DataFrame}: DataFrame from point_values_csv file imgxy {arr}: x, y coordinates for the image, from get_topo_stats.py filename {str}: file name - dem {arr}: dem - figs_path {str}: base path to save - veg_type {arr}: array of veg types, from topo.nc - heading {str}: heading from file to use model_date {str}: date that model values were pulled - course_date {str}: date of course values - settings {list}: list of figure settings - pixel {int}: pixel size logger {class}: logger """ - # need to get this up for DWR if value == 'swe_z': - header = cbarlabel = 'SWE [in]' - else: - logger.warn(' point_values is currently only configured for "swe_z"') - return + header = 'SWE [in]' + intro_col = 'Snow Water Equivalent (SWE) depth in inches' + if value == 'depth': + header = 'snow depth [in]' + intro_col = 'Snow depth in inches' + if value == 'density': + header = 'density [kg/m^3]' + intro_col = 'Snow density in kg/m^3' date_str = datetime.now().date().strftime("%Y-%m-%d") date_col = 'Date generated: {}'.format(date_str) - swe_col = 'SWE [in]' - med_col = '9 pixel median SWE [in]' + basin_col = 'Model basin: {}'.format(basin) + model_col = 'Model results from: {}'.format(model_date) + med_col = '9 pixel median {}'.format(header) lat_col = 'latitude' lon_col = 'longitude' x_col = 'model x' y_col = 'model y' stn_col = 'name' - sic = 'The "{}" column refers the model value in the pixel that '\ - 'contains the "{}" "{}" point'.format(swe_col, lat_col, lon_col) - npmc = 'The "{}" column means the median SWE value '\ - 'of the 9 closest pixels to the "{}" "{}" '\ + out_cols = [stn_col, lat_col, lon_col, x_col, y_col, header, med_col] + all_cols = [stn_col, x_col, y_col, lat_col, lon_col, header, med_col, + heading] + sic = 'The "{}" column refers the model value in the pixel that ' \ + 'contains the "{}" "{}" point'.format(header, lat_col, lon_col) + npmc = 'The "{}" column means the median value ' \ + 'of the 9 closest pixels to the "{}" "{}" ' \ 'point'.format(med_col, lat_col, lon_col) file_header = ['USDA Agicultural Research Service Snowpack Summary Data', - 'Snow Water Equivalent (SWE) depth in inches', + intro_col, 'Data provided are model results from the iSnobal model', + basin_col, + model_col, + date_col, 'The "model x" column refers to the model column index', 'The "model y" column refers to the model row index', sic, npmc, - date_col, 'Contact: Scott Havens \n'] - cols = [stn_col, lat_col, lon_col, x_col, y_col, header, med_col, heading] - - pixel_swe = pd.DataFrame(columns=cols) + pixel_swe = pd.DataFrame(columns=all_cols) # get closest model pixel for i in df.index: - sta = df.loc[i, 'name'] - lat = df.loc[i, 'latitude'] - lon = df.loc[i, 'longitude'] + sta = df.loc[i, stn_col] + lat = df.loc[i, lat_col] + lon = df.loc[i, lon_col] ll = utm.from_latlon(lat, lon) xind = np.where(abs(imgxy[0] - ll[0]) == min(abs(imgxy[0] - ll[0])))[0] @@ -96,9 +96,7 @@ def point_values(output, value, df, imgxy, filename, dem, figs_path, veg_type, pixel_swe.loc[i, lon_col] = lon pixel_swe.loc[i, x_col] = int(xind) pixel_swe.loc[i, y_col] = int(yind) - - if heading is not None: - pixel_swe.loc[i, heading] = df.loc[i, heading] + pixel_swe.loc[i, heading] = df.loc[i, heading] # Get the median of the nine closest pixels val = np.array([]) @@ -121,320 +119,348 @@ def point_values(output, value, df, imgxy, filename, dem, figs_path, veg_type, with open(filename, mode='w', encoding='utf-8') as f: f.write('\n'.join(file_header)) - pixel_swe.to_csv(filename, encoding='utf-8', mode='a', index=False) - - ########################################################################### - # figures - ########################################################################### - - if heading is not None: - - width = settings[0] - height = settings[1] - dpi = settings[2] - nrows = settings[3] - ncols = settings[4] - font_small = settings[5] - font_medium = settings[6] - npix = settings[7] - ss = settings[8] - levels = settings[9] - annot_x1 = settings[10] - annot_y1 = settings[11] - annot_x2 = settings[12] - annot_y2 = settings[13] - - sns.set_style('white') - plt.close(0) - f, a = plt.subplots(num=0, figsize=(int(width), int(height)), - dpi=dpi, nrows=nrows, ncols=ncols) - a = a.flatten() - - plt.close(1) - f1, a1 = plt.subplots(num=1, figsize=(int(width), int(height)), - dpi=dpi, nrows=nrows, ncols=ncols) - a1 = a1.flatten() - - plt.close(2) - f2 = plt.figure(num=2, figsize=(width, height - 2), dpi=dpi) - a2 = plt.gca() - - plt.close(3) - f3 = plt.figure(num=3, figsize=(width, height - 2), dpi=400) - a3 = plt.gca() - - lvls = list(np.arange(np.min(dem.flatten()), np.max(dem.flatten()), 500)) - - snow_map = cmocean.cm.haline_r - - # get min/max for all sub-domains - pixel_swe = pd.DataFrame(pixel_swe) - veg = [] - for n, idx in enumerate(pixel_swe.index): - # name = pixel_swe.loc[idx, stn_col] - ix = int(pixel_swe.loc[idx][x_col]) - iy = int(pixel_swe.loc[idx][y_col]) - - veg_sub = veg_type[(iy - npix):(iy + npix + 1), - (ix - npix):(ix + npix + 1)].flatten() - veg = np.append(veg_sub, veg) - - if n == 0: - g_min = np.nanmin(output[(iy - npix):(iy + npix), - (ix - npix):(ix + npix)]) - g_max = np.nanmax(output[(iy - npix):(iy + npix), - (ix - npix):(ix + npix)]) + pixel_swe.to_csv(filename, encoding='utf-8', mode='a', columns=out_cols, + index=False) + logger.info(" Saved: {}".format(filename)) - else: - vmin = np.nanmin(output[(iy - npix):(iy + npix), - (ix - npix):(ix + npix)]) - vmax = np.nanmax(output[(iy - npix):(iy + npix), - (ix - npix):(ix + npix)]) - - if vmin < g_min: - g_min = vmin - if vmax > g_max: - g_max = vmax - - ymin = copy.deepcopy(g_min) - veg = np.unique(veg) - vl = list(veg) - norm = matplotlib.colors.BoundaryNorm(vl, len(vl)) - - h3 = a3.imshow(veg_type, cmap='Vega20', norm=norm, alpha=0.75) - a3.contour(dem, colors='k', levels=lvls, linewidths=0.15) - - place = np.linspace(g_min, g_max, 256) - - # sep = 0.05 - # wid = 1 / len(pixel_swe[stn_col].unique()) - sep - # widths = np.arange((-1 + wid), (1 - wid), wid) - - for idx, v in enumerate(pixel_swe.name.unique()): - mflag = False - - if idx < len(pixel_swe[stn_col].unique()) + 1: - - # this plots repeating values - if len(pixel_swe[pixel_swe[stn_col] == v][stn_col].values) > 1: - mflag = True - vstr = mstr = '' - - for idx3 in range(0, len(pixel_swe[pixel_swe[stn_col] == v][stn_col].values)): - course = pixel_swe.loc[idx + idx3][heading] - ix = int(pixel_swe[pixel_swe[stn_col] == v][x_col].values[idx3]) - iy = int(pixel_swe[pixel_swe[stn_col] == v][y_col].values[idx3]) - ixs = int(np.where(abs(place - course) == min(abs(place - course)))[0]) - color = snow_map(ixs) - - if idx3 == 0: - vstr = '{}'.format(round(course, 1)) - mstr = '{}'.format(output[iy, ix].round(1)) - else: - vstr = vstr + ', {}'.format(round(course, 1)) - mstr = mstr + ', {}'.format(output[iy, ix].round(1)) - - a[idx].scatter(x=ix, y=iy, marker='^', s=ss, c=color, - edgecolors='k', linewidths=0.5) - - cstr = 'validation: {}\nmodel: {}'.format(vstr, mstr) - a[idx].text(annot_x1, annot_y1, cstr, horizontalalignment='left', - transform=a[idx].transAxes, fontsize=font_small - 1, - color='w') - - ix = int(pixel_swe[pixel_swe[stn_col] == v][x_col].values[0]) - iy = int(pixel_swe[pixel_swe[stn_col] == v][y_col].values[0]) - swe = pixel_swe[pixel_swe[stn_col] == v][header].values[0] - course = pixel_swe[pixel_swe[stn_col] == v][heading].values[0] - - s = dem[(iy - npix):(iy + npix + 1), (ix - npix):(ix + npix + 1)] - snow_sec = output[(iy - npix):(iy + npix + 1), (ix - npix):(ix + npix + 1)] - - l1 = [int(x) * snow_sec.shape[0] + npix - 1 for x in range(npix - 1, npix + 2)] + \ - [int(x) * snow_sec.shape[0] + npix for x in range(npix - 1, npix + 2)] + \ - [int(x) * snow_sec.shape[0] + npix + 1 for x in range(npix - 1, npix + 2)] - zo = 1 - if idx == 0: - ml = 'model' - vl = 'validation' - sl = r'$\pm${} pixels'.format(npix) - bl1 = r'$\pm$1 pixel'.format(npix) - - else: - ml = '__nolabel__' - vl = '__nolabel__' - sl = '__nolabel__' - bl1 = '__nolabel__' - - # hv = a2.plot(idx, course, 'bP', markersize=5, label=vl, zorder=4) - # hs = a2.plot(idx, swe, 'rX', markersize=5, label=ml, zorder=3) - b1 = a2.boxplot(snow_sec.flatten(), positions=[idx - 0.22], - widths=0.18, showfliers=False) - b2 = a2.boxplot(snow_sec.flatten()[l1], positions=[idx + 0.22], - widths=0.18, showfliers=False) - a2.plot(0, ymin, 'dimgray', label=sl) - a2.plot(0, ymin, 'forestgreen', label=bl1) - if idx == 0: - astr = 'val-mod [in], %:\n{}\n' \ - '{}%'.format(round((course - swe), 1), - int((swe / course) * 100)) - else: - astr = '{}\n{}%'.format(round((course - swe), 1), - int((swe / course) * 100)) + return pixel_swe - a2.annotate(astr, (idx - 0.25, ymin - 2), fontsize=font_small, - color='k') - for prop in ['boxes', 'whiskers', 'fliers', 'caps']: - plt.setp(b1[prop], color='dimgray') - plt.setp(b2[prop], color='forestgreen') +def point_values_figures(output, value, df, dem, figs_path, veg_type, + model_date, course_date, settings, pixel, heading, + logger): + """ Make point values figures. - lvls = list(np.arange(np.min(s.flatten()), - np.max(s.flatten()), levels)) - ixs = int(np.where(abs(place - course) == - min(abs(place - course)))[0]) + Args + ------ + output {arr}: model output array from date of interest + value {str}: snowav database value + df {DataFrame}: DataFrame from point_values_csv function + dem {arr}: dem + figs_path {str}: base path for saving + veg_type {arr}: veg type + model_date {str}: date that model values were pulled + course_date {str}: snow course data + settings {list}: figure settings + pixel {int}: pixel sixe + heading {string}: df heading + logger {class}: logger + """ - color = snow_map(ixs) + if value == 'swe_z': + header = 'SWE [in]' + if value == 'depth': + header = 'snow depth [in]' + if value == 'density': + header = 'density [kg/m^3]' - a[idx].contour(dem, colors='k', levels=lvls, linewidths=0.25) - h = a[idx].imshow(output, cmap=snow_map, clim=(g_min, g_max)) + x_col = 'model x' + y_col = 'model y' + stn_col = 'name' + width = settings[0] + height = settings[1] + dpi = settings[2] + nrows = settings[3] + ncols = settings[4] + font_small = settings[5] + font_medium = settings[6] + npix = settings[7] + ss = settings[8] + levels = settings[9] + annot_x1 = settings[10] + annot_y1 = settings[11] + annot_x2 = settings[12] + annot_y2 = settings[13] + + sns.set_style('white') + plt.close(0) + f, a = plt.subplots(num=0, figsize=(int(width), int(height)), + dpi=dpi, nrows=nrows, ncols=ncols) + a = a.flatten() + + plt.close(1) + f1, a1 = plt.subplots(num=1, figsize=(int(width), int(height)), + dpi=dpi, nrows=nrows, ncols=ncols) + a1 = a1.flatten() + + plt.close(2) + f2 = plt.figure(num=2, figsize=(width, height - 2), dpi=dpi) + a2 = plt.gca() + + plt.close(3) + f3 = plt.figure(num=3, figsize=(width, height - 2), dpi=400) + a3 = plt.gca() + + lvls = list(np.arange(np.min(dem.flatten()), np.max(dem.flatten()), 500)) + + snow_map = cmocean.cm.haline_r + + veg = [] + for n, idx in enumerate(df.index): + + ix = int(df.loc[idx][x_col]) + iy = int(df.loc[idx][y_col]) + + veg_sub = veg_type[(iy - npix):(iy + npix + 1), + (ix - npix):(ix + npix + 1)].flatten() + veg = np.append(veg_sub, veg) + + if n == 0: + g_min = np.nanmin(output[(iy - npix):(iy + npix), + (ix - npix):(ix + npix)]) + g_max = np.nanmax(output[(iy - npix):(iy + npix), + (ix - npix):(ix + npix)]) + + else: + vmin = np.nanmin(output[(iy - npix):(iy + npix), + (ix - npix):(ix + npix)]) + vmax = np.nanmax(output[(iy - npix):(iy + npix), + (ix - npix):(ix + npix)]) + + if vmin < g_min: + g_min = vmin + if vmax > g_max: + g_max = vmax + + ymin = copy.deepcopy(g_min) + veg = np.unique(veg) + vl = list(veg) + norm = matplotlib.colors.BoundaryNorm(vl, len(vl)) + + h3 = a3.imshow(veg_type, cmap='Vega20', norm=norm, alpha=0.75) + a3.contour(dem, colors='k', levels=lvls, linewidths=0.15) + + place = np.linspace(g_min, g_max, 256) + + for idx, v in enumerate(df.name.unique()): + mflag = False + + if idx < len(df[stn_col].unique()) + 1: + + # this plots repeating values + if len(df[df[stn_col] == v][stn_col].values) > 1: + mflag = True + vstr = mstr = '' + + for idx3 in range(0, len(df[df[stn_col] == v][stn_col].values)): + course = df.loc[idx + idx3][heading] + ix = int(df[df[stn_col] == v][x_col].values[idx3]) + iy = int(df[df[stn_col] == v][y_col].values[idx3]) + ixs = int(np.where(abs(place - course) == + min(abs(place - course)))[0]) + color = snow_map(ixs) + + if idx3 == 0: + vstr = '{}'.format(round(course, 1)) + mstr = '{}'.format(output[iy, ix].round(1)) + else: + vstr = vstr + ', {}'.format(round(course, 1)) + mstr = mstr + ', {}'.format(output[iy, ix].round(1)) - if not mflag: a[idx].scatter(x=ix, y=iy, marker='^', s=ss, c=color, edgecolors='k', linewidths=0.5) - cstr = 'validation: {}\nmodel: ' \ - '{}'.format(round(course, 1), - output[iy, ix].round(1)) - a[idx].text(annot_x1, annot_y1, cstr, - horizontalalignment='left', - transform=a[idx].transAxes, - fontsize=font_small, - color='w') - - a[idx].get_xaxis().set_ticks([]) - a[idx].get_yaxis().set_ticks([]) - # a[idx].set_ylim((iy - (npix + 0.5), iy + npix + 0.5)) - # a[idx].set_xlim((ix - (npix + 0.5), ix + npix + 0.5)) - - a[idx].set_ylim((iy + npix + 0.5), (iy - (npix + 0.5))) - a[idx].set_xlim((ix - (npix + 0.5), ix + npix + 0.5)) - - a[idx].set_title(v, fontsize=font_small) - - # legend of sorts - veg_subp = veg_type[(iy - npix):(iy + npix + 1), - (ix - npix):(ix + npix + 1)] - vegmap = plt.cm.get_cmap('Vega20') - - a1[idx].contour(dem, colors='k', levels=lvls, linewidths=0.25) - h1 = a1[idx].imshow(veg_type, cmap='Vega20', norm=norm) - a1[idx].scatter(x=ix, y=iy, marker='^', s=ss, c='k', - edgecolors='k', linewidths=0.5) - a1[idx].get_xaxis().set_ticks([]) - a1[idx].get_yaxis().set_ticks([]) - a1[idx].set_ylim((iy + npix + 0.5), (iy - (npix + 0.5))) - a1[idx].set_xlim((ix - (npix + 0.5), ix + npix + 0.5)) - a1[idx].set_title(v, fontsize=font_small) - - a3.scatter(x=ix, y=iy, marker='s', s=10, color="None", - edgecolors='k', linewidths=0.5) - a3.annotate(v, (ix + 5, iy - 5), + cstr = 'validation: {}\nmodel: {}'.format(vstr, mstr) + a[idx].text(annot_x1, + annot_y1, + cstr, + horizontalalignment='left', + transform=a[idx].transAxes, fontsize=font_small - 1, - color='k') - - for vs in np.unique(veg_subp): - ixf = np.where(veg_subp == vs) - fx = ixf[0][0] - fy = ixf[1][0] - nx = ix + fy - npix - ny = iy + fx - npix - a1[idx].scatter(x=nx, y=ny, marker='X', s=3, c='k', - edgecolors='k', linewidths=0.5) - a1[idx].annotate(str(vs), (nx, ny), - fontsize=font_small - 3, - color='w') - - # tack on another subplot for legend and colorbar - a[idx + 1].imshow(output, cmap=snow_map, alpha=0) - a[idx + 1].set_title('', fontsize=font_small) - a[idx + 1].get_xaxis().set_ticks([]) - a[idx + 1].get_yaxis().set_ticks([]) - a[idx + 1].axis('off') - cbaxes = inset_axes(a[idx + 1], width="90%", height="10%", loc=8) - cbar = f.colorbar(h, cax=cbaxes, ticks=[int(g_min + 1), - int(((g_max - g_min) / 2) + g_min), - int(g_max)], - orientation='horizontal') - cbar.ax.tick_params(labelsize=font_medium) - cbar.set_label(cbarlabel, fontsize=font_medium) - cbar.ax.xaxis.set_label_position('top') - cstr = (r'Validation pixel $\pm${} pixels' - '\nmodel: {}\nvalidation: {}\ncontour interval: {} ' - 'ft\npixel width: {} m, {} ' - 'ft'.format(npix, model_date, course_date, - levels, str(pixel), str(int(pixel * 3.28)))) - a[idx + 1].text(annot_x2, annot_y2, cstr, horizontalalignment='left', - transform=a[idx + 1].transAxes, fontsize=font_medium) - - a1[idx + 1].imshow(veg_type, cmap=vegmap, clim=(np.min(veg), - np.max(veg)), - alpha=0) - a1[idx + 1].set_title('', fontsize=font_small) - a1[idx + 1].get_xaxis().set_ticks([]) - a1[idx + 1].get_yaxis().set_ticks([]) - a1[idx + 1].axis('off') - cstr = ('model: {}\nvalidation: {}\ncontour interval: {} ' - 'ft\npixel width: {} m, {} ' - 'ft'.format(model_date, course_date, - levels, str(pixel), str(int(pixel * 3.28)))) - a1[idx + 1].text(annot_x2, annot_y2, cstr, horizontalalignment='left', - transform=a1[idx + 1].transAxes, fontsize=font_medium) - - for n in range(idx + 2, nrows * ncols): - f.delaxes(a[n]) - - for n in range(idx + 2, nrows * ncols): - f1.delaxes(a1[n]) - - a2.set_ylabel(cbarlabel) - a2.set_xticks(list(range(0, len(pixel_swe[stn_col].unique())))) - a2.set_xticklabels(pixel_swe[stn_col].unique(), rotation=90, - fontsize=font_small - 1) - a2.set_xlim((-1, len(pixel_swe[stn_col].unique()) + 1)) - a2.legend(loc='upper left') - a2.grid(linewidth=0.25) - a2.set_title('Validation and Model Pixel Values, {}'.format(model_date)) - - a3.get_xaxis().set_ticks([]) - a3.get_yaxis().set_ticks([]) - divider3 = make_axes_locatable(a3) - cax3 = divider3.append_axes("right", size="4%", pad=0.2) - cbar3 = plt.colorbar(h3, cax=cax3, ticks=list(veg)) - cbar3.ax.tick_params() - cbar3.set_label('vegetation type classification') - - f.tight_layout() - f1.tight_layout() - f2.tight_layout() - f3.tight_layout() - - fig_name_short = 'validation_list_{}'.format(value) - fig_name = '{}{}.png'.format(figs_path, fig_name_short) - logger.info(' saving {}'.format(fig_name)) - snowav.framework.figures.save_fig(f2, fig_name) + color='w') + + ix = int(df[df[stn_col] == v][x_col].values[0]) + iy = int(df[df[stn_col] == v][y_col].values[0]) + swe = df[df[stn_col] == v][header].values[0] + course = df[df[stn_col] == v][heading].values[0] + + s = dem[(iy - npix):(iy + npix + 1), + (ix - npix):(ix + npix + 1)] + snow_sec = output[(iy - npix):(iy + npix + 1), + (ix - npix):(ix + npix + 1)] + + l1 = [int(x) * snow_sec.shape[0] + + npix - 1 for x in range(npix - 1, npix + 2)] + \ + [int(x) * snow_sec.shape[0] + + npix for x in range(npix - 1, npix + 2)] + \ + [int(x) * snow_sec.shape[0] + + npix + 1 for x in range(npix - 1, npix + 2)] + + if idx == 0: + sl = r'$\pm${} pixels'.format(npix) + bl1 = r'$\pm$1 pixel'.format(npix) + else: + sl = '__nolabel__' + bl1 = '__nolabel__' + + b1 = a2.boxplot(snow_sec.flatten(), positions=[idx - 0.22], + widths=0.18, showfliers=False) + b2 = a2.boxplot(snow_sec.flatten()[l1], positions=[idx + 0.22], + widths=0.18, showfliers=False) + a2.plot(0, ymin, 'dimgray', label=sl) + a2.plot(0, ymin, 'forestgreen', label=bl1) + if idx == 0: + astr = 'val-mod [in], %:\n{}\n' \ + '{}%'.format(round((course - swe), 1), + int((swe / course) * 100)) + else: + astr = '{}\n{}%'.format(round((course - swe), 1), + int((swe / course) * 100)) + a2.annotate(astr, (idx - 0.25, ymin - 2), fontsize=font_small, + color='k') + + for prop in ['boxes', 'whiskers', 'fliers', 'caps']: + plt.setp(b1[prop], color='dimgray') + plt.setp(b2[prop], color='forestgreen') + + lvls = list(np.arange(np.min(s.flatten()), + np.max(s.flatten()), levels)) + ixs = int(np.where(abs(place - course) == + min(abs(place - course)))[0]) + + color = snow_map(ixs) + a[idx].contour(dem, colors='k', levels=lvls, linewidths=0.25) + h = a[idx].imshow(output, cmap=snow_map, clim=(g_min, g_max)) + + if not mflag: + a[idx].scatter(x=ix, y=iy, marker='^', s=ss, c=color, + edgecolors='k', linewidths=0.5) + cstr = 'validation: {}\nmodel: ' \ + '{}'.format(round(course, 1), + output[iy, ix].round(1)) + a[idx].text(annot_x1, annot_y1, cstr, + horizontalalignment='left', + transform=a[idx].transAxes, + fontsize=font_small, + color='w') + + a[idx].get_xaxis().set_ticks([]) + a[idx].get_yaxis().set_ticks([]) + a[idx].set_ylim((iy + npix + 0.5), (iy - (npix + 0.5))) + a[idx].set_xlim((ix - (npix + 0.5), ix + npix + 0.5)) + a[idx].set_title(v, fontsize=font_small) + + # legend of sorts + veg_subp = veg_type[(iy - npix):(iy + npix + 1), + (ix - npix):(ix + npix + 1)] + vegmap = plt.cm.get_cmap('Vega20') + + a1[idx].contour(dem, colors='k', levels=lvls, linewidths=0.25) + a1[idx].imshow(veg_type, cmap='Vega20', norm=norm) + a1[idx].scatter(x=ix, y=iy, marker='^', s=ss, c='k', + edgecolors='k', linewidths=0.5) + a1[idx].get_xaxis().set_ticks([]) + a1[idx].get_yaxis().set_ticks([]) + + a1[idx].set_ylim((iy + npix + 0.5), (iy - (npix + 0.5))) + a1[idx].set_xlim((ix - (npix + 0.5), ix + npix + 0.5)) + a1[idx].set_title(v, fontsize=font_small) + + a3.scatter(x=ix, y=iy, marker='s', s=10, color="None", + edgecolors='k', linewidths=0.5) + a3.annotate(v, + (ix + 5, iy - 5), + fontsize=font_small - 1, + color='k') + + for vs in np.unique(veg_subp): + ixf = np.where(veg_subp == vs) + fx = ixf[0][0] + fy = ixf[1][0] + nx = ix + fy - npix + ny = iy + fx - npix + a1[idx].scatter(x=nx, y=ny, marker='X', s=3, c='k', + edgecolors='k', linewidths=0.5) + a1[idx].annotate(str(vs), (nx, ny), fontsize=font_small - 3, + color='w') + + # tack on another subplot for legend and colorbar + a[idx + 1].imshow(output, cmap=snow_map, alpha=0) + a[idx + 1].set_title('', fontsize=font_small) + a[idx + 1].get_xaxis().set_ticks([]) + a[idx + 1].get_yaxis().set_ticks([]) + a[idx + 1].axis('off') + cbaxes = inset_axes(a[idx + 1], width="90%", height="10%", loc=8) + cbar = f.colorbar(h, + cax=cbaxes, + ticks=[int(g_min + 1), + int(((g_max - g_min) / 2) + g_min), + int(g_max)], + orientation='horizontal') + cbar.ax.tick_params(labelsize=font_medium) + cbar.set_label(header, fontsize=font_medium) + cbar.ax.xaxis.set_label_position('top') + cstr = (r'Validation pixel $\pm${} pixels' + '\nmodel: {}\nvalidation: {}\ncontour interval: {} ' + 'ft\npixel width: {} m, {} ' + 'ft'.format(npix, model_date, course_date, + levels, str(pixel), str(int(pixel * 3.28)))) + a[idx + 1].text(annot_x2, annot_y2, cstr, horizontalalignment='left', + transform=a[idx + 1].transAxes, fontsize=font_medium) + + a1[idx + 1].imshow(veg_type, + cmap=vegmap, + clim=(np.min(veg), np.max(veg)), + alpha=0) + a1[idx + 1].set_title('', fontsize=font_small) + a1[idx + 1].get_xaxis().set_ticks([]) + a1[idx + 1].get_yaxis().set_ticks([]) + a1[idx + 1].axis('off') + cstr = ('model: {}\nvalidation: {}\ncontour interval: {} ' + 'ft\npixel width: {} m, {} ' + 'ft'.format(model_date, course_date, + levels, str(pixel), str(int(pixel * 3.28)))) + a1[idx + 1].text(annot_x2, annot_y2, cstr, horizontalalignment='left', + transform=a1[idx + 1].transAxes, fontsize=font_medium) + + for n in range(idx + 2, nrows * ncols): + f.delaxes(a[n]) + for n in range(idx + 2, nrows * ncols): + f1.delaxes(a1[n]) + + a2.set_ylabel(header) + a2.set_xticks(list(range(0, len(df[stn_col].unique())))) + a2.set_xticklabels(df[stn_col].unique(), rotation=90, + fontsize=font_small - 1) + a2.set_xlim((-1, len(df[stn_col].unique()) + 1)) + a2.legend(loc='upper left') + a2.grid(linewidth=0.25) + a2.set_title('Validation and Model Pixel Values, {}'.format(model_date)) + + a3.get_xaxis().set_ticks([]) + a3.get_yaxis().set_ticks([]) + divider3 = make_axes_locatable(a3) + cax3 = divider3.append_axes("right", size="4%", pad=0.2) + cbar3 = plt.colorbar(h3, cax=cax3, ticks=list(veg)) + cbar3.ax.tick_params() + cbar3.set_label('vegetation type classification') + + f.tight_layout() + f1.tight_layout() + f2.tight_layout() + f3.tight_layout() + + fig_name_short = 'validation_list_{}'.format(value) + fig_name = '{}{}.png'.format(figs_path, fig_name_short) + logger.info(' Saved: {}'.format(fig_name)) + snowav.framework.figures.save_fig(f2, fig_name) + + # currently only passing the swe_z image + if value == 'swe_z': fig_name_short = 'validation_map_{}'.format(value) fig_name = '{}{}.png'.format(figs_path, fig_name_short) - logger.info(' saving {}'.format(fig_name)) + logger.info(' Saved: {}'.format(fig_name)) snowav.framework.figures.save_fig(f, fig_name) - fig_name_short = 'validation_veg_map' - fig_name = '{}{}.png'.format(figs_path, fig_name_short) - logger.info(' saving {}'.format(fig_name)) + fig_name_short = 'validation_veg_map' + fig_name = '{}{}.png'.format(figs_path, fig_name_short) + if not os.path.isfile(os.path.abspath(fig_name)): + logger.info(' Saved: {}'.format(fig_name)) snowav.framework.figures.save_fig(f1, fig_name) - fig_name_short = 'validation_locations' - fig_name = '{}{}.png'.format(figs_path, fig_name_short) - logger.info(' saving {}'.format(fig_name)) + fig_name_short = 'validation_locations' + fig_name = '{}{}.png'.format(figs_path, fig_name_short) + if not os.path.isfile(os.path.abspath(fig_name)): + logger.info(' Saved: {}'.format(fig_name)) snowav.framework.figures.save_fig(f3, fig_name) diff --git a/tests/test_snowav.py b/tests/test_snowav.py index 76a1f82..28bda77 100755 --- a/tests/test_snowav.py +++ b/tests/test_snowav.py @@ -641,7 +641,7 @@ def tearDownClass(self): 'validation_locations.png', 'swe_vol_timeseries_20190402_taf.csv', 'swi_vol_timeseries_20190402_taf.csv', - 'model_pixel_swe_z_20190402.csv', + 'lakes_pixel_swe_z_2019-04-02.csv', 'inputs_lakes_test.png', 'inputs_period_lakes_test.png', 'SnowpackSummary20190403.pdf',