STIC-JPL 1.1.0__tar.gz → 1.2.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.

Potentially problematic release.


This version of STIC-JPL might be problematic. Click here for more details.

Files changed (43) hide show
  1. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/PKG-INFO +20 -10
  2. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/README.md +9 -6
  3. stic_jpl-1.2.2/STIC_JPL/FVC_from_NDVI.py +49 -0
  4. stic_jpl-1.2.2/STIC_JPL/LAI_from_NDVI.py +61 -0
  5. stic_jpl-1.2.2/STIC_JPL/STIC_JPL.py +3 -0
  6. stic_jpl-1.2.2/STIC_JPL/celcius_to_kelvin.py +11 -0
  7. stic_jpl-1.2.2/STIC_JPL/generate_STIC_inputs.py +65 -0
  8. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/initialize_with_solar.py +1 -2
  9. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/iterate_with_solar.py +1 -1
  10. stic_jpl-1.1.0/STIC_JPL/STIC_JPL.py → stic_jpl-1.2.2/STIC_JPL/model.py +16 -14
  11. stic_jpl-1.2.2/STIC_JPL/process_STIC_table.py +61 -0
  12. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/soil_moisture_initialization.py +2 -1
  13. stic_jpl-1.2.2/STIC_JPL/version.txt +1 -0
  14. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL.egg-info/PKG-INFO +20 -10
  15. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL.egg-info/SOURCES.txt +6 -9
  16. stic_jpl-1.2.2/STIC_JPL.egg-info/requires.txt +23 -0
  17. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/pyproject.toml +12 -5
  18. stic_jpl-1.1.0/STIC_JPL/diagnostic.py +0 -70
  19. stic_jpl-1.1.0/STIC_JPL/meteorology_conversion/__init__.py +0 -1
  20. stic_jpl-1.1.0/STIC_JPL/meteorology_conversion/meteorology_conversion.py +0 -123
  21. stic_jpl-1.1.0/STIC_JPL/soil_heat_flux/__init__.py +0 -1
  22. stic_jpl-1.1.0/STIC_JPL/soil_heat_flux/calculate_SEBAL_soil_heat_flux.py +0 -40
  23. stic_jpl-1.1.0/STIC_JPL/timer/__init__.py +0 -1
  24. stic_jpl-1.1.0/STIC_JPL/timer/timer.py +0 -77
  25. stic_jpl-1.1.0/STIC_JPL/vegetation_conversion/__init__.py +0 -1
  26. stic_jpl-1.1.0/STIC_JPL/vegetation_conversion/vegetation_conversion.py +0 -47
  27. stic_jpl-1.1.0/STIC_JPL/version.txt +0 -1
  28. stic_jpl-1.1.0/STIC_JPL.egg-info/requires.txt +0 -16
  29. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/__init__.py +0 -0
  30. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/canopy_air_stream.py +0 -0
  31. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/closure.py +0 -0
  32. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/constants.py +0 -0
  33. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/initialize_without_solar.py +0 -0
  34. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/iterate_without_solar.py +0 -0
  35. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/net_radiation.py +0 -0
  36. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/root_zone_initialization.py +0 -0
  37. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/root_zone_iteration.py +0 -0
  38. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL/soil_moisture_iteration.py +0 -0
  39. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL.egg-info/dependency_links.txt +0 -0
  40. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/STIC_JPL.egg-info/top_level.txt +0 -0
  41. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/setup.cfg +0 -0
  42. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/tests/test_import_STIC.py +0 -0
  43. {stic_jpl-1.1.0 → stic_jpl-1.2.2}/tests/test_import_dependencies.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: STIC-JPL
3
- Version: 1.1.0
3
+ Version: 1.2.2
4
4
  Summary: Surface Temperature Initiated Closure (STIC) Evapotranspiration Model Python Implementation
5
5
  Author-email: Gregory Halverson <gregory.h.halverson@jpl.nasa.gov>, Kaniska Mallick <kaniska.mallick@list.lu>, Madeleine Pascolini-Campbell <madeleine.a.pascolini-campbell@jpl.nasa.gov>, "Claire S. Villanueva-Weeks" <claire.s.villanueva-weeks@jpl.gov>
6
6
  Project-URL: Homepage, https://github.com/JPL-Evapotranspiration-Algorithms/STIC-JPL
@@ -8,14 +8,21 @@ Classifier: Programming Language :: Python :: 3
8
8
  Classifier: Operating System :: OS Independent
9
9
  Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
+ Requires-Dist: check-distribution
11
12
  Requires-Dist: colored-logging
12
- Requires-Dist: ECOv002-CMR
13
- Requires-Dist: ECOv002-granules
13
+ Requires-Dist: ECOv002-CMR>=1.0.5
14
+ Requires-Dist: ECOv002-granules>=1.0.3
15
+ Requires-Dist: ECOv003-granules
14
16
  Requires-Dist: GEOS5FP>=1.1.1
17
+ Requires-Dist: monte-carlo-sensitivity
15
18
  Requires-Dist: numpy
16
19
  Requires-Dist: pandas
17
- Requires-Dist: rasters
20
+ Requires-Dist: pytictoc
21
+ Requires-Dist: rasters>=1.4.6
22
+ Requires-Dist: seaborn
23
+ Requires-Dist: SEBAL-soil-heat-flux
18
24
  Requires-Dist: solar-apparent-time
25
+ Requires-Dist: verma-net-radiation>=1.1.0
19
26
  Provides-Extra: dev
20
27
  Requires-Dist: build; extra == "dev"
21
28
  Requires-Dist: pytest>=6.0; extra == "dev"
@@ -24,7 +31,8 @@ Requires-Dist: jupyter; extra == "dev"
24
31
  Requires-Dist: pytest; extra == "dev"
25
32
  Requires-Dist: twine; extra == "dev"
26
33
 
27
- # Surface Temperature Initiated Closure (STIC) Evapotranspiration Model Python Implementation
34
+ # `STIC-JPL`
35
+ ## Surface Temperature Initiated Closure (STIC) Evapotranspiration Model Python Implementation
28
36
 
29
37
  [![CI](https://github.com/JPL-Evapotranspiration-Algorithms/STIC/actions/workflows/ci.yml/badge.svg)](https://github.com/JPL-Evapotranspiration-Algorithms/STIC/actions/workflows/ci.yml)
30
38
 
@@ -63,21 +71,23 @@ NASA Jet Propulsion Laboratory 329G
63
71
 
64
72
  ## Installation
65
73
 
66
- Use the pip package manager to install the `STIC` PyPi package.
74
+ Use the pip package manager to install the `STIC-JPL` PyPi package with dashes in the name.
67
75
 
68
76
  ```
69
- pip install STIC
77
+ pip install STIC-JPL
70
78
  ```
71
79
 
72
80
  ## Usage
73
81
 
74
- Import the `STIC` function from the `STIC` package.
82
+ Import the `STIC_JPL` function from the `STIC_JPL` package with underscores in the name.
75
83
 
76
84
  ```
77
- from STIC import STIC
85
+ from STIC_JPL import STIC_JPL
78
86
  ```
79
87
 
80
- See the [ECOSTRESS example](ECOSTRESS%20Example.ipynb) for usage.
88
+ See the [ECOSTRESS example](ECOSTRESS%20Example.ipynb) notebook for usage.
89
+
90
+ See the [STIC sensitivity](STIC%20Sensitivity.ipynb) notebook for sensitivity analysis.
81
91
 
82
92
  ## References
83
93
 
@@ -1,4 +1,5 @@
1
- # Surface Temperature Initiated Closure (STIC) Evapotranspiration Model Python Implementation
1
+ # `STIC-JPL`
2
+ ## Surface Temperature Initiated Closure (STIC) Evapotranspiration Model Python Implementation
2
3
 
3
4
  [![CI](https://github.com/JPL-Evapotranspiration-Algorithms/STIC/actions/workflows/ci.yml/badge.svg)](https://github.com/JPL-Evapotranspiration-Algorithms/STIC/actions/workflows/ci.yml)
4
5
 
@@ -37,21 +38,23 @@ NASA Jet Propulsion Laboratory 329G
37
38
 
38
39
  ## Installation
39
40
 
40
- Use the pip package manager to install the `STIC` PyPi package.
41
+ Use the pip package manager to install the `STIC-JPL` PyPi package with dashes in the name.
41
42
 
42
43
  ```
43
- pip install STIC
44
+ pip install STIC-JPL
44
45
  ```
45
46
 
46
47
  ## Usage
47
48
 
48
- Import the `STIC` function from the `STIC` package.
49
+ Import the `STIC_JPL` function from the `STIC_JPL` package with underscores in the name.
49
50
 
50
51
  ```
51
- from STIC import STIC
52
+ from STIC_JPL import STIC_JPL
52
53
  ```
53
54
 
54
- See the [ECOSTRESS example](ECOSTRESS%20Example.ipynb) for usage.
55
+ See the [ECOSTRESS example](ECOSTRESS%20Example.ipynb) notebook for usage.
56
+
57
+ See the [STIC sensitivity](STIC%20Sensitivity.ipynb) notebook for sensitivity analysis.
55
58
 
56
59
  ## References
57
60
 
@@ -0,0 +1,49 @@
1
+ from typing import Union
2
+ import numpy as np
3
+ import rasters as rt
4
+ from rasters import Raster
5
+
6
+ KPAR = 0.5
7
+ MIN_FIPAR = 0.0
8
+ MAX_FIPAR = 1.0
9
+ MIN_LAI = 0.0
10
+ MAX_LAI = 10.0
11
+
12
+ def FVC_from_NDVI(NDVI: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
13
+ """
14
+ Estimate Fractional Vegetation Cover (FVC) from Normalized Difference Vegetation Index (NDVI)
15
+ using a scaled NDVI approach.
16
+
17
+ This method linearly scales NDVI values between two endmembers:
18
+ - NDVIs: NDVI value for bare soil (typically ~0.04 ± 0.03)
19
+ - NDVIv: NDVI value for full vegetation (typically ~0.52 ± 0.03)
20
+
21
+ The resulting Fractional Vegetation Cover (FVC) is calculated as:
22
+
23
+ FVC = clip((NDVI - NDVIs) / (NDVIv - NDVIs), 0.0, 1.0)
24
+
25
+ This approach is based on the assumption that NDVI increases linearly with vegetation cover
26
+ between these two extremes, and is well-supported in the literature.
27
+
28
+ References:
29
+ Carlson, T. N., & Ripley, D. A. (1997). On the relation between NDVI, fractional vegetation cover,
30
+ and leaf area index. Remote Sensing of Environment, 62(3), 241–252.
31
+ https://doi.org/10.1016/S0034-4257(97)00104-1
32
+
33
+ Gutman, G., & Ignatov, A. (1998). The derivation of the green vegetation fraction from NOAA/AVHRR
34
+ data for use in numerical weather prediction models. International Journal of Remote Sensing,
35
+ 19(8), 1533–1543. https://doi.org/10.1080/014311698215333
36
+
37
+ Parameters:
38
+ NDVI (Union[Raster, np.ndarray]): Input NDVI data.
39
+
40
+ Returns:
41
+ Union[Raster, np.ndarray]: Estimated Fractional Vegetation Cover (FVC).
42
+ """
43
+ NDVIv = 0.52 # NDVI for fully vegetated pixel
44
+ NDVIs = 0.04 # NDVI for bare soil pixel
45
+
46
+ # Scale NDVI to FVC using a linear model and clip to [0, 1]
47
+ FVC = rt.clip((NDVI - NDVIs) / (NDVIv - NDVIs), 0.0, 1.0)
48
+
49
+ return FVC
@@ -0,0 +1,61 @@
1
+ from typing import Union
2
+ import numpy as np
3
+ import rasters as rt
4
+ from rasters import Raster
5
+
6
+ # Constants
7
+ KPAR = 0.5 # Extinction coefficient for PAR, assumed average for broadleaf canopies (Weiss & Baret, 2010)
8
+ MIN_FIPAR = 0.0
9
+ MAX_FIPAR = 1.0
10
+ MIN_LAI = 0.0
11
+ MAX_LAI = 10.0
12
+
13
+ def LAI_from_NDVI(
14
+ NDVI: Union[Raster, np.ndarray],
15
+ min_fIPAR: float = MIN_FIPAR,
16
+ max_fIPAR: float = MAX_FIPAR,
17
+ min_LAI: float = MIN_LAI,
18
+ max_LAI: float = MAX_LAI) -> Union[Raster, np.ndarray]:
19
+ """
20
+ Estimate Leaf Area Index (LAI) from NDVI using a simplified two-step empirical model.
21
+
22
+ This method first approximates the fraction of absorbed photosynthetically active radiation (fIPAR)
23
+ from NDVI, and then estimates LAI using the Beer–Lambert Law. The extinction coefficient for PAR (KPAR)
24
+ is assumed to be 0.5, which is typical for broadleaf canopies under diffuse light conditions.
25
+
26
+ Steps:
27
+ 1. fIPAR ≈ NDVI - 0.05 (empirical offset to account for soil background and sensor noise)
28
+ - Based on observed relationships in Myneni & Williams (1994)
29
+ 2. LAI = -ln(1 - fIPAR) / KPAR (Beer–Lambert Law)
30
+ - From Sellers (1985)
31
+
32
+ All outputs are clipped to user-defined minimum and maximum thresholds to ensure physical realism.
33
+
34
+ Parameters:
35
+ NDVI (Union[Raster, np.ndarray]): Input NDVI data.
36
+ min_fIPAR (float): Minimum fIPAR value for clipping (default 0.0).
37
+ max_fIPAR (float): Maximum fIPAR value for clipping (default 1.0).
38
+ min_LAI (float): Minimum LAI value for clipping (default 0.0).
39
+ max_LAI (float): Maximum LAI value for clipping (default 10.0).
40
+
41
+ Returns:
42
+ Union[Raster, np.ndarray]: Estimated LAI values.
43
+
44
+ References:
45
+ - Sellers, P. J. (1985). Canopy reflectance, photosynthesis and transpiration.
46
+ *International Journal of Remote Sensing*, 6(8), 1335–1372.
47
+ - Myneni, R. B., & Williams, D. L. (1994). On the relationship between FAPAR and NDVI.
48
+ *Remote Sensing of Environment*, 49(3), 200–211.
49
+ - Weiss, M., & Baret, F. (2010). CAN-EYE V6.1 User Manual. INRA-CSE.
50
+
51
+ """
52
+ # Empirical conversion from NDVI to fIPAR (adjusted for background noise)
53
+ fIPAR = rt.clip(NDVI - 0.05, min_fIPAR, max_fIPAR)
54
+
55
+ # Avoid division by zero or log of 0 by masking zero fIPAR values
56
+ fIPAR = np.where(fIPAR == 0, np.nan, fIPAR)
57
+
58
+ # Apply Beer–Lambert law to estimate LAI
59
+ LAI = rt.clip(-np.log(1 - fIPAR) * (1 / KPAR), min_LAI, max_LAI)
60
+
61
+ return LAI
@@ -0,0 +1,3 @@
1
+ from .model import *
2
+ from .generate_STIC_inputs import generate_STIC_inputs
3
+ from .process_STIC_table import process_STIC_table
@@ -0,0 +1,11 @@
1
+ from typing import Union
2
+ import numpy as np
3
+ from rasters import Raster
4
+
5
+ def celcius_to_kelvin(T_C: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
6
+ """
7
+ convert temperature in celsius to kelvin.
8
+ :param T_C: temperature in celsius
9
+ :return: temperature in kelvin
10
+ """
11
+ return T_C + 273.15
@@ -0,0 +1,65 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+ from dateutil import parser
5
+ from pandas import DataFrame
6
+ from rasters import Point
7
+ from sentinel_tiles import sentinel_tiles
8
+ from solar_apparent_time import UTC_to_solar
9
+ from SEBAL_soil_heat_flux import calculate_SEBAL_soil_heat_flux
10
+
11
+ from .model import STIC_JPL, MAX_ITERATIONS, USE_VARIABLE_ALPHA
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ def generate_STIC_inputs(STIC_inputs_from_calval_df: DataFrame) -> DataFrame:
16
+ """
17
+ STIC_inputs_from_claval_df:
18
+ Pandas DataFrame containing the columns: tower, lat, lon, time_UTC, albedo, elevation_km
19
+ return:
20
+ Pandas DataFrame containing the columns: tower, lat, lon, time_UTC, doy, albedo, elevation_km, AOT, COT, vapor_gccm, ozone_cm, SZA, KG
21
+ """
22
+ # output_rows = []
23
+ STIC_inputs_df = STIC_inputs_from_calval_df.copy()
24
+
25
+ hour_of_day = []
26
+ doy = []
27
+ Topt = []
28
+ fAPARmax = []
29
+
30
+ for i, input_row in STIC_inputs_from_calval_df.iterrows():
31
+ tower = input_row.tower
32
+ lat = input_row.lat
33
+ lon = input_row.lon
34
+ time_UTC = input_row.time_UTC
35
+ albedo = input_row.albedo
36
+ elevation_km = input_row.elevation_km
37
+ logger.info(f"collecting STIC inputs for tower {tower} lat {lat} lon {lon} time {time_UTC} UTC")
38
+ time_UTC = parser.parse(str(time_UTC))
39
+ time_solar = UTC_to_solar(time_UTC, lon)
40
+ hour_of_day.append(time_solar.hour)
41
+ doy.append(time_UTC.timetuple().tm_yday)
42
+ date_UTC = time_UTC.date()
43
+ tile = sentinel_tiles.toMGRS(lat, lon)[:5]
44
+ tile_grid = sentinel_tiles.grid(tile=tile, cell_size=70)
45
+ rows, cols = tile_grid.shape
46
+ row, col = tile_grid.index_point(Point(lon, lat))
47
+ geometry = tile_grid[max(0, row - 1):min(row + 2, rows - 1),
48
+ max(0, col - 1):min(col + 2, cols - 1)]
49
+
50
+ if not "hour_of_day" in STIC_inputs_df.columns:
51
+ STIC_inputs_df["hour_of_day"] = hour_of_day
52
+
53
+ if not "doy" in STIC_inputs_df.columns:
54
+ STIC_inputs_df["doy"] = doy
55
+
56
+ if not "Topt" in STIC_inputs_df.columns:
57
+ STIC_inputs_df["Topt"] = Topt
58
+
59
+ if not "fAPARmax" in STIC_inputs_df.columns:
60
+ STIC_inputs_df["fAPARmax"] = fAPARmax
61
+
62
+ if "Ta" in STIC_inputs_df and "Ta_C" not in STIC_inputs_df:
63
+ STIC_inputs_df.rename({"Ta": "Ta_C"}, inplace=True)
64
+
65
+ return STIC_inputs_df
@@ -5,8 +5,7 @@ import rasters as rt
5
5
 
6
6
  from rasters import Raster
7
7
 
8
- from .vegetation_conversion.vegetation_conversion import FVC_from_NDVI
9
- from .soil_heat_flux import calculate_SEBAL_soil_heat_flux
8
+ from SEBAL_soil_heat_flux import calculate_SEBAL_soil_heat_flux
10
9
 
11
10
  from .constants import *
12
11
  from .soil_moisture_initialization import initialize_soil_moisture
@@ -5,7 +5,7 @@ import rasters as rt
5
5
 
6
6
  from rasters import Raster
7
7
 
8
- from .soil_heat_flux import calculate_SEBAL_soil_heat_flux
8
+ from SEBAL_soil_heat_flux import calculate_SEBAL_soil_heat_flux
9
9
 
10
10
  from .constants import *
11
11
  from .canopy_air_stream import calculate_canopy_air_stream_vapor_pressure
@@ -5,19 +5,18 @@ from os.path import join, abspath, expanduser
5
5
  from typing import Dict, List
6
6
  import numpy as np
7
7
  import warnings
8
- from .diagnostic import diagnostic
8
+
9
+ from pytictoc import TicToc
10
+
9
11
  import colored_logging as cl
10
- from .meteorology_conversion import calculate_air_density, calculate_specific_heat, calculate_specific_humidity, calculate_surface_pressure, celcius_to_kelvin
12
+ from check_distribution import check_distribution
11
13
  import rasters as rt
12
14
  from GEOS5FP import GEOS5FP
13
15
  from solar_apparent_time import solar_day_of_year_for_area, solar_hour_of_day_for_area
14
-
15
- from .timer import Timer
16
+ from SEBAL_soil_heat_flux import calculate_SEBAL_soil_heat_flux
16
17
 
17
18
  from rasters import Raster, RasterGeometry
18
19
 
19
- from .vegetation_conversion.vegetation_conversion import FVC_from_NDVI, LAI_from_NDVI
20
-
21
20
  from .constants import *
22
21
  from .closure import STIC_closure
23
22
  from .soil_moisture_initialization import initialize_soil_moisture
@@ -29,8 +28,9 @@ from .initialize_without_solar import initialize_without_solar
29
28
  from .iterate_with_solar import iterate_with_solar
30
29
  from .iterate_without_solar import iterate_without_solar
31
30
  from .root_zone_initialization import calculate_root_zone_moisture
32
-
33
- from .soil_heat_flux import calculate_SEBAL_soil_heat_flux
31
+ from .FVC_from_NDVI import FVC_from_NDVI
32
+ from .LAI_from_NDVI import LAI_from_NDVI
33
+ from .celcius_to_kelvin import celcius_to_kelvin
34
34
 
35
35
  __author__ = 'Kaniska Mallick, Madeleine Pascolini-Campbell, Gregory Halverson'
36
36
 
@@ -181,7 +181,7 @@ def STIC_JPL(
181
181
  G_method = DEFAULT_G_METHOD, # method for calculating soil heat flux
182
182
  )
183
183
 
184
- diagnostic(Ms, "Ms", **diag_kwargs)
184
+ check_distribution(Ms, "Ms")
185
185
 
186
186
  # STIC analytical equations (convergence on LE)
187
187
  gB_ms, gS_ms, dT_C, EF = STIC_closure(
@@ -228,7 +228,9 @@ def STIC_JPL(
228
228
  PT_Wm2 = None
229
229
  iteration = 1
230
230
  LE_Wm2_max_change = 0
231
- t = Timer()
231
+
232
+ t = TicToc()
233
+ t.tic()
232
234
 
233
235
  while (np.nanmax(LE_Wm2_change) >= LE_convergence_target and iteration <= max_iterations):
234
236
  logger.info(f"running STIC iteration {cl.val(iteration)} / {cl.val(max_iterations)}")
@@ -331,11 +333,11 @@ def STIC_JPL(
331
333
  LE_Wm2_old = LE_Wm2_new
332
334
  LE_Wm2_max_change = np.nanmax(LE_Wm2_change)
333
335
  logger.info(
334
- f"completed STIC iteration {cl.val(iteration)} / {cl.val(max_iterations)} with max LE change: {cl.val(np.round(LE_Wm2_max_change, 3))} ({t} seconds)")
336
+ f"completed STIC iteration {cl.val(iteration)} / {cl.val(max_iterations)} with max LE change: {cl.val(np.round(LE_Wm2_max_change, 3))} ({t.tocvalue()} seconds)")
335
337
 
336
- diagnostic(SM, f"SM_{iteration}", **diag_kwargs)
337
- diagnostic(G, f"G_{iteration}", **diag_kwargs)
338
- diagnostic(LE_Wm2_new, f"LE_{iteration}", **diag_kwargs)
338
+ check_distribution(SM, f"SM_{iteration}")
339
+ check_distribution(G, f"G_{iteration}")
340
+ check_distribution(LE_Wm2_new, f"LE_{iteration}")
339
341
 
340
342
  if LE_Wm2_max_change <= LE_convergence_target:
341
343
  logger.info(f"max LE change {cl.val(np.round(LE_Wm2_max_change, 3))} within convergence target {cl.val(np.round(LE_convergence_target, 3))} with {cl.val(iteration)} iteration{'s' if iteration > 1 else ''}")
@@ -0,0 +1,61 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+ from dateutil import parser
5
+ from pandas import DataFrame
6
+ from rasters import Point
7
+ from sentinel_tiles import sentinel_tiles
8
+ from solar_apparent_time import UTC_to_solar
9
+ from SEBAL_soil_heat_flux import calculate_SEBAL_soil_heat_flux
10
+
11
+ from .model import STIC_JPL, MAX_ITERATIONS, USE_VARIABLE_ALPHA
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ def process_STIC_table(
16
+ input_df: DataFrame,
17
+ max_iterations = MAX_ITERATIONS,
18
+ use_variable_alpha = USE_VARIABLE_ALPHA) -> DataFrame:
19
+ hour_of_day = np.float64(np.array(input_df.hour_of_day))
20
+ lon = np.float64(np.array(input_df.lon))
21
+ ST_C = np.float64(np.array(input_df.ST_C))
22
+ emissivity = np.float64(np.array(input_df.EmisWB))
23
+ NDVI = np.float64(np.array(input_df.NDVI))
24
+ albedo = np.float64(np.array(input_df.albedo))
25
+ Ta_C = np.float64(np.array(input_df.Ta_C))
26
+ RH = np.float64(np.array(input_df.RH))
27
+ Rn = np.float64(np.array(input_df.Rn))
28
+ Rg = np.float64(np.array(input_df.Rg))
29
+
30
+ if "G" in input_df:
31
+ G = np.array(input_df.G)
32
+ else:
33
+ G = calculate_SEBAL_soil_heat_flux(
34
+ Rn=Rn,
35
+ ST_C=ST_C,
36
+ NDVI=NDVI,
37
+ albedo=albedo
38
+ )
39
+
40
+ results = STIC_JPL(
41
+ hour_of_day=hour_of_day,
42
+ # longitude=lon,
43
+ ST_C = ST_C,
44
+ emissivity=emissivity,
45
+ NDVI=NDVI,
46
+ albedo=albedo,
47
+ Ta_C=Ta_C,
48
+ RH=RH,
49
+ Rn_Wm2=Rn,
50
+ G=G,
51
+ # Rg_Wm2=Rg,
52
+ max_iterations=max_iterations,
53
+ use_variable_alpha=use_variable_alpha
54
+ )
55
+
56
+ output_df = input_df.copy()
57
+
58
+ for key, value in results.items():
59
+ output_df[key] = value
60
+
61
+ return output_df
@@ -4,7 +4,8 @@ import numpy as np
4
4
  import rasters as rt
5
5
  from rasters import Raster
6
6
 
7
- from .vegetation_conversion.vegetation_conversion import FVC_from_NDVI
7
+ from .FVC_from_NDVI import FVC_from_NDVI
8
+ from .LAI_from_NDVI import LAI_from_NDVI
8
9
 
9
10
  from .constants import GAMMA_HPA
10
11
  from .root_zone_initialization import calculate_root_zone_moisture
@@ -0,0 +1 @@
1
+ 1.2.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: STIC-JPL
3
- Version: 1.1.0
3
+ Version: 1.2.2
4
4
  Summary: Surface Temperature Initiated Closure (STIC) Evapotranspiration Model Python Implementation
5
5
  Author-email: Gregory Halverson <gregory.h.halverson@jpl.nasa.gov>, Kaniska Mallick <kaniska.mallick@list.lu>, Madeleine Pascolini-Campbell <madeleine.a.pascolini-campbell@jpl.nasa.gov>, "Claire S. Villanueva-Weeks" <claire.s.villanueva-weeks@jpl.gov>
6
6
  Project-URL: Homepage, https://github.com/JPL-Evapotranspiration-Algorithms/STIC-JPL
@@ -8,14 +8,21 @@ Classifier: Programming Language :: Python :: 3
8
8
  Classifier: Operating System :: OS Independent
9
9
  Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
+ Requires-Dist: check-distribution
11
12
  Requires-Dist: colored-logging
12
- Requires-Dist: ECOv002-CMR
13
- Requires-Dist: ECOv002-granules
13
+ Requires-Dist: ECOv002-CMR>=1.0.5
14
+ Requires-Dist: ECOv002-granules>=1.0.3
15
+ Requires-Dist: ECOv003-granules
14
16
  Requires-Dist: GEOS5FP>=1.1.1
17
+ Requires-Dist: monte-carlo-sensitivity
15
18
  Requires-Dist: numpy
16
19
  Requires-Dist: pandas
17
- Requires-Dist: rasters
20
+ Requires-Dist: pytictoc
21
+ Requires-Dist: rasters>=1.4.6
22
+ Requires-Dist: seaborn
23
+ Requires-Dist: SEBAL-soil-heat-flux
18
24
  Requires-Dist: solar-apparent-time
25
+ Requires-Dist: verma-net-radiation>=1.1.0
19
26
  Provides-Extra: dev
20
27
  Requires-Dist: build; extra == "dev"
21
28
  Requires-Dist: pytest>=6.0; extra == "dev"
@@ -24,7 +31,8 @@ Requires-Dist: jupyter; extra == "dev"
24
31
  Requires-Dist: pytest; extra == "dev"
25
32
  Requires-Dist: twine; extra == "dev"
26
33
 
27
- # Surface Temperature Initiated Closure (STIC) Evapotranspiration Model Python Implementation
34
+ # `STIC-JPL`
35
+ ## Surface Temperature Initiated Closure (STIC) Evapotranspiration Model Python Implementation
28
36
 
29
37
  [![CI](https://github.com/JPL-Evapotranspiration-Algorithms/STIC/actions/workflows/ci.yml/badge.svg)](https://github.com/JPL-Evapotranspiration-Algorithms/STIC/actions/workflows/ci.yml)
30
38
 
@@ -63,21 +71,23 @@ NASA Jet Propulsion Laboratory 329G
63
71
 
64
72
  ## Installation
65
73
 
66
- Use the pip package manager to install the `STIC` PyPi package.
74
+ Use the pip package manager to install the `STIC-JPL` PyPi package with dashes in the name.
67
75
 
68
76
  ```
69
- pip install STIC
77
+ pip install STIC-JPL
70
78
  ```
71
79
 
72
80
  ## Usage
73
81
 
74
- Import the `STIC` function from the `STIC` package.
82
+ Import the `STIC_JPL` function from the `STIC_JPL` package with underscores in the name.
75
83
 
76
84
  ```
77
- from STIC import STIC
85
+ from STIC_JPL import STIC_JPL
78
86
  ```
79
87
 
80
- See the [ECOSTRESS example](ECOSTRESS%20Example.ipynb) for usage.
88
+ See the [ECOSTRESS example](ECOSTRESS%20Example.ipynb) notebook for usage.
89
+
90
+ See the [STIC sensitivity](STIC%20Sensitivity.ipynb) notebook for sensitivity analysis.
81
91
 
82
92
  ## References
83
93
 
@@ -1,16 +1,21 @@
1
1
  README.md
2
2
  pyproject.toml
3
+ STIC_JPL/FVC_from_NDVI.py
4
+ STIC_JPL/LAI_from_NDVI.py
3
5
  STIC_JPL/STIC_JPL.py
4
6
  STIC_JPL/__init__.py
5
7
  STIC_JPL/canopy_air_stream.py
8
+ STIC_JPL/celcius_to_kelvin.py
6
9
  STIC_JPL/closure.py
7
10
  STIC_JPL/constants.py
8
- STIC_JPL/diagnostic.py
11
+ STIC_JPL/generate_STIC_inputs.py
9
12
  STIC_JPL/initialize_with_solar.py
10
13
  STIC_JPL/initialize_without_solar.py
11
14
  STIC_JPL/iterate_with_solar.py
12
15
  STIC_JPL/iterate_without_solar.py
16
+ STIC_JPL/model.py
13
17
  STIC_JPL/net_radiation.py
18
+ STIC_JPL/process_STIC_table.py
14
19
  STIC_JPL/root_zone_initialization.py
15
20
  STIC_JPL/root_zone_iteration.py
16
21
  STIC_JPL/soil_moisture_initialization.py
@@ -21,13 +26,5 @@ STIC_JPL.egg-info/SOURCES.txt
21
26
  STIC_JPL.egg-info/dependency_links.txt
22
27
  STIC_JPL.egg-info/requires.txt
23
28
  STIC_JPL.egg-info/top_level.txt
24
- STIC_JPL/meteorology_conversion/__init__.py
25
- STIC_JPL/meteorology_conversion/meteorology_conversion.py
26
- STIC_JPL/soil_heat_flux/__init__.py
27
- STIC_JPL/soil_heat_flux/calculate_SEBAL_soil_heat_flux.py
28
- STIC_JPL/timer/__init__.py
29
- STIC_JPL/timer/timer.py
30
- STIC_JPL/vegetation_conversion/__init__.py
31
- STIC_JPL/vegetation_conversion/vegetation_conversion.py
32
29
  tests/test_import_STIC.py
33
30
  tests/test_import_dependencies.py
@@ -0,0 +1,23 @@
1
+ check-distribution
2
+ colored-logging
3
+ ECOv002-CMR>=1.0.5
4
+ ECOv002-granules>=1.0.3
5
+ ECOv003-granules
6
+ GEOS5FP>=1.1.1
7
+ monte-carlo-sensitivity
8
+ numpy
9
+ pandas
10
+ pytictoc
11
+ rasters>=1.4.6
12
+ seaborn
13
+ SEBAL-soil-heat-flux
14
+ solar-apparent-time
15
+ verma-net-radiation>=1.1.0
16
+
17
+ [dev]
18
+ build
19
+ pytest>=6.0
20
+ pytest-cov
21
+ jupyter
22
+ pytest
23
+ twine
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
3
3
 
4
4
  [project]
5
5
  name = "STIC-JPL"
6
- version = "1.1.0"
6
+ version = "1.2.2"
7
7
  description = "Surface Temperature Initiated Closure (STIC) Evapotranspiration Model Python Implementation"
8
8
  readme = "README.md"
9
9
  authors = [
@@ -17,14 +17,21 @@ classifiers = [
17
17
  "Operating System :: OS Independent",
18
18
  ]
19
19
  dependencies = [
20
+ "check-distribution",
20
21
  "colored-logging",
21
- "ECOv002-CMR",
22
- "ECOv002-granules",
22
+ "ECOv002-CMR>=1.0.5",
23
+ "ECOv002-granules>=1.0.3",
24
+ "ECOv003-granules",
23
25
  "GEOS5FP>=1.1.1",
26
+ "monte-carlo-sensitivity",
24
27
  "numpy",
25
28
  "pandas",
26
- "rasters",
27
- "solar-apparent-time"
29
+ "pytictoc",
30
+ "rasters>=1.4.6",
31
+ "seaborn",
32
+ "SEBAL-soil-heat-flux",
33
+ "solar-apparent-time",
34
+ "verma-net-radiation>=1.1.0"
28
35
  ]
29
36
  requires-python = ">=3.10"
30
37
 
@@ -1,70 +0,0 @@
1
- from typing import Union
2
- from os.path import join
3
- from datetime import date
4
- import numpy as np
5
- import logging
6
-
7
- import colored_logging as cl
8
- from rasters import Raster
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
- def diagnostic(values: Union[Raster, np.ndarray], variable: str, show_distributions: bool = True, output_directory: str = None):
13
- if isinstance(values, Raster) and output_directory is not None:
14
- filename = join(output_directory, f"{variable}.tif")
15
- logger.info(filename)
16
- values.to_geotiff(filename)
17
-
18
- if show_distributions:
19
- unique = np.unique(values)
20
- nan_proportion = np.count_nonzero(np.isnan(values)) / np.size(values)
21
-
22
- if len(unique) < 10:
23
- logger.info(f"variable {cl.name(variable)} ({values.dtype}) has {cl.val(unique)} unique values")
24
-
25
- for value in unique:
26
- if np.isnan(value):
27
- count = np.count_nonzero(np.isnan(values))
28
- else:
29
- count = np.count_nonzero(values == value)
30
-
31
- if value == 0 or np.isnan(value):
32
- logger.info(f"* {cl.colored(value, 'red')}: {cl.colored(count, 'red')}")
33
- else:
34
- logger.info(f"* {cl.val(value)}: {cl.val(count)}")
35
- else:
36
- minimum = np.nanmin(values)
37
-
38
- if minimum < 0:
39
- minimum_string = cl.colored(f"{minimum:0.3f}", "red")
40
- else:
41
- minimum_string = cl.val(f"{minimum:0.3f}")
42
-
43
- maximum = np.nanmax(values)
44
-
45
- if maximum <= 0:
46
- maximum_string = cl.colored(f"{maximum:0.3f}", "red")
47
- else:
48
- maximum_string = cl.val(f"{maximum:0.3f}")
49
-
50
- if nan_proportion > 0.5:
51
- nan_proportion_string = cl.colored(f"{(nan_proportion * 100):0.2f}%", "yellow")
52
- elif nan_proportion == 1:
53
- nan_proportion_string = cl.colored(f"{(nan_proportion * 100):0.2f}%", "red")
54
- else:
55
- nan_proportion_string = cl.val(f"{(nan_proportion * 100):0.2f}%")
56
-
57
- message = "variable " + cl.name(variable) + \
58
- " min: " + minimum_string + \
59
- " mean: " + cl.val(f"{np.nanmean(values):0.3f}") + \
60
- " max: " + maximum_string + \
61
- " nan: " + nan_proportion_string
62
-
63
- if np.all(values == 0):
64
- message += " all zeros"
65
- logger.warning(message)
66
- else:
67
- logger.info(message)
68
-
69
- if nan_proportion == 1:
70
- raise ValueError(f"variable {variable} is blank")
@@ -1 +0,0 @@
1
- from .meteorology_conversion import *
@@ -1,123 +0,0 @@
1
- from typing import Union
2
- import numpy as np
3
- import rasters as rt
4
- from rasters import Raster
5
-
6
- # gas constant for dry air in joules per kilogram per kelvin
7
- RD = 286.9
8
-
9
- # gas constant for moist air in joules per kilogram per kelvin
10
- RW = 461.5
11
-
12
- # specific heat of water vapor in joules per kilogram per kelvin
13
- CPW = 1846.0
14
-
15
- # specific heat of dry air in joules per kilogram per kelvin
16
- CPD = 1005.0
17
-
18
- def kelvin_to_celsius(T_K: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
19
- """
20
- convert temperature in kelvin to celsius.
21
- :param T_K: temperature in kelvin
22
- :return: temperature in celsius
23
- """
24
- return T_K - 273.15
25
-
26
- def celcius_to_kelvin(T_C: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
27
- """
28
- convert temperature in celsius to kelvin.
29
- :param T_C: temperature in celsius
30
- :return: temperature in kelvin
31
- """
32
- return T_C + 273.15
33
-
34
- def calculate_specific_humidity(
35
- Ea_Pa: Union[Raster, np.ndarray],
36
- Ps_Pa: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
37
- """
38
- Calculate the specific humidity of air as a ratio of kilograms of water to kilograms of air.
39
-
40
- Args:
41
- Ea_Pa (Union[Raster, np.ndarray]): Actual water vapor pressure in Pascal.
42
- surface_pressure_Pa (Union[Raster, np.ndarray]): Surface pressure in Pascal.
43
-
44
- Returns:
45
- Union[Raster, np.ndarray]: Specific humidity in kilograms of water per kilograms of air.
46
- """
47
- return ((0.622 * Ea_Pa) / (Ps_Pa - (0.387 * Ea_Pa)))
48
-
49
- def calculate_specific_heat(specific_humidity: Union[Raster, np.ndarray]):
50
- # calculate specific heat capacity of the air (Cp)
51
- # in joules per kilogram per kelvin
52
- # from specific heat of water vapor (CPW)
53
- # and specific heat of dry air (CPD)
54
- Cp_Jkg = specific_humidity * CPW + (1 - specific_humidity) * CPD
55
-
56
- return Cp_Jkg
57
-
58
- def calculate_air_density(
59
- surface_pressure_Pa: Union[Raster, np.ndarray],
60
- Ta_K: Union[Raster, np.ndarray],
61
- specific_humidity: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
62
- """
63
- Calculate air density.
64
-
65
- Parameters:
66
- surface_pressure_Pa (Union[Raster, np.ndarray]): Surface pressure in Pascal.
67
- Ta_K (Union[Raster, np.ndarray]): Air temperature in Kelvin.
68
- specific_humidity (Union[Raster, np.ndarray]): Specific humidity.
69
-
70
- Returns:
71
- Union[Raster, np.ndarray]: Air density in kilograms per cubic meter.
72
- """
73
- # numerator: Pa(N / m ^ 2 = kg * m / s ^ 2); denominator: J / kg / K * K)
74
- rhoD = surface_pressure_Pa / (RD * Ta_K)
75
-
76
- # calculate air density (rho) in kilograms per cubic meter
77
- rho = rhoD * ((1.0 + specific_humidity) / (1.0 + specific_humidity * (RW / RD)))
78
-
79
- return rho
80
-
81
- def SVP_kPa_from_Ta_C(Ta_C: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
82
- """
83
- Calculate the saturation vapor pressure in kiloPascal (kPa) from air temperature in Celsius.
84
-
85
- Parameters:
86
- Ta_C (Union[Raster, np.ndarray]): Air temperature in Celsius.
87
-
88
- Returns:
89
- Union[Raster, np.ndarray]: Saturation vapor pressure in kPa.
90
-
91
- """
92
- SVP_kPa = np.clip(0.611 * np.exp((Ta_C * 17.27) / (Ta_C + 237.7)), 1, None)
93
-
94
- return SVP_kPa
95
-
96
- def SVP_Pa_from_Ta_C(Ta_C: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
97
- """
98
- Calculate the saturation vapor pressure in Pascal (Pa) from the air temperature in Celsius (Ta_C).
99
-
100
- Parameters:
101
- Ta_C (Union[Raster, np.ndarray]): Air temperature in Celsius.
102
-
103
- Returns:
104
- Union[Raster, np.ndarray]: Saturation vapor pressure in Pascal (Pa).
105
- """
106
- return SVP_kPa_from_Ta_C(Ta_C) * 1000
107
-
108
- def calculate_surface_pressure(elevation_m: Union[Raster, np.ndarray], Ta_C: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
109
- """
110
- Calculate surface pressure using elevation and air temperature.
111
-
112
- Parameters:
113
- elevation_m (Union[Raster, np.ndarray]): Elevation in meters.
114
- Ta_K (Union[Raster, np.ndarray]): Air temperature in Kelvin.
115
-
116
- Returns:
117
- Union[Raster, np.ndarray]: Surface pressure in Pascal (Pa).
118
- """
119
- Ta_K = kelvin_to_celsius(Ta_C)
120
- Ps_Pa = 101325.0 * (1.0 - 0.0065 * elevation_m / Ta_K) ** (9.807 / (0.0065 * 287.0)) # [Pa]
121
-
122
- return Ps_Pa
123
-
@@ -1 +0,0 @@
1
- from .calculate_SEBAL_soil_heat_flux import calculate_SEBAL_soil_heat_flux
@@ -1,40 +0,0 @@
1
- from typing import Union
2
-
3
- import numpy as np
4
- import rasters as rt
5
- from rasters import Raster
6
-
7
- def calculate_SEBAL_soil_heat_flux(
8
- Rn: Union[Raster, np.ndarray],
9
- ST_C: Union[Raster, np.ndarray],
10
- NDVI: Union[Raster, np.ndarray],
11
- albedo: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
12
- """
13
- This function calculates the soil heat flux (G) in the Surface Energy Balance Algorithm for Land (SEBAL) model.
14
- The formula used in the function is a simplification of the more complex relationship between these variables in the energy balance at the surface.
15
-
16
- Parameters:
17
- Rn (np.ndarray): Net radiation at the surface.
18
- ST_C (np.ndarray): Surface temperature in Celsius.
19
- NDVI (np.ndarray): Normalized Difference Vegetation Index, indicating the presence and condition of vegetation.
20
- albedo (np.ndarray): Measure of the diffuse reflection of solar radiation.
21
-
22
- Returns:
23
- np.ndarray: The soil heat flux (G), a key component in the energy balance.
24
-
25
- Reference:
26
- "Evapotranspiration Estimation Based on Remote Sensing and the SEBAL Model in the Bosten Lake Basin of China" [^1^][1]
27
- """
28
- # Empirical coefficients used in the calculation
29
- coeff1 = 0.0038
30
- coeff2 = 0.0074
31
-
32
- # Vegetation cover correction factor
33
- NDVI_correction = 1 - 0.98 * NDVI ** 4
34
-
35
- # Calculation of the soil heat flux (G)
36
- G = Rn * ST_C * (coeff1 + coeff2 * albedo) * NDVI_correction
37
-
38
- G = rt.clip(G, 0, None)
39
-
40
- return G
@@ -1 +0,0 @@
1
- from .timer import *
@@ -1,77 +0,0 @@
1
- """
2
- This is a minimalistic performance timer.
3
- """
4
- import time
5
-
6
- __author__ = "Gregory Halverson"
7
-
8
- DEFAULT_FORMAT = "0.2f"
9
-
10
- class Timer(object):
11
- """
12
- This is a minimalistic performance timer.
13
- """
14
-
15
- def __init__(self):
16
- self._start_time = None
17
- self._end_time = None
18
- self.start()
19
-
20
- def __enter__(self, *args, **kwargs):
21
- self.start()
22
- return self
23
-
24
- def __exit__(self, *args, **kwargs):
25
- self.end()
26
-
27
- def __repr__(self):
28
- # print("Timer.__repr__")
29
- return self.__format__(format_string=DEFAULT_FORMAT)
30
-
31
- def __str__(self):
32
- # print("Timer.__str__")
33
- return self.__repr__()
34
-
35
- def __format__(self, format_string=DEFAULT_FORMAT):
36
- if format_string is None or format_string == "":
37
- format_string = DEFAULT_FORMAT
38
-
39
- return format(self.duration, format_string)
40
-
41
- @property
42
- def now(self):
43
- # return datetime.now()
44
- return time.perf_counter()
45
-
46
- def start(self):
47
- self._start_time = self.now
48
-
49
- return self.start_time
50
-
51
- @property
52
- def start_time(self):
53
- return self._start_time
54
-
55
- def end(self):
56
- self._end_time = self.now
57
-
58
- return self.end_time
59
-
60
- @property
61
- def end_time(self):
62
- return self._end_time
63
-
64
- @property
65
- def duration(self):
66
- if self.start_time is None:
67
- raise Exception("timer never started")
68
-
69
- if self.end_time is None:
70
- end_time = self.now
71
- else:
72
- end_time = self.end_time
73
-
74
- duration = end_time - self.start_time
75
-
76
- return duration
77
-
@@ -1 +0,0 @@
1
- from .vegetation_conversion import *
@@ -1,47 +0,0 @@
1
- from typing import Union
2
- import numpy as np
3
- import rasters as rt
4
- from rasters import Raster
5
-
6
- KPAR = 0.5
7
- MIN_FIPAR = 0.0
8
- MAX_FIPAR = 1.0
9
- MIN_LAI = 0.0
10
- MAX_LAI = 10.0
11
-
12
- def FVC_from_NDVI(NDVI: Union[Raster, np.ndarray]) -> Union[Raster, np.ndarray]:
13
- """
14
- Convert Normalized Difference Vegetation Index (NDVI) to Fractional Vegetation Cover (FVC).
15
-
16
- Parameters:
17
- NDVI (Union[Raster, np.ndarray]): Input NDVI data.
18
-
19
- Returns:
20
- Union[Raster, np.ndarray]: Converted FVC data.
21
- """
22
- NDVIv = 0.52 # +- 0.03
23
- NDVIs = 0.04 # +- 0.03
24
- FVC = rt.clip((NDVI - NDVIs) / (NDVIv - NDVIs), 0.0, 1.0)
25
-
26
- return FVC
27
-
28
- def LAI_from_NDVI(
29
- NDVI: Union[Raster, np.ndarray],
30
- min_fIPAR: float = MIN_FIPAR,
31
- max_fIPAR: float = MAX_FIPAR,
32
- min_LAI: float = MIN_LAI,
33
- max_LAI: float = MAX_LAI) -> Union[Raster, np.ndarray]:
34
- """
35
- Convert Normalized Difference Vegetation Index (NDVI) to Leaf Area Index (LAI).
36
-
37
- Parameters:
38
- NDVI (Union[Raster, np.ndarray]): Input NDVI data.
39
-
40
- Returns:
41
- Union[Raster, np.ndarray]: Converted LAI data.
42
- """
43
- fIPAR = rt.clip(NDVI - 0.05, min_fIPAR, max_fIPAR)
44
- fIPAR = np.where(fIPAR == 0, np.nan, fIPAR)
45
- LAI = rt.clip(-np.log(1 - fIPAR) * (1 / KPAR), min_LAI, max_LAI)
46
-
47
- return LAI
@@ -1 +0,0 @@
1
- 1.0.4
@@ -1,16 +0,0 @@
1
- colored-logging
2
- ECOv002-CMR
3
- ECOv002-granules
4
- GEOS5FP>=1.1.1
5
- numpy
6
- pandas
7
- rasters
8
- solar-apparent-time
9
-
10
- [dev]
11
- build
12
- pytest>=6.0
13
- pytest-cov
14
- jupyter
15
- pytest
16
- twine
File without changes
File without changes
File without changes
File without changes