Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
8b92e71
import directly to xarray and rewrite utils_transformation to work wi…
mats-knmi Jul 17, 2024
23825dd
convert to_rainrate function also
mats-knmi Jul 17, 2024
9952bd3
Rewrite more code to xarray
mats-knmi Jul 18, 2024
904bce8
conversion.py works on the xarray datamodel
mats-knmi Jul 18, 2024
b3aa009
fix converters.py
mats-knmi Jul 18, 2024
016d28f
make dimension.py xarray compatible (#397)
mats-knmi Aug 12, 2024
e7c081c
make all nowcast methods xarray compatible (#414)
mats-knmi Sep 2, 2024
c2ae9db
Added member and time dimension (#432)
gjm174 Sep 23, 2024
9ea07fc
Initial commit to branch
sidekock Oct 8, 2024
53a98a9
Working on _apply_noise_and_ar_model method
sidekock Oct 8, 2024
0c5185f
Only update function needs to be added
sidekock Oct 8, 2024
46bc44a
Fully refactored code
sidekock Oct 9, 2024
eb33c06
Possible solution for errors solved
sidekock Oct 9, 2024
a1ce4bc
Fixed small bug in _nowcast_main
sidekock Oct 9, 2024
b002354
Name changes from feedback Ruben
sidekock Oct 22, 2024
e105a24
Name changes from feedback Ruben v2
sidekock Oct 22, 2024
b6c47af
Name changes from feedback Ruben v3
sidekock Oct 22, 2024
5aaf24d
Added config dataclass to steps nowcast
sidekock Oct 31, 2024
fa9a1ef
Added config dataclass to steps nowcast, v2
sidekock Oct 31, 2024
3da1696
Added config dataclass to steps nowcast, v3
sidekock Oct 31, 2024
8c7982c
Added config dataclass to steps nowcast, v4
sidekock Oct 31, 2024
aa26517
Added config dataclass to steps nowcast, fixed some assignment issues…
sidekock Oct 31, 2024
ff2e2be
Fixed num_ensemble_workers bug
sidekock Oct 31, 2024
2543683
Added params and state dataclasses
sidekock Oct 31, 2024
37739de
Implemented a reset of the states and params to allow for multiple fo…
sidekock Oct 31, 2024
f123bde
Removed some redundant fields
sidekock Oct 31, 2024
1a71e61
Update pysteps/nowcasts/steps.py
sidekock Nov 4, 2024
db953fb
Added suggested changed by Mats regarding __ and typing
sidekock Nov 4, 2024
eff9248
Possible fix for static code analysis
sidekock Nov 4, 2024
663a9a2
Added the needed documentation to the class
sidekock Nov 4, 2024
3226042
Merge remote-tracking branch 'origin/refactoring_steps' into xarray/main
mats-knmi Nov 6, 2024
2066f14
Refactored all names in the steps blending code from old to new
sidekock Nov 18, 2024
72d0fbc
Made some name changes but test still do not pass
sidekock Nov 18, 2024
1ce563e
Fixed naming changes, now the tests pass
sidekock Nov 18, 2024
fbe551b
Built the rough scaffolding for the blending class
sidekock Nov 18, 2024
46a93e5
Refactored untill no rain case
sidekock Nov 27, 2024
1eede39
Added code to estimation of ar parameters of radar
sidekock Nov 28, 2024
a18f1f6
Next go, start with forecast loop #7
sidekock Nov 29, 2024
8d16c11
Added some uniformity between nowcast and blending steps. Now at # 8.…
sidekock Dec 2, 2024
88df97d
Small changes since prev commit
sidekock Dec 2, 2024
7ee0020
All code is tranfered. Last part of the main loop needs to be refactored
sidekock Dec 2, 2024
f387981
Everything is refactored, no test ran as of yet
sidekock Dec 5, 2024
760c185
Old forecast function is updated to fit newly refactored code
sidekock Dec 5, 2024
8d8905a
Removed old code which is no longer used
sidekock Dec 5, 2024
d6249f5
6 more tests that fail
sidekock Dec 5, 2024
38702b3
All tests pass, still need to fix TODOs
sidekock Dec 5, 2024
5ff1713
Updated gitignore
sidekock Dec 5, 2024
d999501
Cleanup of params and state dataclasses, next step: better typing
sidekock Dec 6, 2024
ed20ecc
Cleanup of params and state dataclasses, now all tests pass
sidekock Dec 6, 2024
701e726
Added correct typing to all parts of params and state
sidekock Dec 6, 2024
b9de511
Ready for pull request
sidekock Dec 6, 2024
38ed195
Made changes for Codacy review
sidekock Dec 6, 2024
32b656f
Added aditional tests which currently fail in master branch
sidekock Dec 16, 2024
4fe9f78
Update .gitignore
sidekock Dec 16, 2024
b31d55c
Used the __zero_precip_time in __zero_precipitation_forecast()
sidekock Dec 16, 2024
cc02593
Changed typing hints to python 3.10+ version
sidekock Dec 16, 2024
4e4a148
Added comments back to the State dataclass
sidekock Dec 16, 2024
0f4e037
Changed the self.__state.velocity_perturbations = [] to self.__params…
sidekock Dec 17, 2024
9f413aa
Added code changes as suggested by Ruben, comments and documentation …
sidekock Dec 18, 2024
c72d953
Added frozen functionality to dataclasses, removed reset_state and fi…
sidekock Dec 19, 2024
00f057b
Added frozen dataclass to nowcast
sidekock Dec 19, 2024
1b82512
The needed checks are done for this TODO so it can be removed
sidekock Dec 19, 2024
47ab6c3
Use the seed in all rng in blending code (#449)
mats-knmi Jan 2, 2025
48187c4
Removed deepcopy of worker_state. The state is now accessable to all …
sidekock Jan 3, 2025
9b216a7
Update to probmatching comments to keep in track with main
sidekock Jan 8, 2025
4e2d4b7
Merge remote-tracking branch 'origin/master' into xarray/main
mats-knmi Jan 14, 2025
2ff8e2c
Merge remote-tracking branch 'origin/refactor-steps-in-blending-code'…
mats-knmi Jan 14, 2025
561e7ac
Fix for multithreading issue, this produces exactly the same results …
sidekock Jan 20, 2025
4fb784e
New commit for new pr
sidekock Jan 20, 2025
9f48c66
Merge remote-tracking branch 'origin/refactor-steps-in-blending-code'…
mats-knmi Jan 29, 2025
7d1e91e
Fix more of the unit tests with the new xarray based code (#454)
mats-knmi Sep 8, 2025
eef8d1d
Add XR comment
mats-knmi Sep 8, 2025
fc666a6
Merge remote-tracking branch 'origin/master' into xarray/main
mats-knmi Sep 9, 2025
325aab3
fix tests that failed during merge with master
mats-knmi Sep 9, 2025
d9f6f6a
Xarray/add more xr comments (#504)
mats-knmi Sep 10, 2025
e9ddf0f
test_datasets.py to xarray (#511)
larsbonnefoy Sep 10, 2025
7455efe
Xarray/passing more tests (#512)
sidekock Sep 10, 2025
73bf8b2
Xarray/fix even more tests (#513)
mats-knmi Sep 10, 2025
7791b4a
make last test discoverable and fix dimension test
mats-knmi Sep 11, 2025
2330b01
Fixed test_exports, Required hardcoding some passing test as exporter…
larsbonnefoy Sep 11, 2025
7771632
All specific IO tests are done (#519)
sidekock Sep 12, 2025
d77f1b4
removed failing plugin test. Cookiecutter does not work correctly wit…
larsbonnefoy Sep 12, 2025
961fb27
xarray in blending code (#522)
mats-knmi Sep 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/ci_test_env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies:
- pillow
- pyproj
- scipy
- xarray
# Optional dependencies
- dask
- pyfftw
Expand Down
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ dependencies:
- pillow
- pyproj
- scipy
- xarray
4 changes: 3 additions & 1 deletion environment_dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ dependencies:
- dask
- pyfftw
- h5py
- PyWavelets
- pygrib
- black
- pytest-cov
Expand All @@ -31,3 +30,6 @@ dependencies:
- scikit-image
- pandas
- rasterio
- xarray
- geotiff
- cookiecutter
50 changes: 22 additions & 28 deletions pysteps/blending/linear_blending.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,28 @@
"""

import numpy as np
import xarray as xr
from pysteps import nowcasts
from pysteps.utils import conversion
from scipy.stats import rankdata

from pysteps.xarray_helpers import convert_output_to_xarray_dataset


def forecast(
precip,
precip_metadata,
velocity,
radar_dataset: xr.Dataset,
timesteps,
timestep,
nowcast_method,
precip_nwp=None,
precip_nwp_metadata=None,
model_dataset: xr.Dataset = None,
start_blending=120,
end_blending=240,
fill_nwp=True,
saliency=False,
nowcast_kwargs=None,
):
"""Generate a forecast by linearly or saliency-based blending of nowcasts with NWP data
# XR: Update docstring

Parameters
----------
Expand Down Expand Up @@ -105,31 +106,27 @@ def forecast(
if nowcast_kwargs is None:
nowcast_kwargs = dict()

# Ensure that only the most recent precip timestep is used
if len(precip.shape) == 3:
precip = precip[-1, :, :]

# First calculate the number of needed timesteps (up to end_blending) for the nowcast
# to ensure that the nowcast calculation time is limited.
timesteps_nowcast = int(end_blending / timestep)

nowcast_method_func = nowcasts.get_method(nowcast_method)

# Check if NWP data is given as input
if precip_nwp is not None:
if model_dataset is not None:
# Calculate the nowcast
precip_nowcast = nowcast_method_func(
precip,
velocity,
timesteps_nowcast,
**nowcast_kwargs,
nowcast_dataset = nowcast_method_func(
radar_dataset, timesteps_nowcast, **nowcast_kwargs
)

# Make sure that precip_nowcast and precip_nwp are in mm/h
precip_nowcast, _ = conversion.to_rainrate(
precip_nowcast, metadata=precip_metadata
)
precip_nwp, _ = conversion.to_rainrate(precip_nwp, metadata=precip_nwp_metadata)
nowcast_dataset = conversion.to_rainrate(nowcast_dataset)
nowcast_precip_var = nowcast_dataset.attrs["precip_var"]
precip_nowcast = nowcast_dataset[nowcast_precip_var].values

model_dataset = conversion.to_rainrate(model_dataset)
model_precip_var = model_dataset.attrs["precip_var"]
precip_nwp = model_dataset[model_precip_var].values

if len(precip_nowcast.shape) == 4:
n_ens_members_nowcast = precip_nowcast.shape[0]
Expand Down Expand Up @@ -261,22 +258,19 @@ def forecast(

else:
# Calculate the nowcast
precip_nowcast = nowcast_method_func(
precip,
velocity,
timesteps,
**nowcast_kwargs,
nowcast_dataset = nowcast_method_func(
radar_dataset, timesteps, **nowcast_kwargs
)

# Make sure that precip_nowcast and precip_nwp are in mm/h
precip_nowcast, _ = conversion.to_rainrate(
precip_nowcast, metadata=precip_metadata
)
nowcast_dataset = conversion.to_rainrate(nowcast_dataset)
nowcast_precip_var = nowcast_dataset.attrs["precip_var"]
precip_nowcast = nowcast_dataset[nowcast_precip_var].values

# If no NWP data is given, the blended field is simply equal to the nowcast field
precip_blended = precip_nowcast

return precip_blended
return convert_output_to_xarray_dataset(radar_dataset, timesteps, precip_blended)


def _get_slice(n_dims, ref_dim, ref_id):
Expand Down
115 changes: 86 additions & 29 deletions pysteps/blending/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@
calculate_weights_spn
blend_means_sigmas
"""
from datetime import datetime
import math
import time
from copy import copy, deepcopy
from functools import partial
from multiprocessing.pool import ThreadPool

import numpy as np
import xarray as xr
from scipy.linalg import inv
from scipy.ndimage import binary_dilation, generate_binary_structure, iterate_structure

Expand All @@ -57,6 +59,7 @@
from pysteps.postprocessing import probmatching
from pysteps.timeseries import autoregression, correlation
from pysteps.utils.check_norain import check_norain
from pysteps.xarray_helpers import convert_output_to_xarray_dataset

try:
import dask
Expand Down Expand Up @@ -412,20 +415,76 @@ class StepsBlendingState:
class StepsBlendingNowcaster:
def __init__(
self,
precip,
precip_models,
velocity,
velocity_models,
radar_dataset: xr.Dataset,
model_dataset: xr.Dataset,
time_steps,
issue_time,
issue_time: datetime,
steps_blending_config: StepsBlendingConfig,
):
"""Initializes the StepsBlendingNowcaster with inputs and configurations."""
# Store inputs
self.__precip = precip
self.__precip_models = precip_models
self.__velocity = velocity
self.__velocity_models = velocity_models
radar_precip_var = radar_dataset.attrs["precip_var"]
model_precip_var = model_dataset.attrs["precip_var"]
if issue_time != radar_dataset.time.isel(time=-1).values.astype(
"datetime64[us]"
).astype(datetime):
raise ValueError(
"Issue time should be equal to last timestep in radar dataset"
)
time_stepsize_seconds = radar_dataset.time.attrs["stepsize"]
if isinstance(time_steps, list):
# XR: validates this works or just remove the subtimestep stuff
timesteps_seconds = (
np.array(list(range(time_steps[-1] + 1))) * time_stepsize_seconds
)
else:
timesteps_seconds = (
np.array(list(range(time_steps + 1))) * time_stepsize_seconds
)
model_times = radar_dataset.time.isel(
time=-1
).values + timesteps_seconds.astype("timedelta64[s]")
model_dataset = model_dataset.sel(time=model_times)

self.__precip = radar_dataset[radar_precip_var].values
# XR: don't extract to dict but pass dataset
if model_dataset[model_precip_var].ndim == 5:
self.__precip_models = np.array(
[
[
{
"cascade_levels": model_dataset[model_precip_var]
.sel(time=time, ens_number=ens_number)
.values,
"means": model_dataset["means"]
.sel(time=time, ens_number=ens_number)
.values,
"stds": model_dataset["stds"]
.sel(time=time, ens_number=ens_number)
.values,
"domain": model_dataset[model_precip_var].attrs["domain"],
"normalized": model_dataset[model_precip_var].attrs[
"normalized"
],
"compact_output": model_dataset[model_precip_var].attrs[
"compact_output"
],
}
for time in model_dataset.time
]
for ens_number in model_dataset.ens_number
]
)
else:
self.__precip_models = model_dataset[model_precip_var].values
self.__velocity = np.array(
[radar_dataset["velocity_x"].values, radar_dataset["velocity_y"].values]
)
self.__velocity_models = np.array(
[model_dataset["velocity_x"].values, model_dataset["velocity_y"].values]
).transpose(1, 2, 0, 3, 4)
self.__original_timesteps = time_steps
self.__input_radar_dataset = radar_dataset
self.__timesteps = time_steps
self.__issuetime = issue_time

Expand All @@ -447,6 +506,7 @@ def compute_forecast(self):

Parameters
----------
# XR: fix docstring
precip: array-like
Array of shape (ar_order+1,m,n) containing the input precipitation fields
ordered by timestamp from oldest to newest. The time steps between the
Expand Down Expand Up @@ -545,7 +605,7 @@ def compute_forecast(self):

# Determine if rain is present in both radar and NWP fields
if self.__params.zero_precip_radar and self.__params.zero_precip_model_fields:
return self.__zero_precipitation_forecast()
result = self.__zero_precipitation_forecast()
else:
# Prepare the data for the zero precipitation radar case and initialize the noise correctly
if self.__params.zero_precip_radar:
Expand All @@ -572,16 +632,20 @@ def compute_forecast(self):
for j in range(self.__config.n_ens_members)
]
)
if self.__config.measure_time:
return (
self.__state.final_blended_forecast,
self.__init_time,
self.__mainloop_time,
)
else:
return self.__state.final_blended_forecast
result = self.__state.final_blended_forecast
else:
return None
result_dataset = convert_output_to_xarray_dataset(
self.__input_radar_dataset, self.__original_timesteps, result
)
if self.__config.measure_time:
return (
result_dataset,
self.__init_time,
self.__mainloop_time,
)
else:
return result_dataset

def __blended_nowcast_main_loop(self):
"""
Expand Down Expand Up @@ -2817,10 +2881,8 @@ def __measure_time(self, label, start_time):


def forecast(
precip,
precip_models,
velocity,
velocity_models,
radar_dataset,
model_dataset,
timesteps,
timestep,
issuetime,
Expand Down Expand Up @@ -2864,6 +2926,7 @@ def forecast(

Parameters
----------
# XR: fix docstring
precip: array-like
Array of shape (ar_order+1,m,n) containing the input precipitation fields
ordered by timestamp from oldest to newest. The time steps between the
Expand Down Expand Up @@ -3182,13 +3245,7 @@ def forecast(
"""
# Create an instance of the new class with all the provided arguments
blended_nowcaster = StepsBlendingNowcaster(
precip,
precip_models,
velocity,
velocity_models,
timesteps,
issuetime,
blending_config,
radar_dataset, model_dataset, timesteps, issuetime, blending_config
)

forecast_steps_nowcast = blended_nowcaster.compute_forecast()
Expand Down
Loading