Skip to content

Commit

Permalink
Add Contour and Filled Contour Plotting Capabilities (JCSDA-internal#216
Browse files Browse the repository at this point in the history
)

## Description

This PR adds the ability to plot contour and filled contour plots from
EMCPy's plotting capabilities. The appropriate driver scripts and
configuration yamls have been created.

## Dependencies
None.

## Impact

Please list the other repositories (if any) that this PR will require
changes in (example below):

None.
  • Loading branch information
kevindougherty-noaa authored Feb 10, 2025
1 parent 474199a commit f2e3242
Show file tree
Hide file tree
Showing 9 changed files with 451 additions and 0 deletions.
99 changes: 99 additions & 0 deletions src/eva/plotting/batch/base/diagnostics/contour_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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 numpy as np

from abc import ABC, abstractmethod

# --------------------------------------------------------------------------------------------------


class ContourPlot(ABC):

"""Base class for creating Contour plots."""

def __init__(self, config, logger, dataobj):

"""
Creates a Contour plot abstract class based on the provided configuration.
Args:
config (dict): A dictionary containing the configuration for the contour plot on a map.
logger (Logger): An instance of the logger for logging messages.
dataobj: An instance of the data object containing input data.
Example:
::
config = {
"x": {"variable": "collection::group::variable"},
"y": {"variable": "collection::group::variable"},
"z": {"variable": "collection::group::variable"},
"plot_property": "property_value",
"plot_option": "option_value",
"schema": "path_to_schema_file.yaml"
}
logger = Logger()
contour_plot = ContourPlot(config, logger, None)
"""

self.config = config
self.logger = logger
self.dataobj = dataobj
self.xdata = []
self.ydata = []
self.zdata = []
self.plotobj = None

# --------------------------------------------------------------------------------------------------

def data_prep(self):
""" Preparing data for configure_plot """

# Get the data to plot from the data_collection
# ---------------------------------------------
var0 = self.config['x']['variable']
var1 = self.config['y']['variable']
var2 = self.config['z']['variable']

var0_cgv = var0.split('::')
var1_cgv = var1.split('::')
var2_cgv = var2.split('::')

if len(var0_cgv) != 3:
self.logger.abort('Contour: 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('Contour: comparison second var \'var1\' does not appear to ' +
'be in the required format of collection::group::variable.')
if len(var2_cgv) != 3:
self.logger.abort('Contour: comparison second var \'var2\' 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)
ydata = self.dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], var1_cgv[2], channel)
zdata = self.dataobj.get_variable_data(var2_cgv[0], var2_cgv[1], var2_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)
zdata = slice_var_from_str(self.config['z'], zdata, self.logger)

# contour data should be flattened
xdata = xdata.flatten()
ydata = ydata.flatten()
zdata = zdata.flatten()

@abstractmethod
def configure_plot(self):
""" Virtual method for configuring plot based on selected backend """
pass

# --------------------------------------------------------------------------------------------------
100 changes: 100 additions & 0 deletions src/eva/plotting/batch/base/diagnostics/filled_contour_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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 numpy as np

from abc import ABC, abstractmethod

# --------------------------------------------------------------------------------------------------


class FilledContourPlot(ABC):

"""Base class for creating Filled Contour plots."""

def __init__(self, config, logger, dataobj):

"""
Creates a Filled Contour plot abstract class based on the provided configuration.
Args:
config (dict): A dictionary containing the configuration for the filled contour
plot on a map.
logger (Logger): An instance of the logger for logging messages.
dataobj: An instance of the data object containing input data.
Example:
::
config = {
"x": {"variable": "collection::group::variable"},
"y": {"variable": "collection::group::variable"},
"z": {"variable": "collection::group::variable"},
"plot_property": "property_value",
"plot_option": "option_value",
"schema": "path_to_schema_file.yaml"
}
logger = Logger()
filled_contour_plot = FilledContourPlot(config, logger, None)
"""

self.config = config
self.logger = logger
self.dataobj = dataobj
self.xdata = []
self.ydata = []
self.zdata = []
self.plotobj = None

# --------------------------------------------------------------------------------------------------

def data_prep(self):
""" Preparing data for configure_plot """

# Get the data to plot from the data_collection
# ---------------------------------------------
var0 = self.config['x']['variable']
var1 = self.config['y']['variable']
var2 = self.config['z']['variable']

var0_cgv = var0.split('::')
var1_cgv = var1.split('::')
var2_cgv = var2.split('::')

if len(var0_cgv) != 3:
self.logger.abort('Contour: 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('Contour: comparison second var \'var1\' does not appear to ' +
'be in the required format of collection::group::variable.')
if len(var2_cgv) != 3:
self.logger.abort('Contour: comparison third var \'var2\' 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)
ydata = self.dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], var1_cgv[2], channel)
zdata = self.dataobj.get_variable_data(var2_cgv[0], var2_cgv[1], var2_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)
zdata = slice_var_from_str(self.config['z'], zdata, self.logger)

# contour data should be flattened
xdata = xdata.flatten()
ydata = ydata.flatten()
zdata = zdata.flatten()

@abstractmethod
def configure_plot(self):
""" Virtual method for configuring plot based on selected backend """
pass

# --------------------------------------------------------------------------------------------------
75 changes: 75 additions & 0 deletions src/eva/plotting/batch/base/diagnostics/map_contour.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from eva.eva_path import return_eva_path
from eva.utilities.utils import get_schema, update_object, slice_var_from_str

from abc import ABC, abstractmethod

# --------------------------------------------------------------------------------------------------


class MapContour(ABC):

"""Base class for creating map contour plots."""

def __init__(self, config, logger, dataobj):

"""
Creates a contour map plot abstract class based on the provided configuration.
Args:
config (dict): A dictionary containing the configuration for the contour map plot.
logger (Logger): An instance of the logger for logging messages.
dataobj: An instance of the data object containing input data.
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 = MapContour(config, logger, None)
"""
self.collection = None
self.datavar_name = None

self.config = config
self.logger = logger
self.dataobj = dataobj
self.lonvar = []
self.latvar = []
self.datavar = []
self.plotobj = None

# --------------------------------------------------------------------------------------------------

def data_prep(self):
""" Preparing data for configure_plot """

# prepare data based on config
lonvar_cgv = self.config['longitude']['variable'].split('::')
self.collection = lonvar_cgv[0]
self.lonvar = self.dataobj.get_variable_data(lonvar_cgv[0], lonvar_cgv[1],
lonvar_cgv[2], None)
self.lonvar = slice_var_from_str(self.config['longitude'], self.lonvar, self.logger)
latvar_cgv = self.config['latitude']['variable'].split('::')
self.latvar = self.dataobj.get_variable_data(latvar_cgv[0], latvar_cgv[1],
latvar_cgv[2], None)
self.latvar = slice_var_from_str(self.config['latitude'], self.latvar, self.logger)
datavar_cgv = self.config['data']['variable'].split('::')
self.datavar_name = datavar_cgv[1] + '::' + datavar_cgv[2]
self.datavar = self.dataobj.get_variable_data(datavar_cgv[0], datavar_cgv[1],
datavar_cgv[2], None)
self.datavar = slice_var_from_str(self.config['data'], self.datavar, self.logger)

# --------------------------------------------------------------------------------------------------

@abstractmethod
def configure_plot(self):
""" Virtual method for configuring plot based on selected backend """
pass
9 changes: 9 additions & 0 deletions src/eva/plotting/batch/emcpy/defaults/contour_plot.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Contour default yaml
corner_mask: False
colors: 'black'
alpha:
cmap:
norm:
vmin:
vmax:
levels:
13 changes: 13 additions & 0 deletions src/eva/plotting/batch/emcpy/defaults/filled_contour_plot.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Filled Contour default yaml
corner_mask: False
colors:
alpha:
cmap: 'viridis'
norm:
vmin:
vmax:
origin:
extent:
locator:
extend:
colorbar: True
11 changes: 11 additions & 0 deletions src/eva/plotting/batch/emcpy/defaults/map_contour.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# MapContour class default schema
levels:
clabel: False
colors: 'black'
linewidths: 1.5
linestyles: '-'
cmap:
vmin:
vmax:
alpha:
colorbar: False
50 changes: 50 additions & 0 deletions src/eva/plotting/batch/emcpy/diagnostics/emcpy_contour_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from eva.eva_path import return_eva_path
from eva.utilities.config import get
from eva.utilities.utils import get_schema, update_object
import emcpy.plots.plots
import os

from eva.plotting.batch.base.diagnostics.contour_plot import ContourPlot

# --------------------------------------------------------------------------------------------------


class EmcpyContourPlot(ContourPlot):

"""
EmcpyContourPlot class is a subclass of the ContourPlot class, designed for configuring
and plotting contour plot visualizations using the emcpy library.
Attributes:
Inherits attributes from the ContourPlot class.
Methods:
configure_plot(): Configures the plotting settings for the contour plot.
"""

def configure_plot(self):

"""
Configures the plotting settings for the contour plot.
Returns:
plotobj: The configured plot object for emcpy contour plots.
"""

# Create declarative plotting ContourPlot object
# -------------------------------------------
self.plotobj = emcpy.plots.plots.ContourPlot(self.xdata, self.ydata, self.z)

# Get defaults from schema
# ------------------------
layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting',
'batch', 'emcpy', 'defaults', 'contour_plot.yaml'))
new_config = get_schema(layer_schema, self.config, self.logger)
delvars = ['x', 'y', 'z', 'type', 'schema']
for d in delvars:
new_config.pop(d, None)
self.plotobj = update_object(self.plotobj, new_config, self.logger)

return self.plotobj

# --------------------------------------------------------------------------------------------------
Loading

0 comments on commit f2e3242

Please sign in to comment.