pyVPRM 5.0__tar.gz → 5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyvprm-5.0/pyVPRM.egg-info → pyvprm-5.2}/PKG-INFO +13 -2
- {pyvprm-5.0 → pyvprm-5.2}/README.md +33 -7
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/VPRM.py +122 -72
- pyvprm-5.2/pyVPRM/__init__.py +3 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/lib/flux_tower_class.py +41 -14
- pyvprm-5.2/pyVPRM/lib/functions.py +495 -0
- pyvprm-5.2/pyVPRM/meteorologies/era5_land_destinE.py +200 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/base_manager.py +13 -8
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/modis.py +38 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/sentinel2.py +12 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/viirs.py +18 -12
- pyvprm-5.0/pyVPRM/vprm_models/vprm_base.py → pyvprm-5.2/pyVPRM/vprm_models/vprm_base_model.py +11 -8
- pyvprm-5.0/pyVPRM/vprm_models/vprm_base_no_xeric.py → pyvprm-5.2/pyVPRM/vprm_models/vprm_base_model_no_xeric.py +6 -6
- pyvprm-5.0/pyVPRM/vprm_models/vprm_modified.py → pyvprm-5.2/pyVPRM/vprm_models/vprm_modified_model.py +7 -7
- pyvprm-5.2/pyVPRM/vprm_models/vprm_nn_model.py +149 -0
- {pyvprm-5.0 → pyvprm-5.2/pyVPRM.egg-info}/PKG-INFO +13 -2
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM.egg-info/SOURCES.txt +5 -4
- {pyvprm-5.0 → pyvprm-5.2}/setup.py +2 -1
- pyvprm-5.0/pyVPRM/__init__.py +0 -1
- pyvprm-5.0/pyVPRM/lib/functions.py +0 -471
- pyvprm-5.0/pyVPRM/vprm_models/vprm_nn.py +0 -130
- {pyvprm-5.0 → pyvprm-5.2}/LICENSE +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/MANIFEST.in +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/lib/__init__.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/lib/downmodis.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/lib/fancy_plot.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/lib/fluxnet_info/fluxnet_sites.pkl +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/lib/fluxnet_info/site_infos.txt +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/lib/reproject_fiona.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/meteorologies/__init__.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/meteorologies/era5_class_dkrz.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/meteorologies/era5_class_draft.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/meteorologies/era5_monthly_xr.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/meteorologies/met_base_class.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/meteorologies/met_local_measurement.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/__init__.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/city.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/copernicus.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/esa_world_cover.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/mapbiomas.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/proba_v.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/synmap.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/sat_managers/viirs09ga.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/vprm_configs/__init__.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/vprm_configs/copernicus_land_cover.yaml +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/vprm_configs/esa_world_cover.yaml +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/vprm_configs/synmap.yaml +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/vprm_models/__init__.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM/vprm_models/model_params/__init__.py +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM.egg-info/dependency_links.txt +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM.egg-info/not-zip-safe +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM.egg-info/requires.txt +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/pyVPRM.egg-info/top_level.txt +0 -0
- {pyvprm-5.0 → pyvprm-5.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyVPRM
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.2
|
|
4
4
|
Summary: Vegetation Photosynthesis and Respiration Model
|
|
5
5
|
Home-page: https://github.com/tglauch/pyVPRM/
|
|
6
6
|
Author: Theo Glauch
|
|
@@ -12,6 +12,7 @@ Classifier: Environment :: Console
|
|
|
12
12
|
Classifier: Intended Audience :: Science/Research
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
16
|
License-File: LICENSE
|
|
16
17
|
Requires-Dist: numpy
|
|
17
18
|
Requires-Dist: scipy
|
|
@@ -37,5 +38,15 @@ Requires-Dist: requests
|
|
|
37
38
|
Requires-Dist: statsmodels
|
|
38
39
|
Requires-Dist: loguru
|
|
39
40
|
Requires-Dist: fiona
|
|
41
|
+
Dynamic: author
|
|
42
|
+
Dynamic: author-email
|
|
43
|
+
Dynamic: classifier
|
|
44
|
+
Dynamic: description
|
|
45
|
+
Dynamic: home-page
|
|
46
|
+
Dynamic: keywords
|
|
47
|
+
Dynamic: license
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
Dynamic: requires-dist
|
|
50
|
+
Dynamic: summary
|
|
40
51
|
|
|
41
52
|
A tool to calculate the CO2 exchange flux between atmosphere and terrestrial biosphere using the Vegetation Photosynthesis and Respiration Model
|
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
[](https://doi.org/10.5281/zenodo.14216613)
|
|
2
|
+
[](https://pypi.org/project/pyVPRM/)
|
|
3
|
+
[](https://pypi.org/project/pyVPRM/)
|
|
4
|
+
[](https://pypi.org/project/pyVPRM/)
|
|
5
|
+

|
|
2
6
|
|
|
3
7
|
|
|
8
|
+
<figure>
|
|
9
|
+
<img src="https://github.com/tglauch/pyVPRM/assets/29706254/ba2565e6-1434-4a95-8086-936462f8d05d", height=150pt>
|
|
10
|
+
</figure>
|
|
11
|
+
|
|
12
|
+
A description of the model is published in Geoscientific Model Development (GMD) https://gmd.copernicus.org/articles/18/4713/2025/. If you use this package for you scientific work please cite as follows
|
|
4
13
|
|
|
5
|
-
|
|
14
|
+
```
|
|
15
|
+
Glauch, T., Marshall, J., Gerbig, C., Botía, S., Gałkowski, M., Vardag, S. N., & Butz, A. (2025). pyVPRM: A next-generation vegetation photosynthesis and respiration model for the post-MODIS era. Geoscientific Model Development, 18(14), 4713–4742. https://doi.org/10.5194/gmd-18-4713-2025
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
or using BibTeX:
|
|
19
|
+
```
|
|
20
|
+
@Article{gmd-18-4713-2025,
|
|
21
|
+
AUTHOR = {Glauch, T. and Marshall, J. and Gerbig, C. and Bot\'{\i}a, S. and Ga{\l}kowski, M. and Vardag, S. N. and Butz, A.},
|
|
22
|
+
TITLE = {\textit{pyVPRM}: a next-generation vegetation photosynthesis and respiration model for the post-MODIS era},
|
|
23
|
+
JOURNAL = {Geoscientific Model Development},
|
|
24
|
+
VOLUME = {18},
|
|
25
|
+
YEAR = {2025},
|
|
26
|
+
NUMBER = {14},
|
|
27
|
+
PAGES = {4713--4742},
|
|
28
|
+
URL = {https://gmd.copernicus.org/articles/18/4713/2025/},
|
|
29
|
+
DOI = {10.5194/gmd-18-4713-2025}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
In case of any questions please write an E-Mail to theo.glauch@dlr.de.
|
|
6
34
|
|
|
7
35
|
# About
|
|
8
36
|
|
|
9
37
|
|
|
10
|
-
`pyVPRM` is a software package to calculate the CO2 exchange flux between atmosphere and terrestrial biosphere using the Vegetation Photosynthesis and Respriation Model (VPRM). It takes into account both, the primary productivity (GPP) as well as the respiration. The net flux between both of them is the net ecosystem exchange (NEE). The implementation is flexible and can be run with different satellite products (Sentinel-2, MODIS, VIIRS,...), land cover products (Copernicus Land Cover Service, ESA 10-m World Cover Map, MapBiomas) and
|
|
38
|
+
`pyVPRM` is a software package to calculate the CO2 exchange flux between atmosphere and terrestrial biosphere using the Vegetation Photosynthesis and Respriation Model (VPRM). It takes into account both, the primary productivity (GPP) as well as the respiration. The net flux between both of them is the net ecosystem exchange (NEE). The implementation is flexible and can be run with different satellite products (Sentinel-2, MODIS, VIIRS,...), land cover products (Copernicus Land Cover Service, ESA 10-m World Cover Map, MapBiomas) and meteorological drivers like ECMWFS ERA5 Reanalysis. Through its modular structure it is also easily extendable.
|
|
11
39
|
|
|
12
40
|
Among others it can be used for
|
|
13
41
|
|
|
@@ -15,16 +43,14 @@ Among others it can be used for
|
|
|
15
43
|
2. Making CO2 flux predictions for a given region of interest
|
|
16
44
|
3. Generating input files to run VPRM in the Weather Research and Forecasting Model (WRF)
|
|
17
45
|
|
|
18
|
-
In case of any questions please write an E-Mail to theo.glauch@dlr.de.
|
|
19
|
-
|
|
20
46
|
## Example Net Ecosystem Fluxes generated with pyVPRM
|
|
21
47
|
|
|
22
48
|
<figure>
|
|
23
|
-
<img src="https://github.com/user-attachments/assets/
|
|
49
|
+
<img src="https://github.com/user-attachments/assets/c099ef1f-6c5a-445c-bc6e-f6697c47b641", height=300pt>
|
|
24
50
|
</figure>
|
|
25
51
|
|
|
26
52
|
<figure>
|
|
27
|
-
<img src="https://github.com/user-attachments/assets/
|
|
53
|
+
<img src="https://github.com/user-attachments/assets/26913805-f188-477a-9a85-08911a165b1e", height=300pt>
|
|
28
54
|
</figure>
|
|
29
55
|
|
|
30
56
|
|
|
@@ -63,7 +89,7 @@ In order to start your own project you need to at least follow theses steps:
|
|
|
63
89
|
Remarks:
|
|
64
90
|
- If there is not yet an interface for your satellite data or land cover map, implement a new subclass in `pyVPRM/sat_managers/`
|
|
65
91
|
- For new land cover maps you need to additionaly provide a mapping of the land cover classes to the VPRM classes in a config file which is stored in `pyVPRM/vprm_configs`
|
|
66
|
-
- Open access to land cover maps: Copernicus: https://
|
|
92
|
+
- Open access to land cover maps: Copernicus: https://land.copernicus.eu/en/products/global-dynamic-land-cover/copernicus-global-land-service-land-cover-100m-collection-3-epoch-2019-globe#download | ESA World Cover: https://viewer.esa-worldcover.org
|
|
67
93
|
- Open access to satellite data: MODIS,VIIRS: https://e4ftl01.cr.usgs.gov | Sentinel-2: https://scihub.copernicus.eu/
|
|
68
94
|
|
|
69
95
|
## Examples
|
|
@@ -12,6 +12,7 @@ from pyVPRM.lib.functions import (
|
|
|
12
12
|
do_lowess_smoothing,
|
|
13
13
|
make_xesmf_grid,
|
|
14
14
|
to_esmf_grid,
|
|
15
|
+
replace_inf_runs_ignore_nans
|
|
15
16
|
)
|
|
16
17
|
from scipy.ndimage import uniform_filter
|
|
17
18
|
from pyproj import Transformer
|
|
@@ -36,13 +37,13 @@ regridder_options = dict()
|
|
|
36
37
|
regridder_options["conservative"] = "conserve"
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
class
|
|
40
|
+
class vprm_preprocessor:
|
|
40
41
|
"""
|
|
41
42
|
Main class for the Vegetation Photosynthesis and Respiration Model
|
|
42
43
|
"""
|
|
43
44
|
|
|
44
45
|
def __init__(
|
|
45
|
-
self, vprm_config_path, land_cover_map=None, verbose=False, n_cpus=1,
|
|
46
|
+
self, vprm_config_path, land_cover_map=None, verbose=False, n_cpus=1, flux_tower_instances=None
|
|
46
47
|
):
|
|
47
48
|
"""
|
|
48
49
|
Initialize a class instance
|
|
@@ -51,7 +52,7 @@ class vprm:
|
|
|
51
52
|
land_cover_map (xarray): A pre calculated map with the land cover types
|
|
52
53
|
verbose (bool): Set true for additional output when debugging
|
|
53
54
|
n_cpus: Number of CPUs
|
|
54
|
-
|
|
55
|
+
flux_tower_instances: For fitting. Provide a list of flux_tower_instances.
|
|
55
56
|
|
|
56
57
|
Returns:
|
|
57
58
|
The lowess smoothed array
|
|
@@ -60,55 +61,57 @@ class vprm:
|
|
|
60
61
|
logger.info("Running with pyVPRM version {}".format(pyVPRM.__version__))
|
|
61
62
|
self.sat_imgs = []
|
|
62
63
|
|
|
63
|
-
self.
|
|
64
|
-
if self.sites is not None:
|
|
65
|
-
self.lonlats = [i.get_lonlat() for i in sites]
|
|
64
|
+
self.set_flux_tower_inst(flux_tower_instances)
|
|
66
65
|
self.n_cpus = n_cpus
|
|
67
66
|
self.counter = 0
|
|
68
67
|
self.fit_params_dict = None
|
|
69
|
-
self.res = None
|
|
70
68
|
|
|
71
69
|
self.new = True
|
|
72
70
|
self.timestamps = []
|
|
73
71
|
self.t2m = None
|
|
74
72
|
|
|
75
73
|
# self.target_shape = None
|
|
76
|
-
|
|
77
|
-
self.sat_img_buffer = dict()
|
|
74
|
+
|
|
78
75
|
self.buffer = dict()
|
|
79
76
|
self.buffer["cur_lat"] = None
|
|
80
77
|
self.buffer["cur_lon"] = None
|
|
81
|
-
self.
|
|
78
|
+
self.empty_xr_lat_lon_grid = None
|
|
82
79
|
|
|
83
80
|
self.land_cover_type = land_cover_map
|
|
84
81
|
# land_cover_type: tmin, topt, tmax
|
|
85
82
|
|
|
86
83
|
with open(vprm_config_path, "r") as stream:
|
|
87
84
|
try:
|
|
88
|
-
|
|
85
|
+
self.vprm_cfg = yaml.safe_load(stream)
|
|
89
86
|
except yaml.YAMLError as exc:
|
|
90
87
|
logger.info(exc)
|
|
91
88
|
|
|
92
89
|
self.temp_coefficients = dict()
|
|
93
90
|
self.map_to_vprm_class = dict()
|
|
94
|
-
for key in
|
|
95
|
-
if "tmin" in
|
|
96
|
-
self.temp_coefficients[
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
for key in self.vprm_cfg:
|
|
92
|
+
if "tmin" in self.vprm_cfg[key].keys():
|
|
93
|
+
self.temp_coefficients[self.vprm_cfg[key]["vprm_class"]] = [
|
|
94
|
+
self.vprm_cfg[key]["tmin"],
|
|
95
|
+
self.vprm_cfg[key]["topt"],
|
|
96
|
+
self.vprm_cfg[key]["tmax"],
|
|
97
|
+
self.vprm_cfg[key]["tlow"],
|
|
101
98
|
]
|
|
102
|
-
for c in
|
|
103
|
-
self.map_to_vprm_class[c] =
|
|
99
|
+
for c in self.vprm_cfg[key]["class_numbers"]:
|
|
100
|
+
self.map_to_vprm_class[c] = self.vprm_cfg[key]["vprm_class"]
|
|
104
101
|
return
|
|
105
102
|
|
|
103
|
+
def set_flux_tower_inst(self, flux_tower_instances):
|
|
104
|
+
self.flux_tower_instances = flux_tower_instances
|
|
105
|
+
if self.flux_tower_instances is not None:
|
|
106
|
+
self.lonlats = [i.get_lonlat() for i in flux_tower_instances]
|
|
107
|
+
return
|
|
108
|
+
|
|
106
109
|
def to_wrf_output(
|
|
107
110
|
self,
|
|
108
111
|
out_grid,
|
|
109
112
|
weights_for_regridder=None,
|
|
110
113
|
regridder_save_path=None,
|
|
111
|
-
driver="
|
|
114
|
+
driver="xESMF",
|
|
112
115
|
interp_method="conservative",
|
|
113
116
|
n_cpus=None,
|
|
114
117
|
mpi=True,
|
|
@@ -144,7 +147,7 @@ class vprm:
|
|
|
144
147
|
logger.info(
|
|
145
148
|
"Need to generate the weights for the regridder. This can be very slow and memory intensive"
|
|
146
149
|
)
|
|
147
|
-
if driver == "
|
|
150
|
+
if driver == "xESMF":
|
|
148
151
|
regridder = xe.Regridder(src_grid, ds_out, interp_method)
|
|
149
152
|
if regridder_save_path is not None:
|
|
150
153
|
regridder.to_netcdf(regridder_save_path)
|
|
@@ -182,7 +185,7 @@ class vprm:
|
|
|
182
185
|
# os.remove(dest_temp_path)
|
|
183
186
|
weights_for_regridder = regridder_save_path
|
|
184
187
|
else:
|
|
185
|
-
logger.info("Driver needs to be
|
|
188
|
+
logger.info("Driver needs to be xESMF or ESMF_RegridWeightGen")
|
|
186
189
|
if weights_for_regridder is not None:
|
|
187
190
|
regridder = xe.Regridder(
|
|
188
191
|
src_grid,
|
|
@@ -277,7 +280,12 @@ class vprm:
|
|
|
277
280
|
"evi_max": ds_t_max_evi,
|
|
278
281
|
"evi_min": ds_t_min_evi,
|
|
279
282
|
}
|
|
280
|
-
|
|
283
|
+
|
|
284
|
+
veg_classes_def = 'VPRM class definition: '
|
|
285
|
+
for k in self.vprm_cfg.keys():
|
|
286
|
+
veg_classes_def += '{} {}, '.format(self.vprm_cfg[k]['vprm_class'], k)
|
|
287
|
+
veg_classes_def = veg_classes_def[:-2]
|
|
288
|
+
|
|
281
289
|
for key in ret_dict.keys():
|
|
282
290
|
ret_dict[key] = ret_dict[key].assign_attrs(
|
|
283
291
|
title="VPRM input data for WRF: {}".format(key),
|
|
@@ -289,7 +297,7 @@ class vprm:
|
|
|
289
297
|
institution2="Deutsches Zentrum für Luft- und Raumfahrt (DLR)",
|
|
290
298
|
contact="theo.glauch@dlr.de",
|
|
291
299
|
date_created=str(datetime.now()),
|
|
292
|
-
comment=
|
|
300
|
+
comment=veg_classes_def,
|
|
293
301
|
)
|
|
294
302
|
return ret_dict
|
|
295
303
|
|
|
@@ -302,10 +310,12 @@ class vprm:
|
|
|
302
310
|
b_swir=None,
|
|
303
311
|
drop_bands=False,
|
|
304
312
|
which_evi=None,
|
|
313
|
+
add_ndvi=False,
|
|
305
314
|
timestamp_key=None,
|
|
306
315
|
mask_bad_pixels=True,
|
|
307
316
|
mask_clouds=True,
|
|
308
317
|
mask_snow=True,
|
|
318
|
+
mask_water=True,
|
|
309
319
|
):
|
|
310
320
|
"""
|
|
311
321
|
Add a new satellite image and calculate EVI and LSWI if desired
|
|
@@ -375,12 +385,20 @@ class vprm:
|
|
|
375
385
|
temp_lswi = xr.where((temp_lswi < -1) | (temp_lswi > 1), np.nan, temp_lswi)
|
|
376
386
|
handler.sat_img["evi"] = temp_evi
|
|
377
387
|
handler.sat_img["lswi"] = temp_lswi
|
|
388
|
+
if add_ndvi:
|
|
389
|
+
nir = handler.sat_img[b_nir]
|
|
390
|
+
red = handler.sat_img[b_red]
|
|
391
|
+
temp_ndvi = (nir-red)/(nir+red)
|
|
392
|
+
handler.sat_img['ndvi'] = temp_ndvi
|
|
393
|
+
|
|
378
394
|
if timestamp_key is not None:
|
|
379
395
|
handler.sat_img = handler.sat_img.rename({timestamp_key: "timestamps"})
|
|
380
396
|
|
|
381
397
|
bands_to_mask = []
|
|
382
398
|
if which_evi in ["evi", "evi2"]:
|
|
383
399
|
bands_to_mask = ["evi", "lswi"]
|
|
400
|
+
if add_ndvi:
|
|
401
|
+
bands_to_mask.append('ndvi')
|
|
384
402
|
else:
|
|
385
403
|
for btm in [b_nir, b_red, b_blue, b_swir]:
|
|
386
404
|
if btm is not None:
|
|
@@ -390,6 +408,11 @@ class vprm:
|
|
|
390
408
|
handler.mask_snow()
|
|
391
409
|
else:
|
|
392
410
|
handler.mask_snow(bands_to_mask)
|
|
411
|
+
if mask_water:
|
|
412
|
+
if bands_to_mask == []:
|
|
413
|
+
handler.mask_water()
|
|
414
|
+
else:
|
|
415
|
+
handler.mask_water(bands_to_mask)
|
|
393
416
|
if mask_clouds:
|
|
394
417
|
if bands_to_mask == []:
|
|
395
418
|
handler.mask_clouds()
|
|
@@ -443,6 +466,7 @@ class vprm:
|
|
|
443
466
|
x, y = t.transform(ll[0], ll[1])
|
|
444
467
|
x_ind = np.argmin(np.abs(x - xs))
|
|
445
468
|
y_ind = np.argmin(np.abs(y - ys))
|
|
469
|
+
arsz = 3 # Hard Coded. Means only 3 by 3 pixes around the lonlat location are smoothed
|
|
446
470
|
for key in keys:
|
|
447
471
|
logger.info(key)
|
|
448
472
|
self.sat_imgs.sat_img[key][
|
|
@@ -464,20 +488,21 @@ class vprm:
|
|
|
464
488
|
interp_method="nearest",
|
|
465
489
|
)
|
|
466
490
|
self.sat_imgs.sat_img = self.sat_imgs.sat_img.assign_coords(
|
|
467
|
-
{"site_names": [i.get_site_name() for i in self.
|
|
491
|
+
{"site_names": [i.get_site_name() for i in self.flux_tower_instances]}
|
|
468
492
|
)
|
|
469
493
|
|
|
470
|
-
def sort_and_merge_by_timestamp(self):
|
|
494
|
+
def sort_and_merge_by_timestamp(self, min_lenght_snow_period=21):
|
|
471
495
|
"""
|
|
472
496
|
Called after adding the satellite images with 'add_sat_img'. Sorts the satellite
|
|
473
497
|
images by timestamp and merges everything to one satellite_data_manager.
|
|
474
498
|
|
|
475
499
|
Parameters:
|
|
476
|
-
|
|
500
|
+
min_lenght_snow_period defines how long a period should be until gaps are
|
|
501
|
+
filled to stabilize lowess fits
|
|
477
502
|
Returns:
|
|
478
503
|
None
|
|
479
504
|
"""
|
|
480
|
-
if self.
|
|
505
|
+
if self.flux_tower_instances is None:
|
|
481
506
|
x_time_y = 0
|
|
482
507
|
for h in self.sat_imgs:
|
|
483
508
|
size_dict = dict(h.sat_img.sizes)
|
|
@@ -485,15 +510,15 @@ class vprm:
|
|
|
485
510
|
if prod > x_time_y:
|
|
486
511
|
biggest = h
|
|
487
512
|
x_time_y = prod
|
|
488
|
-
self.
|
|
489
|
-
keys = list(self.
|
|
490
|
-
self.
|
|
513
|
+
self.prototype_satellite_manager = copy.deepcopy(biggest)
|
|
514
|
+
keys = list(self.prototype_satellite_manager.sat_img.keys())
|
|
515
|
+
self.prototype_satellite_manager.sat_img = self.prototype_satellite_manager.sat_img.drop(keys)
|
|
491
516
|
# for h in self.sat_imgs:
|
|
492
|
-
# h.sat_img = h.sat_img.rio.reproject_match(self.
|
|
517
|
+
# h.sat_img = h.sat_img.rio.reproject_match(self.prototype_satellite_manager.sat_img, nodata=np.nan)
|
|
493
518
|
else:
|
|
494
|
-
self.
|
|
495
|
-
keys = list(self.
|
|
496
|
-
self.
|
|
519
|
+
self.prototype_satellite_manager = copy.deepcopy(self.sat_imgs[0])
|
|
520
|
+
keys = list(self.prototype_satellite_manager.sat_img.keys())
|
|
521
|
+
self.prototype_satellite_manager.sat_img = self.prototype_satellite_manager.sat_img.drop(keys)
|
|
497
522
|
self.sat_imgs = satellite_data_manager(
|
|
498
523
|
sat_img=xr.concat([k.sat_img for k in self.sat_imgs], "time")
|
|
499
524
|
)
|
|
@@ -510,7 +535,7 @@ class vprm:
|
|
|
510
535
|
)
|
|
511
536
|
day_steps = [i.days for i in (self.timestamps - self.timestamp_start)]
|
|
512
537
|
self.sat_imgs.sat_img = self.sat_imgs.sat_img.assign_coords({"time": day_steps})
|
|
513
|
-
self.
|
|
538
|
+
self.prototype_satellite_manager.sat_img = self.prototype_satellite_manager.sat_img.assign_coords(
|
|
514
539
|
{"time": day_steps}
|
|
515
540
|
)
|
|
516
541
|
|
|
@@ -531,19 +556,33 @@ class vprm:
|
|
|
531
556
|
{"timestamps": (dims, tismp)}
|
|
532
557
|
)
|
|
533
558
|
self.time_key = "time"
|
|
559
|
+
|
|
560
|
+
N = int(min_lenght_snow_period / np.diff(self.sat_imgs.sat_img['time']).mean())
|
|
534
561
|
if "evi" in list(self.sat_imgs.sat_img.data_vars):
|
|
535
|
-
self.sat_imgs.sat_img["evi"] =
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
)
|
|
562
|
+
self.sat_imgs.sat_img["evi"] = replace_inf_runs_ignore_nans(self.sat_imgs.sat_img["evi"],
|
|
563
|
+
N = N,
|
|
564
|
+
time_dim = self.time_key)
|
|
565
|
+
# self.sat_imgs.sat_img["evi"] = xr.where(
|
|
566
|
+
# (self.sat_imgs.sat_img["evi"] == np.inf),
|
|
567
|
+
# self.sat_imgs.sat_img["evi"].min(dim=self.time_key),
|
|
568
|
+
# self.sat_imgs.sat_img["evi"],
|
|
569
|
+
# )
|
|
540
570
|
|
|
541
571
|
if "lswi" in list(self.sat_imgs.sat_img.data_vars):
|
|
542
|
-
self.sat_imgs.sat_img["lswi"] =
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
)
|
|
572
|
+
self.sat_imgs.sat_img["lswi"] = replace_inf_runs_ignore_nans(self.sat_imgs.sat_img["lswi"],
|
|
573
|
+
N = N,
|
|
574
|
+
time_dim = self.time_key)
|
|
575
|
+
# self.sat_imgs.sat_img["lswi"] = xr.where(
|
|
576
|
+
# (self.sat_imgs.sat_img["lswi"] == np.inf),
|
|
577
|
+
# self.sat_imgs.sat_img["lswi"].min(dim=self.time_key),
|
|
578
|
+
# self.sat_imgs.sat_img["lswi"],
|
|
579
|
+
# )
|
|
580
|
+
|
|
581
|
+
if "ndvi" in list(self.sat_imgs.sat_img.data_vars):
|
|
582
|
+
self.sat_imgs.sat_img["ndvi"] = replace_inf_runs_ignore_nans(self.sat_imgs.sat_img["ndvi"],
|
|
583
|
+
N = N,
|
|
584
|
+
time_dim = self.time_key)
|
|
585
|
+
|
|
547
586
|
return
|
|
548
587
|
|
|
549
588
|
def clip_to_box(self, sat_to_crop):
|
|
@@ -552,7 +591,7 @@ class vprm:
|
|
|
552
591
|
bounds[0], bounds[1], bounds[2], bounds[3]
|
|
553
592
|
)
|
|
554
593
|
keys = list(self.sat_imgs.sat_img.keys())
|
|
555
|
-
self.
|
|
594
|
+
self.prototype_satellite_manager = satellite_data_manager(
|
|
556
595
|
sat_img=self.sat_imgs.sat_img.drop(keys)
|
|
557
596
|
)
|
|
558
597
|
return
|
|
@@ -708,7 +747,7 @@ class vprm:
|
|
|
708
747
|
.to_array()
|
|
709
748
|
.values[0]
|
|
710
749
|
)
|
|
711
|
-
self.land_cover_type = copy.deepcopy(self.
|
|
750
|
+
self.land_cover_type = copy.deepcopy(self.prototype_satellite_manager)
|
|
712
751
|
self.land_cover_type.sat_img = self.land_cover_type.sat_img.assign(
|
|
713
752
|
{var_name: (["y", "x"], t)}
|
|
714
753
|
)
|
|
@@ -740,11 +779,11 @@ class vprm:
|
|
|
740
779
|
Returns:
|
|
741
780
|
None
|
|
742
781
|
"""
|
|
743
|
-
self.max_lswi = copy.deepcopy(self.
|
|
744
|
-
self.min_lswi = copy.deepcopy(self.
|
|
745
|
-
self.min_max_evi = copy.deepcopy(self.
|
|
782
|
+
self.max_lswi = copy.deepcopy(self.prototype_satellite_manager)
|
|
783
|
+
self.min_lswi = copy.deepcopy(self.prototype_satellite_manager)
|
|
784
|
+
self.min_max_evi = copy.deepcopy(self.prototype_satellite_manager)
|
|
746
785
|
shortcut = self.sat_imgs.sat_img
|
|
747
|
-
# if self.
|
|
786
|
+
# if self.flux_tower_instances is None:
|
|
748
787
|
self.min_lswi.sat_img["min_lswi"] = shortcut["lswi"].min(
|
|
749
788
|
self.time_key, skipna=True
|
|
750
789
|
)
|
|
@@ -764,7 +803,8 @@ class vprm:
|
|
|
764
803
|
)
|
|
765
804
|
return
|
|
766
805
|
|
|
767
|
-
def lowess(self, keys, lonlats=None, times=False, frac=0.25, it=3,
|
|
806
|
+
def lowess(self, keys, lonlats=None, times=False, frac=0.25, it=3,
|
|
807
|
+
n_cpus=None, smooth_all=False):
|
|
768
808
|
"""
|
|
769
809
|
Performs the lowess smoothing
|
|
770
810
|
|
|
@@ -808,7 +848,7 @@ class vprm:
|
|
|
808
848
|
xvals = self.sat_imgs.sat_img["time"]
|
|
809
849
|
logger.info("Lowess timestamps {}".format(xvals))
|
|
810
850
|
|
|
811
|
-
if self.
|
|
851
|
+
if (self.flux_tower_instances is not None) and (smooth_all is False): # Is flux tower sites are given
|
|
812
852
|
if "timestamps" in list(self.sat_imgs.sat_img.data_vars):
|
|
813
853
|
for key in keys:
|
|
814
854
|
self.sat_imgs.sat_img = self.sat_imgs.sat_img.assign(
|
|
@@ -924,13 +964,21 @@ class vprm:
|
|
|
924
964
|
)
|
|
925
965
|
return
|
|
926
966
|
|
|
927
|
-
def clip_values(self, key, min_val, max_val):
|
|
928
|
-
|
|
929
|
-
self.sat_imgs.sat_img[key].values
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
self.sat_imgs.sat_img[key].values
|
|
933
|
-
|
|
967
|
+
def clip_values(self, key, min_val, max_val, to_nan=False):
|
|
968
|
+
if to_nan:
|
|
969
|
+
self.sat_imgs.sat_img[key].values[
|
|
970
|
+
self.sat_imgs.sat_img[key].values < min_val
|
|
971
|
+
] = np.nan
|
|
972
|
+
self.sat_imgs.sat_img[key].values[
|
|
973
|
+
self.sat_imgs.sat_img[key].values > max_val
|
|
974
|
+
] = np.nan
|
|
975
|
+
else:
|
|
976
|
+
self.sat_imgs.sat_img[key].values[
|
|
977
|
+
self.sat_imgs.sat_img[key].values < min_val
|
|
978
|
+
] = min_val
|
|
979
|
+
self.sat_imgs.sat_img[key].values[
|
|
980
|
+
self.sat_imgs.sat_img[key].values > max_val
|
|
981
|
+
] = max_val
|
|
934
982
|
return
|
|
935
983
|
|
|
936
984
|
def clip_non_finite(self, data_var, val, sel):
|
|
@@ -1024,7 +1072,7 @@ class vprm:
|
|
|
1024
1072
|
).values.flatten()
|
|
1025
1073
|
else:
|
|
1026
1074
|
self.buffer[key] = self.sat_imgs.sat_img[key].isel(select_dict)
|
|
1027
|
-
return self.
|
|
1075
|
+
return self.buffer[key]
|
|
1028
1076
|
|
|
1029
1077
|
def get_sat_img_values_for_all_keys(self, lon=None, lat=None, counter_range=None):
|
|
1030
1078
|
"""
|
|
@@ -1057,9 +1105,11 @@ class vprm:
|
|
|
1057
1105
|
days_after_first_image = (
|
|
1058
1106
|
datetime_utc - self.timestamp_start
|
|
1059
1107
|
).total_seconds() / (24 * 60 * 60)
|
|
1108
|
+
# Calculate a list of time differences between the xarray time and the datetime_utc both with respect to timestamp_start.
|
|
1109
|
+
# Take the index (argmin) with smallest difference
|
|
1060
1110
|
counter_new = np.argmin(
|
|
1061
1111
|
np.abs(self.sat_imgs.sat_img[self.time_key].values - days_after_first_image)
|
|
1062
|
-
)
|
|
1112
|
+
)
|
|
1063
1113
|
if (days_after_first_image < 0) | (
|
|
1064
1114
|
days_after_first_image > self.sat_imgs.sat_img[self.time_key][-1]
|
|
1065
1115
|
):
|
|
@@ -1074,21 +1124,21 @@ class vprm:
|
|
|
1074
1124
|
self.new = False
|
|
1075
1125
|
return # Still same satellite image
|
|
1076
1126
|
|
|
1077
|
-
def
|
|
1078
|
-
src_x = self.
|
|
1079
|
-
src_y = self.
|
|
1127
|
+
def _set_empty_xr_lat_lon_grid(self):
|
|
1128
|
+
src_x = self.prototype_satellite_manager.sat_img.coords["x"].values
|
|
1129
|
+
src_y = self.prototype_satellite_manager.sat_img.coords["y"].values
|
|
1080
1130
|
X, Y = np.meshgrid(src_x, src_y)
|
|
1081
1131
|
t = Transformer.from_crs(
|
|
1082
|
-
self.
|
|
1132
|
+
self.prototype_satellite_manager.sat_img.rio.crs, "+proj=longlat +datum=WGS84"
|
|
1083
1133
|
)
|
|
1084
1134
|
x_long, y_lat = t.transform(X, Y)
|
|
1085
|
-
self.
|
|
1135
|
+
self.empty_xr_lat_lon_grid = xr.Dataset(
|
|
1086
1136
|
{
|
|
1087
1137
|
"lon": (["y", "x"], x_long, {"units": "degrees_east"}),
|
|
1088
1138
|
"lat": (["y", "x"], y_lat, {"units": "degrees_north"}),
|
|
1089
1139
|
}
|
|
1090
1140
|
)
|
|
1091
|
-
self.
|
|
1141
|
+
self.empty_xr_lat_lon_grid = self.empty_xr_lat_lon_grid.set_coords(["lon", "lat"])
|
|
1092
1142
|
return
|
|
1093
1143
|
|
|
1094
1144
|
def save(self, save_path):
|
|
@@ -1099,7 +1149,7 @@ class vprm:
|
|
|
1099
1149
|
return
|
|
1100
1150
|
|
|
1101
1151
|
def is_disjoint(self, this_sat_img):
|
|
1102
|
-
bounds = self.
|
|
1152
|
+
bounds = self.prototype_satellite_manager.sat_img.rio.transform_bounds(
|
|
1103
1153
|
this_sat_img.sat_img.rio.crs
|
|
1104
1154
|
)
|
|
1105
1155
|
dj = rasterio.coords.disjoint_bounds(bounds, this_sat_img.sat_img.rio.bounds())
|
|
@@ -1112,7 +1162,7 @@ class vprm:
|
|
|
1112
1162
|
[v.sat_imgs for v in vprm_insts], reproject=allow_reproject
|
|
1113
1163
|
)
|
|
1114
1164
|
keys = list(self.sat_imgs.sat_img.keys())
|
|
1115
|
-
self.
|
|
1165
|
+
self.prototype_satellite_manager = satellite_data_manager(
|
|
1116
1166
|
sat_img=self.sat_imgs.sat_img.drop(keys)
|
|
1117
1167
|
)
|
|
1118
1168
|
|
|
@@ -103,7 +103,7 @@ class brazil_flux_data(flux_tower_data):
|
|
|
103
103
|
"par_fill",
|
|
104
104
|
"ust",
|
|
105
105
|
]
|
|
106
|
-
if use_vars
|
|
106
|
+
if use_vars == "all":
|
|
107
107
|
self.vars = "all"
|
|
108
108
|
site_info_dict = dict()
|
|
109
109
|
site_info_dict["K67"] = dict(lat=-2.857, lon=-54.959, veg_class="EF")
|
|
@@ -188,7 +188,7 @@ class ameri_fluxnet(flux_tower_data):
|
|
|
188
188
|
self.data_path = data_path
|
|
189
189
|
|
|
190
190
|
super().__init__(t_start, t_stop, ssrd_key, t2m_key, site_name)
|
|
191
|
-
if (use_vars is None) | (use_vars
|
|
191
|
+
if (use_vars is None) | (use_vars == "all"):
|
|
192
192
|
self.vars = "all"
|
|
193
193
|
else:
|
|
194
194
|
self.vars = use_vars
|
|
@@ -205,7 +205,7 @@ class ameri_fluxnet(flux_tower_data):
|
|
|
205
205
|
return
|
|
206
206
|
|
|
207
207
|
def add_tower_data(self):
|
|
208
|
-
if self.vars
|
|
208
|
+
if self.vars == "all":
|
|
209
209
|
idata = pd.read_csv(
|
|
210
210
|
glob.glob(os.path.join(self.data_path, "*.csv"))[0], skiprows=2
|
|
211
211
|
)
|
|
@@ -286,7 +286,7 @@ class fluxnet(flux_tower_data):
|
|
|
286
286
|
"TA_F_QC",
|
|
287
287
|
"SW_IN_F_QC",
|
|
288
288
|
]
|
|
289
|
-
elif use_vars
|
|
289
|
+
elif use_vars == "all":
|
|
290
290
|
self.vars = "all"
|
|
291
291
|
else:
|
|
292
292
|
self.vars = use_vars
|
|
@@ -351,6 +351,7 @@ class icos(flux_tower_data):
|
|
|
351
351
|
use_vars=None,
|
|
352
352
|
t_start=None,
|
|
353
353
|
t_stop=None,
|
|
354
|
+
icos_2025=True,
|
|
354
355
|
):
|
|
355
356
|
|
|
356
357
|
self.data_path = data_path
|
|
@@ -380,18 +381,44 @@ class icos(flux_tower_data):
|
|
|
380
381
|
"TA_F_QC",
|
|
381
382
|
"SW_IN_F_QC",
|
|
382
383
|
]
|
|
383
|
-
elif use_vars
|
|
384
|
+
elif use_vars == "all":
|
|
384
385
|
self.vars = "all"
|
|
385
386
|
else:
|
|
386
387
|
self.vars = use_vars
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
388
|
+
if icos_2025:
|
|
389
|
+
site_info = pd.read_csv(
|
|
390
|
+
os.path.join(
|
|
391
|
+
os.path.dirname(self.data_path).replace('FLUXNET_HH_L2', 'ARCHIVE_L2'),
|
|
392
|
+
"ICOSETC_{}_SITEINFO_L2.csv".format(self.site_name),
|
|
393
|
+
),
|
|
394
|
+
on_bad_lines="skip",
|
|
395
|
+
)
|
|
396
|
+
self.footprint = pd.read_csv(
|
|
397
|
+
os.path.join(
|
|
398
|
+
os.path.dirname(self.data_path).replace('FLUXNET_HH_L2', 'ARCHIVE_L2'),
|
|
399
|
+
"ICOSETC_{}_FLUXES_L2.csv".format(self.site_name),
|
|
400
|
+
),
|
|
401
|
+
on_bad_lines="skip",
|
|
402
|
+
)
|
|
403
|
+
if 'FETCH_50' in self.footprint.keys():
|
|
404
|
+
self.fetch50 = np.percentile(self.footprint['FETCH_50'][self.footprint['FETCH_50']>0], 90)
|
|
405
|
+
else:
|
|
406
|
+
self.fetch50 = None
|
|
407
|
+
|
|
408
|
+
if 'FETCH_90' in self.footprint.keys():
|
|
409
|
+
self.fetch90 = np.percentile(self.footprint['FETCH_90'][self.footprint['FETCH_90']>0], 50)
|
|
410
|
+
else:
|
|
411
|
+
self.fetch90 = None
|
|
412
|
+
else:
|
|
413
|
+
site_info = pd.read_csv(
|
|
414
|
+
os.path.join(
|
|
415
|
+
os.path.dirname(self.data_path),
|
|
416
|
+
"ICOSETC_{}_SITEINFO_L2.csv".format(self.site_name),
|
|
417
|
+
),
|
|
418
|
+
on_bad_lines="skip",
|
|
419
|
+
)
|
|
420
|
+
self.fetch90 = None # Not yet implemented
|
|
421
|
+
|
|
395
422
|
self.land_cover_type = site_info.loc[site_info["VARIABLE"] == "IGBP"][
|
|
396
423
|
"DATAVALUE"
|
|
397
424
|
].values[0]
|
|
@@ -405,7 +432,7 @@ class icos(flux_tower_data):
|
|
|
405
432
|
return
|
|
406
433
|
|
|
407
434
|
def add_tower_data(self):
|
|
408
|
-
if self.vars
|
|
435
|
+
if self.vars == "all":
|
|
409
436
|
idata = pd.read_csv(self.data_path, on_bad_lines="skip")
|
|
410
437
|
else:
|
|
411
438
|
idata = pd.read_csv(
|