ocf-data-sampler 0.2.14__tar.gz → 0.2.16__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 ocf-data-sampler might be problematic. Click here for more details.

Files changed (66) hide show
  1. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/PKG-INFO +1 -1
  2. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/load_dataset.py +11 -3
  3. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/datasets/pvnet_uk.py +1 -1
  4. ocf_data_sampler-0.2.16/ocf_data_sampler/torch_datasets/sample/uk_regional.py +262 -0
  5. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/utils/validation_utils.py +39 -0
  6. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler.egg-info/PKG-INFO +1 -1
  7. ocf_data_sampler-0.2.14/ocf_data_sampler/torch_datasets/sample/uk_regional.py +0 -176
  8. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/LICENSE +0 -0
  9. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/README.md +0 -0
  10. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/__init__.py +0 -0
  11. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/config/__init__.py +0 -0
  12. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/config/load.py +0 -0
  13. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/config/model.py +0 -0
  14. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/config/save.py +0 -0
  15. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/data/uk_gsp_locations.csv +0 -0
  16. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/__init__.py +0 -0
  17. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/gsp.py +0 -0
  18. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/nwp/__init__.py +0 -0
  19. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/nwp/nwp.py +0 -0
  20. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/nwp/providers/__init__.py +0 -0
  21. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/nwp/providers/cloudcasting.py +0 -0
  22. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/nwp/providers/ecmwf.py +0 -0
  23. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/nwp/providers/gfs.py +0 -0
  24. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/nwp/providers/icon.py +0 -0
  25. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/nwp/providers/ukv.py +0 -0
  26. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/nwp/providers/utils.py +0 -0
  27. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/satellite.py +0 -0
  28. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/site.py +0 -0
  29. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/load/utils.py +0 -0
  30. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/numpy_sample/__init__.py +0 -0
  31. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/numpy_sample/collate.py +0 -0
  32. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/numpy_sample/common_types.py +0 -0
  33. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/numpy_sample/datetime_features.py +0 -0
  34. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/numpy_sample/gsp.py +0 -0
  35. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/numpy_sample/nwp.py +0 -0
  36. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/numpy_sample/satellite.py +0 -0
  37. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/numpy_sample/site.py +0 -0
  38. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/numpy_sample/sun_position.py +0 -0
  39. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/select/__init__.py +0 -0
  40. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/select/dropout.py +0 -0
  41. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/select/fill_time_periods.py +0 -0
  42. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/select/find_contiguous_time_periods.py +0 -0
  43. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/select/geospatial.py +0 -0
  44. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/select/location.py +0 -0
  45. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/select/select_spatial_slice.py +0 -0
  46. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/select/select_time_slice.py +0 -0
  47. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/datasets/__init__.py +0 -0
  48. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/datasets/site.py +0 -0
  49. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/sample/__init__.py +0 -0
  50. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/sample/base.py +0 -0
  51. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/sample/site.py +0 -0
  52. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/utils/__init__.py +0 -0
  53. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/utils/channel_dict_to_dataarray.py +0 -0
  54. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/utils/merge_and_fill_utils.py +0 -0
  55. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/utils/spatial_slice_for_dataset.py +0 -0
  56. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/utils/time_slice_for_dataset.py +0 -0
  57. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/torch_datasets/utils/valid_time_periods.py +0 -0
  58. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler/utils.py +0 -0
  59. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler.egg-info/SOURCES.txt +0 -0
  60. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler.egg-info/dependency_links.txt +0 -0
  61. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler.egg-info/requires.txt +0 -0
  62. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/ocf_data_sampler.egg-info/top_level.txt +0 -0
  63. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/pyproject.toml +0 -0
  64. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/scripts/refactor_site.py +0 -0
  65. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/setup.cfg +0 -0
  66. {ocf_data_sampler-0.2.14 → ocf_data_sampler-0.2.16}/utils/compute_icon_mean_stddev.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ocf-data-sampler
3
- Version: 0.2.14
3
+ Version: 0.2.16
4
4
  Author: James Fulton, Peter Dudfield
5
5
  Author-email: Open Climate Fix team <info@openclimatefix.org>
6
6
  License: MIT License
@@ -6,20 +6,28 @@ from ocf_data_sampler.config import InputData
6
6
  from ocf_data_sampler.load import open_gsp, open_nwp, open_sat_data, open_site
7
7
 
8
8
 
9
- def get_dataset_dict(input_config: InputData) -> dict[str, dict[xr.DataArray] | xr.DataArray]:
9
+ def get_dataset_dict(input_config: InputData, gsp_ids: list[int] | None = None)\
10
+ -> dict[str, dict[xr.DataArray] | xr.DataArray]:
10
11
  """Construct dictionary of all of the input data sources.
11
12
 
12
13
  Args:
13
14
  input_config: InputData configuration object
15
+ gsp_ids: List of GSP IDs to load. If None, all GSPs are loaded (not National).
14
16
  """
15
17
  datasets_dict = {}
16
18
 
17
19
  # Load GSP data unless the path is None
18
20
  if input_config.gsp and input_config.gsp.zarr_path:
21
+
19
22
  da_gsp = open_gsp(zarr_path=input_config.gsp.zarr_path).compute()
20
23
 
21
- # Remove national GSP
22
- datasets_dict["gsp"] = da_gsp.sel(gsp_id=slice(1, None))
24
+ if gsp_ids is None:
25
+ # Remove national (gsp_id=0)
26
+ da_gsp = da_gsp.sel(gsp_id=slice(1, None))
27
+ else:
28
+ da_gsp = da_gsp.sel(gsp_id=gsp_ids)
29
+
30
+ datasets_dict["gsp"] = da_gsp
23
31
 
24
32
  # Load NWP data if in config
25
33
  if input_config.nwp:
@@ -95,7 +95,7 @@ class AbstractPVNetUKDataset(Dataset):
95
95
  gsp_ids: List of GSP IDs to create samples for. Defaults to all
96
96
  """
97
97
  config = load_yaml_configuration(config_filename)
98
- datasets_dict = get_dataset_dict(config.input_data)
98
+ datasets_dict = get_dataset_dict(config.input_data, gsp_ids=gsp_ids)
99
99
 
100
100
  # Get t0 times where all input data is available
101
101
  valid_t0_times = self.find_valid_t0_times(datasets_dict, config)
@@ -0,0 +1,262 @@
1
+ """PVNet UK Regional sample implementation for dataset handling and visualisation."""
2
+
3
+ import logging
4
+
5
+ import torch
6
+ from typing_extensions import override
7
+
8
+ from ocf_data_sampler.config import Configuration
9
+ from ocf_data_sampler.numpy_sample import (
10
+ GSPSampleKey,
11
+ NWPSampleKey,
12
+ SatelliteSampleKey,
13
+ )
14
+ from ocf_data_sampler.numpy_sample.common_types import NumpySample
15
+ from ocf_data_sampler.torch_datasets.sample.base import SampleBase
16
+ from ocf_data_sampler.torch_datasets.utils.validation_utils import (
17
+ calculate_expected_shapes,
18
+ check_dimensions,
19
+ validation_warning,
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class UKRegionalSample(SampleBase):
26
+ """Handles UK Regional PVNet data operations."""
27
+
28
+ def __init__(self, data: NumpySample) -> None:
29
+ """Initialises UK Regional sample with data."""
30
+ self._data = data
31
+
32
+ @override
33
+ def to_numpy(self) -> NumpySample:
34
+ """Returns the data as a NumPy sample."""
35
+ return self._data
36
+
37
+ @override
38
+ def save(self, path: str) -> None:
39
+ """Saves sample to the specified path in pickle format."""
40
+ # Saves to pickle format
41
+ torch.save(self._data, path)
42
+
43
+ @classmethod
44
+ @override
45
+ def load(cls, path: str) -> "UKRegionalSample":
46
+ """Loads sample from the specified path.
47
+
48
+ Args:
49
+ path: Path to the saved sample file.
50
+
51
+ Returns:
52
+ A UKRegionalSample instance with the loaded data.
53
+ """
54
+ # Loads from .pt format
55
+ # TODO: We should move away from using torch.load(..., weights_only=False)
56
+ return cls(torch.load(path, weights_only=False))
57
+
58
+ def validate_sample(self, config: Configuration) -> dict:
59
+ """Validates the sample, logging warnings and raising errors.
60
+
61
+ Checks that the sample has the expected structure and data shapes based
62
+ on the provided configuration. Critical issues (missing required data,
63
+ shape mismatches) will raise a ValueError. Non-critical issues (e.g.,
64
+ unexpected data components found) will be logged as warnings using
65
+ the standard Python logging module.
66
+
67
+ Args:
68
+ config: Configuration object defining expected shapes and required fields.
69
+
70
+ Returns:
71
+ dict: A dictionary indicating success: `{"valid": True}`.
72
+ If validation fails due to a critical issue, an exception is raised
73
+ instead of returning. Warnings encountered are logged.
74
+
75
+ Raises:
76
+ TypeError: If `config` is not a Configuration object.
77
+ ValueError: For critical validation failures like missing expected data,
78
+ incorrect data shapes, or missing required NWP providers.
79
+ """
80
+ if not isinstance(config, Configuration):
81
+ raise TypeError("config must be Configuration object")
82
+
83
+ # Calculate expected shapes from configuration
84
+ expected_shapes = calculate_expected_shapes(config)
85
+
86
+ # Check GSP shape if specified
87
+ gsp_key = GSPSampleKey.gsp
88
+ if gsp_key in expected_shapes and gsp_key not in self._data:
89
+ raise ValueError(f"Configuration expects GSP data ('{gsp_key}') but is missing.")
90
+
91
+ if gsp_key in self._data:
92
+ if gsp_key in expected_shapes:
93
+ gsp_data = self._data[gsp_key]
94
+ check_dimensions(
95
+ actual_shape=gsp_data.shape,
96
+ expected_shape=expected_shapes[gsp_key],
97
+ name="GSP",
98
+ )
99
+ else:
100
+ validation_warning(
101
+ message=f"GSP data ('{gsp_key}') is present but not expected in configuration.",
102
+ warning_type="unexpected_component",
103
+ component=str(gsp_key),
104
+ )
105
+
106
+ # Checks for NWP data
107
+ nwp_key = NWPSampleKey.nwp
108
+ if nwp_key in expected_shapes and nwp_key not in self._data:
109
+ raise ValueError(f"Configuration expects NWP data ('{nwp_key}') but is missing.")
110
+
111
+ if nwp_key in self._data:
112
+ nwp_data_all_providers = self._data[nwp_key]
113
+ if not isinstance(nwp_data_all_providers, dict):
114
+ raise ValueError(f"NWP data ('{nwp_key}') should be a dictionary.")
115
+
116
+ if nwp_key in expected_shapes:
117
+ expected_providers = set(expected_shapes[nwp_key].keys())
118
+ actual_providers = set(nwp_data_all_providers.keys())
119
+
120
+ unexpected_providers = actual_providers - expected_providers
121
+ if unexpected_providers:
122
+ validation_warning(
123
+ message=f"Unexpected NWP providers found: {list(unexpected_providers)}",
124
+ warning_type="unexpected_provider",
125
+ providers=list(unexpected_providers),
126
+ )
127
+
128
+ missing_expected_providers = expected_providers - actual_providers
129
+ if missing_expected_providers:
130
+ raise ValueError(
131
+ f"Expected NWP providers are missing from the data: "
132
+ f"{list(missing_expected_providers)}",
133
+ )
134
+
135
+ for provider in expected_shapes[nwp_key]:
136
+ provider_data = nwp_data_all_providers[provider]
137
+
138
+ if "nwp" not in provider_data:
139
+ error_msg = (
140
+ f"Missing array key 'nwp' in NWP data for provider '{provider}'."
141
+ )
142
+ raise ValueError(error_msg)
143
+
144
+ nwp_array = provider_data["nwp"]
145
+ check_dimensions(
146
+ actual_shape=nwp_array.shape,
147
+ expected_shape=expected_shapes[nwp_key][provider],
148
+ name=f"NWP data ({provider})",
149
+ )
150
+ else:
151
+ validation_warning(
152
+ message=(
153
+ f"NWP data ('{nwp_key}') is present but not expected "
154
+ "in configuration."
155
+ ),
156
+ warning_type="unexpected_component",
157
+ component=str(nwp_key),
158
+ )
159
+
160
+ # Validate satellite data
161
+ sat_key = SatelliteSampleKey.satellite_actual
162
+ if sat_key in expected_shapes and sat_key not in self._data:
163
+ raise ValueError(f"Configuration expects Satellite data ('{sat_key}') but is missing.")
164
+
165
+ if sat_key in self._data:
166
+ if sat_key in expected_shapes:
167
+ sat_data = self._data[sat_key]
168
+ check_dimensions(
169
+ actual_shape=sat_data.shape,
170
+ expected_shape=expected_shapes[sat_key],
171
+ name="Satellite data",
172
+ )
173
+ else:
174
+ validation_warning(
175
+ message=(
176
+ f"Satellite data ('{sat_key}') is present but not expected "
177
+ "in configuration."
178
+ ),
179
+ warning_type="unexpected_component",
180
+ component=str(sat_key),
181
+ )
182
+
183
+ # Validate solar coordinates data
184
+ solar_keys = ["solar_azimuth", "solar_elevation"]
185
+ for solar_key in solar_keys:
186
+ solar_name = solar_key.replace("_", " ").title()
187
+ if solar_key in expected_shapes and solar_key not in self._data:
188
+ raise ValueError(f"Configuration expects {solar_key} data but is missing.")
189
+
190
+ if solar_key in self._data:
191
+ if solar_key in expected_shapes:
192
+ solar_data = self._data[solar_key]
193
+ check_dimensions(
194
+ actual_shape=solar_data.shape,
195
+ expected_shape=expected_shapes[solar_key],
196
+ name=f"{solar_name} data",
197
+ )
198
+ else:
199
+ validation_warning(
200
+ message=(
201
+ f"{solar_name} data is present but not expected "
202
+ "in configuration."
203
+ ),
204
+ warning_type="unexpected_component",
205
+ component=solar_key,
206
+ )
207
+
208
+ # Check for potentially unexpected components
209
+ checked_keys = {gsp_key, nwp_key, sat_key} | set(solar_keys)
210
+ all_present_keys = set(self._data.keys())
211
+ unexpected_present_keys = all_present_keys - set(expected_shapes.keys())
212
+
213
+ for key in unexpected_present_keys:
214
+ if key not in checked_keys:
215
+ validation_warning(
216
+ message=(
217
+ f"Unexpected component '{key}' is present in data but not defined "
218
+ "in configuration's expected shapes."
219
+ ),
220
+ warning_type="unexpected_component",
221
+ component=str(key),
222
+ )
223
+
224
+ return {
225
+ "valid": True,
226
+ }
227
+
228
+
229
+ @override
230
+ def plot(self) -> None:
231
+ """Plots the sample data for visualization."""
232
+ from matplotlib import pyplot as plt
233
+
234
+ fig, axes = plt.subplots(2, 2, figsize=(12, 8))
235
+
236
+ if NWPSampleKey.nwp in self._data:
237
+ first_nwp = next(iter(self._data[NWPSampleKey.nwp].values()))
238
+ if "nwp" in first_nwp:
239
+ axes[0, 1].imshow(first_nwp["nwp"][0])
240
+ title = "NWP (First Channel)"
241
+ if NWPSampleKey.channel_names in first_nwp:
242
+ channel_names = first_nwp[NWPSampleKey.channel_names]
243
+ if channel_names:
244
+ title = f"NWP: {channel_names[0]}"
245
+ axes[0, 1].set_title(title)
246
+
247
+ if GSPSampleKey.gsp in self._data:
248
+ axes[0, 0].plot(self._data[GSPSampleKey.gsp])
249
+ axes[0, 0].set_title("GSP Generation")
250
+
251
+ if "solar_azimuth" in self._data and "solar_elevation" in self._data:
252
+ axes[1, 1].plot(self._data["solar_azimuth"], label="Azimuth")
253
+ axes[1, 1].plot(self._data["solar_elevation"], label="Elevation")
254
+ axes[1, 1].set_title("Solar Position")
255
+ axes[1, 1].legend()
256
+
257
+ if SatelliteSampleKey.satellite_actual in self._data:
258
+ axes[1, 0].imshow(self._data[SatelliteSampleKey.satellite_actual])
259
+ axes[1, 0].set_title("Satellite Data")
260
+
261
+ plt.tight_layout()
262
+ plt.show()
@@ -1,8 +1,13 @@
1
1
  """Validate sample shape against expected shape - utility function."""
2
2
 
3
+ import logging
4
+ from typing import Any
5
+
3
6
  from ocf_data_sampler.config import Configuration
4
7
  from ocf_data_sampler.numpy_sample import GSPSampleKey, NWPSampleKey, SatelliteSampleKey
5
8
 
9
+ logger = logging.getLogger(__name__)
10
+
6
11
 
7
12
  def check_dimensions(
8
13
  actual_shape: tuple[int, ...],
@@ -93,6 +98,40 @@ def calculate_expected_shapes(
93
98
  return expected_shapes
94
99
 
95
100
 
101
+ def validation_warning(
102
+ message: str,
103
+ warning_type: str,
104
+ *,
105
+ component: str | None = None,
106
+ providers: list[str] | None = None,
107
+ ) -> dict[str, Any]:
108
+ """Constructs warning details and logs a standard warning message.
109
+
110
+ Args:
111
+ message: The base warning message string.
112
+ warning_type: The category of the warning (e.g., 'unexpected_component').
113
+ component: Optional component identifier (e.g., 'gsp').
114
+ providers: Optional list of provider names (e.g., ['ukv']).
115
+
116
+ Returns:
117
+ None - This function now directly logs the warning.
118
+ """
119
+ warning_info: dict[str, Any] = {"type": warning_type, "message": message}
120
+ log_message_parts = [message]
121
+ log_message_parts.append(f"(Type: {warning_type}")
122
+
123
+ if component is not None:
124
+ warning_info["component"] = component
125
+ log_message_parts.append(f", Component: {component}")
126
+ if providers is not None:
127
+ warning_info["providers"] = providers
128
+ log_message_parts.append(f", Providers: {providers}")
129
+
130
+ log_message_parts.append(")")
131
+ log_message = " ".join(log_message_parts)
132
+ logger.warning(log_message)
133
+
134
+
96
135
  def _calculate_time_steps(start_minutes: int, end_minutes: int, resolution_minutes: int) -> int:
97
136
  """Calculate number of time steps based on interval and resolution.
98
137
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ocf-data-sampler
3
- Version: 0.2.14
3
+ Version: 0.2.16
4
4
  Author: James Fulton, Peter Dudfield
5
5
  Author-email: Open Climate Fix team <info@openclimatefix.org>
6
6
  License: MIT License
@@ -1,176 +0,0 @@
1
- """PVNet UK Regional sample implementation for dataset handling and visualisation."""
2
-
3
- import torch
4
- from typing_extensions import override
5
-
6
- from ocf_data_sampler.config import Configuration
7
- from ocf_data_sampler.numpy_sample import (
8
- GSPSampleKey,
9
- NWPSampleKey,
10
- SatelliteSampleKey,
11
- )
12
- from ocf_data_sampler.numpy_sample.common_types import NumpySample
13
- from ocf_data_sampler.torch_datasets.sample.base import SampleBase
14
- from ocf_data_sampler.torch_datasets.utils.validation_utils import (
15
- calculate_expected_shapes,
16
- check_dimensions,
17
- )
18
-
19
-
20
- class UKRegionalSample(SampleBase):
21
- """Handles UK Regional PVNet data operations."""
22
-
23
- def __init__(self, data: NumpySample) -> None:
24
- """Initialises UK Regional sample with data."""
25
- self._data = data
26
-
27
- @override
28
- def to_numpy(self) -> NumpySample:
29
- """Returns the data as a NumPy sample."""
30
- return self._data
31
-
32
- @override
33
- def save(self, path: str) -> None:
34
- """Saves sample to the specified path in pickle format."""
35
- # Saves to pickle format
36
- torch.save(self._data, path)
37
-
38
- @classmethod
39
- @override
40
- def load(cls, path: str) -> "UKRegionalSample":
41
- """Loads sample from the specified path.
42
-
43
- Args:
44
- path: Path to the saved sample file.
45
-
46
- Returns:
47
- A UKRegionalSample instance with the loaded data.
48
- """
49
- # Loads from .pt format
50
- # TODO: We should move away from using torch.load(..., weights_only=False)
51
- return cls(torch.load(path, weights_only=False))
52
-
53
- def validate_sample(self, config: Configuration) -> bool:
54
- """Validates that the sample has the expected structure and data shapes.
55
-
56
- Args:
57
- config: Configuration dict with expected shapes and required fields.
58
-
59
- Returns:
60
- bool: True if validation passes, otherwise raises an exception.
61
- """
62
- if not isinstance(config, Configuration):
63
- raise TypeError("config must be Configuration object")
64
-
65
- # Calculate expected shapes from configuration
66
- expected_shapes = calculate_expected_shapes(config)
67
-
68
- # Check GSP shape if specified
69
- gsp_key = GSPSampleKey.gsp
70
- # Check if GSP data is expected but missing
71
- if gsp_key in expected_shapes and gsp_key not in self._data:
72
- raise ValueError(f"Configuration expects GSP data ('{gsp_key}') but is missing.")
73
-
74
- # Check GSP shape if data exists and is expected
75
- if gsp_key in self._data and gsp_key in expected_shapes:
76
- gsp_data = self._data[gsp_key]
77
- check_dimensions(
78
- actual_shape=gsp_data.shape,
79
- expected_shape=expected_shapes[gsp_key],
80
- name="GSP",
81
- )
82
-
83
- # Checks for NWP data - nested structure
84
- nwp_key = NWPSampleKey.nwp
85
- if nwp_key in expected_shapes and nwp_key not in self._data:
86
- raise ValueError(f"Configuration expects NWP data ('{nwp_key}') but is missing.")
87
-
88
- # Check NWP structure and shapes if data exists
89
- if nwp_key in self._data:
90
- nwp_data_all_providers = self._data[nwp_key]
91
- if not isinstance(nwp_data_all_providers, dict):
92
- raise ValueError(f"NWP data ('{nwp_key}') should be a dictionary.")
93
-
94
- # Loop through providers present in actual data
95
- for provider, provider_data in nwp_data_all_providers.items():
96
- if "nwp" not in provider_data:
97
- raise ValueError(f"Missing array key in NWP data for provider '{provider}'.")
98
-
99
- if nwp_key in expected_shapes and provider in expected_shapes[nwp_key]:
100
- nwp_array = provider_data["nwp"]
101
- actual_shape = nwp_array.shape
102
- expected_shape = expected_shapes[nwp_key][provider]
103
-
104
- check_dimensions(
105
- actual_shape=actual_shape,
106
- expected_shape=expected_shape,
107
- name=f"NWP data ({provider})",
108
- )
109
-
110
- # Validate satellite data
111
- sat_key = SatelliteSampleKey.satellite_actual
112
- # Check if Satellite data is expected but missing
113
- if sat_key in expected_shapes and sat_key not in self._data:
114
- raise ValueError(f"Configuration expects Satellite data ('{sat_key}') but is missing.")
115
-
116
- # Check satellite shape if data exists and is expected
117
- if sat_key in self._data and sat_key in expected_shapes:
118
- sat_data = self._data[sat_key]
119
- check_dimensions(
120
- actual_shape=sat_data.shape,
121
- expected_shape=expected_shapes[sat_key],
122
- name="Satellite data",
123
- )
124
-
125
- # Validate solar coordinates data
126
- solar_keys = ["solar_azimuth", "solar_elevation"]
127
- # Check if solar coordinate is expected but missing
128
- for solar_key in solar_keys:
129
- if solar_key in expected_shapes and solar_key not in self._data:
130
- raise ValueError(f"Configuration expects {solar_key} data but is missing.")
131
-
132
- # Check solar coordinate shape if data exists and is expected
133
- if solar_key in self._data and solar_key in expected_shapes:
134
- solar_data = self._data[solar_key]
135
- check_dimensions(
136
- actual_shape=solar_data.shape,
137
- expected_shape=expected_shapes[solar_key],
138
- name=f"{solar_key.replace('_', ' ').title()} data",
139
- )
140
-
141
- return True
142
-
143
- @override
144
- def plot(self) -> None:
145
- """Plots the sample data for visualization."""
146
- from matplotlib import pyplot as plt
147
-
148
- fig, axes = plt.subplots(2, 2, figsize=(12, 8))
149
-
150
- if NWPSampleKey.nwp in self._data:
151
- first_nwp = next(iter(self._data[NWPSampleKey.nwp].values()))
152
- if "nwp" in first_nwp:
153
- axes[0, 1].imshow(first_nwp["nwp"][0])
154
- title = "NWP (First Channel)"
155
- if NWPSampleKey.channel_names in first_nwp:
156
- channel_names = first_nwp[NWPSampleKey.channel_names]
157
- if channel_names:
158
- title = f"NWP: {channel_names[0]}"
159
- axes[0, 1].set_title(title)
160
-
161
- if GSPSampleKey.gsp in self._data:
162
- axes[0, 0].plot(self._data[GSPSampleKey.gsp])
163
- axes[0, 0].set_title("GSP Generation")
164
-
165
- if "solar_azimuth" in self._data and "solar_elevation" in self._data:
166
- axes[1, 1].plot(self._data["solar_azimuth"], label="Azimuth")
167
- axes[1, 1].plot(self._data["solar_elevation"], label="Elevation")
168
- axes[1, 1].set_title("Solar Position")
169
- axes[1, 1].legend()
170
-
171
- if SatelliteSampleKey.satellite_actual in self._data:
172
- axes[1, 0].imshow(self._data[SatelliteSampleKey.satellite_actual])
173
- axes[1, 0].set_title("Satellite Data")
174
-
175
- plt.tight_layout()
176
- plt.show()