diff --git a/FLiESANN/constants.py b/FLiESANN/constants.py index 83e9f0b..aa7b4d0 100644 --- a/FLiESANN/constants.py +++ b/FLiESANN/constants.py @@ -3,6 +3,8 @@ DEFAULT_WORKING_DIRECTORY = "." DEFAULT_FLIES_INTERMEDIATE = "FLiES_intermediate" +GEOS5FP_DIRECTORY = "~/data/GEOS5FP_download" + DEFAULT_MODEL_FILENAME = join(abspath(dirname(__file__)), "FLiESANN.h5") SPLIT_ATYPES_CTYPES = True diff --git a/FLiESANN/prepare_FLiES_ANN_inputs.py b/FLiESANN/prepare_FLiES_ANN_inputs.py index 9534c1b..6d5c6b9 100644 --- a/FLiESANN/prepare_FLiES_ANN_inputs.py +++ b/FLiESANN/prepare_FLiES_ANN_inputs.py @@ -24,7 +24,7 @@ def prepare_FLiES_ANN_inputs( elevation_km_flat = np.array(elevation_km).flatten() SZA_flat = np.array(SZA).flatten() - inputs = pd.DataFrame({ + inputs_dict = { "ctype": ctype_flat, "atype": atype_flat, "COT": COT_flat, @@ -34,7 +34,22 @@ def prepare_FLiES_ANN_inputs( "albedo": albedo_flat, "elevation_km": elevation_km_flat, "SZA": SZA_flat - }) + } + + # check all values in inputs_dict are numpy arrays and throw an exception if any are not + + for key, value in inputs_dict.items(): + if not isinstance(value, np.ndarray): + raise TypeError(f"input {key} is not a numpy array: {type(value)}") + + # check the sizes of the input arrays and throw an exception if they mis-match + + input_sizes = {key: np.array(value).size for key, value in inputs_dict.items()} + + if len(set(input_sizes.values())) != 1: + raise ValueError(f"FLiES input size mis-match: {input_sizes}") + + inputs = pd.DataFrame(inputs_dict) if split_atypes_ctypes: inputs["ctype0"] = np.float32(inputs.ctype == 0) diff --git a/FLiESANN/process_FLiES_ANN.py b/FLiESANN/process_FLiES_ANN.py index cc98e07..12916db 100644 --- a/FLiESANN/process_FLiES_ANN.py +++ b/FLiESANN/process_FLiES_ANN.py @@ -1,10 +1,11 @@ from typing import Union from time import process_time - +from datetime import datetime import numpy as np import rasters as rt from rasters import Raster, RasterGeometry from geos5fp import GEOS5FP +from solar_apparent_time import solar_day_of_year_for_area, solar_hour_of_day_for_area from sun_angles import calculate_SZA_from_DOY_and_hour from koppengeiger import load_koppen_geiger @@ -14,8 +15,6 @@ from .run_FLiES_ANN_inference import run_FLiES_ANN_inference def process_FLiES_ANN( - day_of_year: Union[Raster, np.ndarray], - hour_of_day: Union[Raster, np.ndarray], albedo: Union[Raster, np.ndarray], COT: Union[Raster, np.ndarray] = None, AOT: Union[Raster, np.ndarray] = None, @@ -25,8 +24,11 @@ def process_FLiES_ANN( SZA: Union[Raster, np.ndarray] = None, KG_climate: Union[Raster, np.ndarray] = None, geometry: RasterGeometry = None, + time_UTC: datetime = None, + day_of_year: Union[Raster, np.ndarray] = None, + hour_of_day: Union[Raster, np.ndarray] = None, GEOS5FP_connection: GEOS5FP = None, - GEOS5FP_directory: str = None, + resampling: str = "cubic", ANN_model=None, model_filename=DEFAULT_MODEL_FILENAME, split_atypes_ctypes=SPLIT_ATYPES_CTYPES) -> dict: @@ -82,6 +84,16 @@ def process_FLiES_ANN( if geometry is None and isinstance(albedo, Raster): geometry = albedo.geometry + if (day_of_year is None or hour_of_day is None) and time_UTC is not None and geometry is not None: + day_of_year = solar_day_of_year_for_area(time_UTC=time_UTC, geometry=geometry) + hour_of_day = solar_hour_of_day_for_area(time_UTC=time_UTC, geometry=geometry) + + if time_UTC is None and day_of_year is None and hour_of_day is None: + raise ValueError("no time given between time_UTC, day_of_year, and hour_of_day") + + if GEOS5FP_connection is None: + GEOS5FP_connection = GEOS5FP(working_directory=DEFAULT_WORKING_DIRECTORY, download_directory=GEOS5FP_DIRECTORY) + ## FIXME need to fetch default values for parameters: COT, AOT, vapor_gccm, ozone_cm, elevation_km, SZA, KG_climate if SZA is None and geometry is not None: @@ -101,6 +113,34 @@ def process_FLiES_ANN( if KG_climate is None: raise ValueError("Koppen Geieger climate classification or geometry must be given") + if COT is None and geometry is not None and time_UTC is not None: + COT = GEOS5FP_connection.COT( + time_UTC=time_UTC, + geometry=geometry, + resampling=resampling + ) + + if AOT is None and geometry is not None and time_UTC is not None: + AOT = GEOS5FP_connection.AOT( + time_UTC=time_UTC, + geometry=geometry, + resampling=resampling + ) + + if vapor_gccm is None and geometry is not None and time_UTC is not None: + vapor_gccm = GEOS5FP_connection.vapor_gccm( + time_UTC=time_UTC, + geometry=geometry, + resampling=resampling + ) + + if ozone_cm is None and geometry is not None and time_UTC is not None: + ozone_cm = GEOS5FP_connection.ozone_cm( + time_UTC=time_UTC, + geometry=geometry, + resampling=resampling + ) + # Preprocess COT and determine aerosol/cloud types COT = np.clip(COT, 0, None) # Ensure COT is non-negative COT = rt.where(COT < 0.001, 0, COT) # Set very small COT values to 0 diff --git a/FLiESANN/version.txt b/FLiESANN/version.txt index cb174d5..589268e 100644 --- a/FLiESANN/version.txt +++ b/FLiESANN/version.txt @@ -1 +1 @@ -1.2.1 \ No newline at end of file +1.3.0 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index edef91b..2a55bb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools>=60", "setuptools-scm>=8.0", "wheel"] [project] name = "FLiESANN" -version = "1.2.1" +version = "1.3.0" description = "Forest Light Environmental Simulator (FLiES) Radiative Transfer Model Artificial Neural Network (ANN) Implementation in Python" readme = "README.md" authors = [