-
Notifications
You must be signed in to change notification settings - Fork 169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adapt pysteps to allow for postprocessing plugins #405
base: master
Are you sure you want to change the base?
Changes from 1 commit
3a798c1
beeee6b
21db3a3
7857661
429641d
6452ef2
1e07f8d
d94c8b3
0bd5275
560aa43
4cba436
a2b51a5
7e1759f
d9b593d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ | |
"""Methods for post-processing of forecasts.""" | ||
|
||
from . import ensemblestats | ||
from postprocessors import * | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
pysteps.postprocessing.interface | ||
==================== | ||
|
||
Interface for the postprocessing module. | ||
|
||
.. currentmodule:: pysteps.postprocessing.interface | ||
|
||
.. autosummary:: | ||
:toctree: ../generated/ | ||
|
||
get_method | ||
""" | ||
import importlib | ||
|
||
from pkg_resources import iter_entry_points | ||
|
||
from pysteps import postprocessing | ||
from pysteps.postprocessing import postprocessors | ||
from pprint import pprint | ||
|
||
_postprocessor_methods = dict() | ||
|
||
def discover_postprocessors(): | ||
""" | ||
Search for installed postprocessors plugins in the entrypoint 'pysteps.plugins.postprocessors' | ||
|
||
The postprocessors found are added to the `pysteps.postprocessing.interface_postprocessor_methods` | ||
dictionary containing the available postprocessors. | ||
""" | ||
|
||
# The pkg resources needs to be reloaded to detect new packages installed during | ||
# the execution of the python application. For example, when the plugins are | ||
# installed during the tests | ||
import pkg_resources | ||
|
||
importlib.reload(pkg_resources) | ||
|
||
for entry_point in pkg_resources.iter_entry_points(group='pysteps.plugins.postprocessors', name=None): | ||
_postprocessor = entry_point.load() | ||
|
||
postprocessor_function_name = _postprocessor.__name__ | ||
postprocessor_short_name = postprocessor_function_name.replace("postprocess_", "") | ||
|
||
_postprocess_kws = getattr(_postprocessor, "postprocess_kws", dict()) | ||
_postprocessor = postprocess_import(**_postprocess_kws)(_postprocessor) | ||
if postprocessor_short_name not in _postprocessor_methods: | ||
_postprocessor_methods[postprocessor_short_name] = _postprocessor | ||
else: | ||
RuntimeWarning( | ||
f"The postprocessor identifier '{postprocessor_short_name}' is already available in " | ||
"'pysteps.postprocessing.interface_postprocessor_methods'.\n" | ||
f"Skipping {entry_point.module_name}:{'.'.join(entry_point.attrs)}" | ||
) | ||
|
||
if hasattr(postprocessors, postprocessor_function_name): | ||
RuntimeWarning( | ||
f"The postprocessor function '{postprocessor_function_name}' is already an attribute" | ||
"of 'pysteps.postprocessing.postprocessors'.\n" | ||
f"Skipping {entry_point.module_name}:{'.'.join(entry_point.attrs)}" | ||
) | ||
else: | ||
setattr(postprocessors, postprocessor_function_name, _postprocessor) | ||
|
||
def postprocessors_info(): | ||
"""Print all the available postprocessors.""" | ||
|
||
# Postprocessors available in the 'postprocessing.postprocessors' module | ||
available_postprocessors = [ | ||
attr for attr in dir(postprocessing.postprocessors) if attr.startswith("postprocess_") | ||
] | ||
|
||
print("\nPostprocessors available in the pysteps.postprocessing.postprocessors module") | ||
pprint(available_postprocessors) | ||
|
||
# Postprocessors declared in the pysteps.postprocessing.get_method interface | ||
postprocessors_in_the_interface = [ | ||
f.__name__ for f in postprocessing.interface._postprocessor_methods.values() | ||
] | ||
|
||
print("\nPostprocessors available in the pysteps.postprocessing.get_method interface") | ||
pprint( | ||
[ | ||
(short_name, f.__name__) | ||
for short_name, f in postprocessing.interface._postprocessor_methods.items() | ||
] | ||
) | ||
|
||
# Let's use sets to find out if there are postprocessors present in the postprocessor module | ||
# but not declared in the interface, and viceversa. | ||
available_postprocessors = set(available_postprocessors) | ||
postprocessors_in_the_interface = set(postprocessors_in_the_interface) | ||
|
||
difference = available_postprocessors ^ postprocessors_in_the_interface | ||
if len(difference) > 0: | ||
print("\nIMPORTANT:") | ||
_diff = available_postprocessors - postprocessors_in_the_interface | ||
if len(_diff) > 0: | ||
print( | ||
"\nIMPORTANT:\nThe following postprocessors are available in pysteps.postprocessing.postprocessors module " | ||
"but not in the pysteps.postprocessing.get_method interface" | ||
) | ||
pprint(_diff) | ||
_diff = postprocessors_in_the_interface - available_postprocessors | ||
if len(_diff) > 0: | ||
print( | ||
"\nWARNING:\n" | ||
"The following postprocessors are available in the pysteps.postprocessing.get_method " | ||
"interface but not in the pysteps.postprocessing.postprocessors module" | ||
) | ||
pprint(_diff) | ||
|
||
return available_postprocessors, postprocessors_in_the_interface | ||
|
||
def get_method(name, method_type): | ||
""" | ||
Return a callable function for the method corresponding to the given | ||
name. | ||
|
||
Parameters | ||
---------- | ||
name: str | ||
Name of the method. The available options are:\n | ||
|
||
Postprocessors: | ||
|
||
.. tabularcolumns:: |p{2cm}|L| | ||
|
||
+-------------+-------------------------------------------------------+ | ||
| Name | Description | | ||
+=============+=======================================================+ | ||
|
||
method_type: {'postprocessor'} | ||
Type of the method (see tables above). | ||
|
||
""" | ||
|
||
if isinstance(method_type, str): | ||
method_type = method_type.lower() | ||
else: | ||
raise TypeError( | ||
"Only strings supported for for the method_type" | ||
+ " argument\n" | ||
+ "The available types are: 'postprocessor'" | ||
) from None | ||
|
||
if isinstance(name, str): | ||
name = name.lower() | ||
else: | ||
raise TypeError( | ||
"Only strings supported for the method's names.\n" | ||
+ "\nAvailable postprocessors names:" | ||
+ str(list(_postprocessor_methods.keys())) | ||
) from None | ||
|
||
if method_type == "postprocessor": | ||
methods_dict = _postprocessor_methods | ||
else: | ||
raise ValueError( | ||
"Unknown method type {}\n".format(name) | ||
+ "The available types are: 'postprocessor'" | ||
) from None | ||
|
||
try: | ||
return methods_dict[name] | ||
except KeyError: | ||
raise ValueError( | ||
"Unknown {} method {}\n".format(method_type, name) | ||
+ "The available methods are:" | ||
+ str(list(methods_dict.keys())) | ||
) from None |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just wondering if "postprocessors" is a good name? sounds a bit too vague, particularly as a submodule of "postprocessing" ... can we try to be a bit more specific? perhaps use "diagnostic" instead? what do you think? @ladc ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes good point - "diagnostic" makes sense if we also want to add other postprocessors in the future which are not purely diagnostic (such as bias correction methods). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
""" | ||
pysteps.io.postprocessors | ||
==================== | ||
|
||
Methods for applying postprocessing. | ||
|
||
The methods in this module implement the following interface:: | ||
|
||
postprocess_xxx(optional arguments) | ||
|
||
where **xxx** is the name of the postprocess to be applied. | ||
|
||
Postprocessor standardizations can be specified here if there is a desired input and output format that all should adhere to. | ||
|
||
Available Postprocessors | ||
------------------------ | ||
|
||
.. autosummary:: | ||
:toctree: ../generated/ | ||
|
||
""" | ||
|
||
import gzip | ||
import os | ||
from functools import partial | ||
|
||
import numpy as np | ||
|
||
from matplotlib.pyplot import imread | ||
|
||
from pysteps.decorators import postprocess_import | ||
from pysteps.exceptions import DataModelError | ||
from pysteps.exceptions import MissingOptionalDependency | ||
from pysteps.utils import aggregate_fields | ||
|
||
try: | ||
from osgeo import gdal, gdalconst, osr | ||
|
||
GDAL_IMPORTED = True | ||
except ImportError: | ||
GDAL_IMPORTED = False | ||
|
||
try: | ||
import h5py | ||
|
||
H5PY_IMPORTED = True | ||
except ImportError: | ||
H5PY_IMPORTED = False | ||
|
||
try: | ||
import metranet | ||
|
||
METRANET_IMPORTED = True | ||
except ImportError: | ||
METRANET_IMPORTED = False | ||
|
||
try: | ||
import netCDF4 | ||
|
||
NETCDF4_IMPORTED = True | ||
except ImportError: | ||
NETCDF4_IMPORTED = False | ||
|
||
try: | ||
from PIL import Image | ||
|
||
PIL_IMPORTED = True | ||
except ImportError: | ||
PIL_IMPORTED = False | ||
|
||
try: | ||
import pyproj | ||
|
||
PYPROJ_IMPORTED = True | ||
except ImportError: | ||
PYPROJ_IMPORTED = False | ||
|
||
try: | ||
import pygrib | ||
|
||
PYGRIB_IMPORTED = True | ||
except ImportError: | ||
PYGRIB_IMPORTED = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.