From 75b1f2c9533780c15b3f058e18b2cf5e983b8884 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Fri, 30 Sep 2022 20:26:57 -0600 Subject: [PATCH] Add min and max thresholds for plotting fields Use these to mask sea-ice concentration (with a default min threshold of 0.15) and sea-ice thickness (with a minimum of zero). --- mpas_analysis/default.cfg | 6 ++ .../sea_ice/climatology_map_sea_ice_conc.py | 16 +++-- .../sea_ice/climatology_map_sea_ice_thick.py | 2 +- .../plot/plot_climatology_map_subtask.py | 65 ++++++++++--------- 4 files changed, 52 insertions(+), 37 deletions(-) diff --git a/mpas_analysis/default.cfg b/mpas_analysis/default.cfg index eda0b2ea4..d9049ecbe 100644 --- a/mpas_analysis/default.cfg +++ b/mpas_analysis/default.cfg @@ -3662,6 +3662,9 @@ observationPrefixes = ['NASATeam', 'Bootstrap'] # arrange subplots vertically? vertical = False +# the minimum threshold below which concentration is masked out +minConcentration = 0.15 + # observations files concentrationNASATeamNH_JFM = SSMI/NASATeam_NSIDC0051/SSMI_NASATeam_gridded_concentration_NH_jfm.interp0.5x0.5_20180710.nc concentrationNASATeamNH_JAS = SSMI/NASATeam_NSIDC0051/SSMI_NASATeam_gridded_concentration_NH_jas.interp0.5x0.5_20180710.nc @@ -3705,6 +3708,9 @@ observationPrefixes = ['NASATeam', 'Bootstrap'] # arrange subplots vertically? vertical = False +# the minimum threshold below which concentration is masked out +minConcentration = 0.15 + # observations files concentrationNASATeamSH_DJF = SSMI/NASATeam_NSIDC0051/SSMI_NASATeam_gridded_concentration_SH_djf.interp0.5x0.5_20180710.nc concentrationNASATeamSH_JJA = SSMI/NASATeam_NSIDC0051/SSMI_NASATeam_gridded_concentration_SH_jja.interp0.5x0.5_20180710.nc diff --git a/mpas_analysis/sea_ice/climatology_map_sea_ice_conc.py b/mpas_analysis/sea_ice/climatology_map_sea_ice_conc.py index 66a04f565..e447177cb 100644 --- a/mpas_analysis/sea_ice/climatology_map_sea_ice_conc.py +++ b/mpas_analysis/sea_ice/climatology_map_sea_ice_conc.py @@ -112,8 +112,7 @@ def __init__(self, config, mpasClimatologyTask, hemisphere, else: self._add_ref_tasks(seasons, comparisonGridNames, hemisphere, hemisphereLong, remapClimatologySubtask, - controlConfig, mpasFieldName, - fieldName, iselValues) + controlConfig, mpasFieldName) def _add_obs_tasks(self, seasons, comparisonGridNames, hemisphere, hemisphereLong, remapClimatologySubtask, @@ -122,6 +121,8 @@ def _add_obs_tasks(self, seasons, comparisonGridNames, hemisphere, obsFieldName = 'seaIceConc' sectionName = self.taskName + minConcentration = config.getfloat(self.taskName, 'minConcentration') + observationPrefixes = config.getexpression(sectionName, 'observationPrefixes') for prefix in observationPrefixes: @@ -181,15 +182,17 @@ def _add_obs_tasks(self, seasons, comparisonGridNames, hemisphere, groupSubtitle=None, groupLink='{}_conc'.format(hemisphere.lower()), galleryName='Observations: SSM/I {}'.format( - prefix)) + prefix), + maskMinThreshold=minConcentration) self.add_subtask(subtask) def _add_ref_tasks(self, seasons, comparisonGridNames, hemisphere, hemisphereLong, remapClimatologySubtask, - controlConfig, mpasFieldName, fieldName, - iselValues): + controlConfig, mpasFieldName): + minConcentration = self.config.getfloat(self.taskName, + 'minConcentration') controlRunName = controlConfig.get('runs', 'mainRunName') galleryName = None refTitleLabel = 'Control: {}'.format(controlRunName) @@ -224,7 +227,8 @@ def _add_ref_tasks(self, seasons, comparisonGridNames, hemisphere, galleryGroup=galleryGroup, groupSubtitle=None, groupLink='{}_conc'.format(hemisphere.lower()), - galleryName=galleryName) + galleryName=galleryName, + maskMinThreshold=minConcentration) self.add_subtask(subtask) diff --git a/mpas_analysis/sea_ice/climatology_map_sea_ice_thick.py b/mpas_analysis/sea_ice/climatology_map_sea_ice_thick.py index bb15c15d5..731f5671d 100644 --- a/mpas_analysis/sea_ice/climatology_map_sea_ice_thick.py +++ b/mpas_analysis/sea_ice/climatology_map_sea_ice_thick.py @@ -164,7 +164,7 @@ def __init__(self, config, mpasClimatologyTask, hemisphere, groupSubtitle=None, groupLink=f'{hemisphere.lower()}_thick', galleryName=galleryName, - maskValue=0) + maskMinThreshold=0) self.add_subtask(subtask) diff --git a/mpas_analysis/shared/plot/plot_climatology_map_subtask.py b/mpas_analysis/shared/plot/plot_climatology_map_subtask.py index 01d5ef16e..112f2081a 100644 --- a/mpas_analysis/shared/plot/plot_climatology_map_subtask.py +++ b/mpas_analysis/shared/plot/plot_climatology_map_subtask.py @@ -97,8 +97,11 @@ class PlotClimatologyMapSubtask(AnalysisTask): configSectionName : str the name of the section where the color map and range is defined - maskValue : float or None - a value to mask out in plots + maskMinThreshold : float or None + a value below which the field is mask out in plots + + maskMaxThreshold : float or None + a value above which the field is mask out in plots """ def __init__(self, parentTask, season, comparisonGridName, @@ -209,13 +212,15 @@ def __init__(self, parentTask, season, comparisonGridName, self.startDate = None self.endDate = None self.filePrefix = None - self.maskValue = None + self.maskMinThreshold = None + self.maskMaxThreshold = None def set_plot_info(self, outFileLabel, fieldNameInTitle, mpasFieldName, refFieldName, refTitleLabel, unitsLabel, imageCaption, galleryGroup, groupSubtitle, groupLink, galleryName, diffTitleLabel='Model - Observations', - configSectionName=None, maskValue=None): + configSectionName=None, maskMinThreshold=None, + maskMaxThreshold=None): """ Store attributes related to plots, plot file names and HTML output. @@ -264,8 +269,11 @@ def set_plot_info(self, outFileLabel, fieldNameInTitle, mpasFieldName, the name of the section where the color map and range is defined, default is the name of the task - maskValue : float or None, optional - a value to mask out in plots + maskMinThreshold : float or None, optional + a value below which the field is mask out in plots + + maskMaxThreshold : float or None, optional + a value above which the field is mask out in plots """ self.outFileLabel = outFileLabel @@ -282,7 +290,8 @@ def set_plot_info(self, outFileLabel, fieldNameInTitle, mpasFieldName, self.groupSubtitle = groupSubtitle self.groupLink = groupLink self.galleryName = galleryName - self.maskValue = maskValue + self.maskMinThreshold = maskMinThreshold + self.maskMaxThreshold = maskMaxThreshold if configSectionName is None: self.configSectionName = self.taskName @@ -486,18 +495,10 @@ def _plot_latlon(self, remappedModelClimatology, remappedRefClimatology, bias = modelOutput - refOutput - # mask with maskValue only after taking the diff - if self.maskValue is not None: - mask = np.logical_or(refOutput.mask, - refOutput == self.maskValue) - refOutput = np.ma.masked_array(refOutput, mask) - bias = np.ma.masked_array(bias, mask) - - # mask with maskValue only after taking the diff - if self.maskValue is not None: - mask = np.logical_or(modelOutput.mask, - modelOutput == self.maskValue) - modelOutput = np.ma.masked_array(modelOutput, mask) + # mask with thresholds only after taking the diff + refOutput = self._mask_with_thresholds(refOutput) + + modelOutput = self._mask_with_thresholds(modelOutput) if config.has_option(configSectionName, 'titleFontSize'): titleFontSize = config.getint(configSectionName, 'titleFontSize') @@ -574,17 +575,9 @@ def _plot_projection(self, remappedModelClimatology, bias = modelOutput - refOutput # mask with maskValue only after taking the diff - if self.maskValue is not None: - mask = np.logical_or(refOutput.mask, - refOutput == self.maskValue) - refOutput = np.ma.masked_array(refOutput, mask) - bias = np.ma.masked_array(bias, mask) - - # mask with maskValue only after taking the diff - if self.maskValue is not None: - mask = np.logical_or(modelOutput.mask, - modelOutput == self.maskValue) - modelOutput = np.ma.masked_array(modelOutput, mask) + refOutput = self._mask_with_thresholds(refOutput) + + modelOutput = self._mask_with_thresholds(modelOutput) comparisonDescriptor = get_comparison_descriptor( config, comparisonGridName) @@ -655,6 +648,18 @@ def _plot_projection(self, remappedModelClimatology, imageDescription=caption, imageCaption=caption) + def _mask_with_thresholds(self, field): + if self.maskMinThreshold is not None or \ + self.maskMaxThreshold is not None: + mask = field.mask + if self.maskMinThreshold is not None: + mask = np.logical_or(mask, field <= self.maskMinThreshold) + if self.maskMaxThreshold is not None: + mask = np.logical_or(mask, field >= self.maskMaxThreshold) + field = np.ma.masked_array(field, mask) + + return field + def _nans_to_numpy_mask(field): """