grdwindinversion 0.2.7__py3-none-any.whl → 0.3.2__py3-none-any.whl

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.
@@ -1,6 +1,9 @@
1
1
  no_subdir: True
2
2
  winddir_convention: "meteorological"
3
+ add_gradientsfeatures: False
4
+ add_nrcs_model: False
3
5
  S1A:
6
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
4
7
  GMF_VV_NAME: "gmf_cmod5n"
5
8
  GMF_VH_NAME: "gmf_s1_v2"
6
9
  dsig_VH_NAME: "gmf_s1_v2"
@@ -12,6 +15,7 @@ S1A:
12
15
  phi_step: 1.0
13
16
  resolution: "high"
14
17
  S1B:
18
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
15
19
  GMF_VV_NAME: "gmf_cmod5n"
16
20
  GMF_VH_NAME: "gmf_s1_v2"
17
21
  dsig_VH_NAME: "gmf_s1_v2"
@@ -23,6 +27,7 @@ S1B:
23
27
  phi_step: 1.0
24
28
  resolution: "high"
25
29
  RS2:
30
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
26
31
  GMF_VV_NAME: "gmf_cmod5n"
27
32
  GMF_VH_NAME: "gmf_rs2_v2"
28
33
  dsig_VH_NAME: "gmf_rs2_v2"
@@ -34,6 +39,7 @@ RS2:
34
39
  phi_step: 1.0
35
40
  resolution: "high"
36
41
  RCM:
42
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
37
43
  GMF_VV_NAME: "gmf_cmod5n"
38
44
  GMF_VH_NAME: "gmf_rcm_noaa"
39
45
  dsig_VH_NAME: "gmf_s1_v2"
@@ -1,16 +1,18 @@
1
1
  no_subdir: True
2
2
  S1A:
3
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
3
4
  GMF_VV_NAME: "gmf_cmod5n"
4
5
  GMF_VH_NAME: "gmf_s1_v2"
5
6
  dsig_VH_NAME: "gmf_s1_v2"
6
7
  apply_flattening: True
7
8
  recalibration: True
8
9
  ancillary: "ecmwf"
9
- inc_step: 0.1
10
- wspd_step: 0.1
11
- phi_step: 1.0
10
+ inc_step: 0.3
11
+ wspd_step: 0.3
12
+ phi_step: 2.0
12
13
  resolution: "high"
13
14
  S1B:
15
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
14
16
  GMF_VV_NAME: "gmf_cmod5n"
15
17
  GMF_VH_NAME: "gmf_s1_v2"
16
18
  dsig_VH_NAME: "gmf_s1_v2"
@@ -22,6 +24,7 @@ S1B:
22
24
  phi_step: 1.0
23
25
  resolution: "high"
24
26
  RS2:
27
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
25
28
  GMF_VV_NAME: "gmf_cmod5n"
26
29
  GMF_VH_NAME: "gmf_rs2_v2"
27
30
  dsig_VH_NAME: "gmf_rs2_v2"
@@ -33,6 +36,7 @@ RS2:
33
36
  phi_step: 1.0
34
37
  resolution: "high"
35
38
  RCM:
39
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
36
40
  GMF_VV_NAME: "gmf_cmod5n"
37
41
  GMF_VH_NAME: "gmf_rcm_noaa"
38
42
  dsig_VH_NAME: "gmf_s1_v2"
@@ -0,0 +1,48 @@
1
+ no_subdir: True
2
+ winddir_convention: "meteorological"
3
+ add_gradientsfeatures: True
4
+ add_nrcs_model: True
5
+ S1A:
6
+ GMF_VV_NAME: "gmf_cmod5n"
7
+ GMF_VH_NAME: "gmf_s1_v2"
8
+ dsig_VH_NAME: "gmf_s1_v2"
9
+ apply_flattening: True
10
+ recalibration: True
11
+ ancillary: "ecmwf"
12
+ inc_step: 0.1
13
+ wspd_step: 0.1
14
+ phi_step: 1.0
15
+ resolution: "high"
16
+ S1B:
17
+ GMF_VV_NAME: "gmf_cmod5n"
18
+ GMF_VH_NAME: "gmf_s1_v2"
19
+ dsig_VH_NAME: "gmf_s1_v2"
20
+ apply_flattening: True
21
+ recalibration: True
22
+ ancillary: "ecmwf"
23
+ inc_step: 0.1
24
+ wspd_step: 0.1
25
+ phi_step: 1.0
26
+ resolution: "high"
27
+ RS2:
28
+ GMF_VV_NAME: "gmf_cmod5n"
29
+ GMF_VH_NAME: "gmf_rs2_v2"
30
+ dsig_VH_NAME: "gmf_rs2_v2"
31
+ apply_flattening: False
32
+ recalibration: True
33
+ ancillary: "ecmwf"
34
+ inc_step: 0.1
35
+ wspd_step: 0.1
36
+ phi_step: 1.0
37
+ resolution: "high"
38
+ RCM:
39
+ GMF_VV_NAME: "gmf_cmod5n"
40
+ GMF_VH_NAME: "gmf_rcm_noaa"
41
+ dsig_VH_NAME: "gmf_s1_v2"
42
+ apply_flattening: True
43
+ recalibration: True
44
+ ancillary: "ecmwf"
45
+ inc_step: 0.1
46
+ wspd_step: 0.1
47
+ phi_step: 1.0
48
+ resolution: "high"
@@ -0,0 +1,52 @@
1
+ no_subdir: True
2
+ winddir_convention: "meteorological"
3
+ add_gradientsfeatures: True
4
+ add_nrcs_model: False
5
+ S1A:
6
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
7
+ GMF_VV_NAME: "gmf_cmod5n"
8
+ GMF_VH_NAME: "gmf_s1_v2"
9
+ dsig_VH_NAME: "gmf_s1_v2"
10
+ apply_flattening: True
11
+ recalibration: False
12
+ ancillary: "ecmwf"
13
+ inc_step: 0.1
14
+ wspd_step: 0.1
15
+ phi_step: 1.0
16
+ resolution: "high"
17
+ S1B:
18
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
19
+ GMF_VV_NAME: "gmf_cmod5n"
20
+ GMF_VH_NAME: "gmf_s1_v2"
21
+ dsig_VH_NAME: "gmf_s1_v2"
22
+ apply_flattening: True
23
+ recalibration: False
24
+ ancillary: "ecmwf"
25
+ inc_step: 0.1
26
+ wspd_step: 0.1
27
+ phi_step: 1.0
28
+ resolution: "high"
29
+ RS2:
30
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
31
+ GMF_VV_NAME: "gmf_cmod5n"
32
+ GMF_VH_NAME: "gmf_rs2_v2"
33
+ dsig_VH_NAME: "gmf_rs2_v2"
34
+ apply_flattening: False
35
+ recalibration: False
36
+ ancillary: "ecmwf"
37
+ inc_step: 0.1
38
+ wspd_step: 0.1
39
+ phi_step: 1.0
40
+ resolution: "high"
41
+ RCM:
42
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
43
+ GMF_VV_NAME: "gmf_cmod5n"
44
+ GMF_VH_NAME: "gmf_rcm_noaa"
45
+ dsig_VH_NAME: "gmf_s1_v2"
46
+ apply_flattening: True
47
+ recalibration: False
48
+ ancillary: "ecmwf"
49
+ inc_step: 0.1
50
+ wspd_step: 0.1
51
+ phi_step: 1.0
52
+ resolution: "high"
@@ -0,0 +1,52 @@
1
+ no_subdir: True
2
+ winddir_convention: "meteorological"
3
+ add_gradientsfeatures: True
4
+ add_nrcs_model: True
5
+ S1A:
6
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
7
+ GMF_VV_NAME: "gmf_cmod5n"
8
+ GMF_VH_NAME: "gmf_s1_v2"
9
+ dsig_VH_NAME: "gmf_s1_v2"
10
+ apply_flattening: True
11
+ recalibration: False
12
+ ancillary: "ecmwf"
13
+ inc_step: 0.1
14
+ wspd_step: 0.1
15
+ phi_step: 1.0
16
+ resolution: "high"
17
+ S1B:
18
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
19
+ GMF_VV_NAME: "gmf_cmod5n"
20
+ GMF_VH_NAME: "gmf_s1_v2"
21
+ dsig_VH_NAME: "gmf_s1_v2"
22
+ apply_flattening: True
23
+ recalibration: False
24
+ ancillary: "ecmwf"
25
+ inc_step: 0.1
26
+ wspd_step: 0.1
27
+ phi_step: 1.0
28
+ resolution: "high"
29
+ RS2:
30
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
31
+ GMF_VV_NAME: "gmf_cmod5n"
32
+ GMF_VH_NAME: "gmf_rs2_v2"
33
+ dsig_VH_NAME: "gmf_rs2_v2"
34
+ apply_flattening: False
35
+ recalibration: False
36
+ ancillary: "ecmwf"
37
+ inc_step: 0.1
38
+ wspd_step: 0.1
39
+ phi_step: 1.0
40
+ resolution: "high"
41
+ RCM:
42
+ GMF_HH_NAME: "nc_lut_gmf_cmod5n_Rhigh_hh_mouche1"
43
+ GMF_VV_NAME: "gmf_cmod5n"
44
+ GMF_VH_NAME: "gmf_rcm_noaa"
45
+ dsig_VH_NAME: "gmf_s1_v2"
46
+ apply_flattening: True
47
+ recalibration: False
48
+ ancillary: "ecmwf"
49
+ inc_step: 0.1
50
+ wspd_step: 0.1
51
+ phi_step: 1.0
52
+ resolution: "high"
@@ -1,5 +1,8 @@
1
- #'ecmwf_0100_1h': '../ecmwf/forecast/hourly/0100deg/netcdf_light_REPRO_tree/%Y/%j/ECMWF_FORECAST_0100_%Y%m%d%H%M_10U_10V.nc'
2
- #'ecmwf_0125_1h': '../ecmwf/0.125deg/1h/forecasts/%Y/%j/ecmwf_%Y%m%d%H%M.nc'
1
+ 'ecmwf_0100_1h': '../ecmwf/forecast/hourly/0100deg/netcdf_light_REPRO_tree/%Y/%j/ECMWF_FORECAST_0100_%Y%m%d%H%M_10U_10V.nc'
2
+ 'ecmwf_0125_1h': '../ecmwf/0.125deg/1h/forecasts/%Y/%j/ecmwf_%Y%m%d%H%M.nc'
3
+
4
+
5
+ unit_test_s1_product: './sentinel-1a/L1/IW/S1A_IW_GRDH_1S/2021/252/S1A_IW_GRDH_1SDV_20210909T130650_20210909T130715_039605_04AE83_C34F.SAFE'
6
+ unit_test_rcm_product: './l1/rcm/rcm-1/sclnd/2023/273/RCM1_OK2767220_PK2769320_1_SCLND_20230930_214014_VV_VH_GRD'
7
+ unit_test_rs2_product: './L1/VV_VH/2022/247/RS2_OK141302_PK1242223_DK1208537_SCWA_20220904_093402_VV_VH_SGF'
3
8
 
4
- 'ecmwf_0100_1h': '/home/datawork-cersat-public/provider/ecmwf/forecast/hourly/0100deg/netcdf_light_REPRO_tree/%Y/%j/ECMWF_FORECAST_0100_%Y%m%d%H%M_10U_10V.nc'
5
- 'ecmwf_0125_1h': '/home/datawork-cersat-intranet/project/ecmwf/0.125deg/1h/forecasts/%Y/%j/ecmwf_%Y%m%d%H%M.nc'
@@ -0,0 +1,448 @@
1
+ import xsarsea.gradients
2
+ import cv2
3
+ import xarray as xr
4
+ import xarray as xr
5
+ from scipy.ndimage import binary_dilation
6
+ import numpy as np
7
+ import logging
8
+
9
+ import logging
10
+ logger = logging.getLogger('grdwindinversion.gradientFeatures')
11
+ logger.addHandler(logging.NullHandler())
12
+
13
+
14
+ class GradientFeatures:
15
+ def __init__(self, xr_dataset, xr_dataset_100, windows_sizes, downscales_factors, window_step=1):
16
+ """
17
+ Initialize variables and xsarsea.gradients.Gradients.
18
+
19
+ Parameters
20
+ ----------
21
+ xr_dataset : xarray.Dataset
22
+ xarray.Dataset containing the SAR data.
23
+ xr_dataset_100 : xarray.Dataset
24
+ xarray.Dataset containing the 100m resolution SAR data.
25
+ windows_sizes : list
26
+ List of window sizes for gradient computation.
27
+ downscales_factors : list
28
+ List of downscale factors for gradient computation.
29
+ window_step : int
30
+ Step size for the window (default is 1).
31
+
32
+ Returns
33
+ -------
34
+ None
35
+ """
36
+ self.xr_dataset = xr_dataset
37
+ self.xr_dataset_100 = xr_dataset_100
38
+ self.windows_sizes = windows_sizes
39
+ self.downscales_factors = downscales_factors
40
+ self.window_step = window_step
41
+ self.gradients = None
42
+ self.hist = None
43
+ self._compute_gradients()
44
+
45
+ def _compute_gradients(self):
46
+ """
47
+ Instantiate the gradients object and compute the histogram.
48
+
49
+ Parameters
50
+ ----------
51
+ None
52
+
53
+ Returns
54
+ -------
55
+ None
56
+
57
+ """
58
+ self.gradients = xsarsea.gradients.Gradients(
59
+ self.xr_dataset_100['sigma0_detrend'],
60
+ windows_sizes=self.windows_sizes,
61
+ downscales_factors=self.downscales_factors,
62
+ window_step=self.window_step
63
+ )
64
+ self.hist = self.gradients.histogram
65
+ # Get orthogonal gradients
66
+ self.hist['angles'] = self.hist['angles'] + np.pi / 2
67
+
68
+ def get_heterogeneity_mask(self, config):
69
+ """
70
+ Compute the heterogeneity mask.
71
+
72
+ Parameters
73
+ ----------
74
+ config : dict
75
+ Configuration parameters.
76
+
77
+ Returns
78
+ -------
79
+ dict
80
+ Dictionary containing the dataArrays related toheterogeneity mask.
81
+
82
+ """
83
+ dual_pol = config["l2_params"]["dual_pol"]
84
+
85
+ new_dataArrays = {}
86
+
87
+ try:
88
+
89
+ sigma0_400_co = [da.sigma0 for da in self.gradients.gradients_list if (
90
+ da.sigma0["pol"] == config["l2_params"]["copol"] and da.sigma0.downscale_factor == 4)][0]
91
+ sigs = [sigma0_400_co]
92
+
93
+ if dual_pol:
94
+ sigma0_800_cross = [da.sigma0 for da in self.gradients.gradients_list if (
95
+ da.sigma0["pol"] == config["l2_params"]["crosspol"] and da.sigma0.downscale_factor == 8)][0]
96
+ sigs.append(sigma0_800_cross)
97
+
98
+ filters = {}
99
+ for sig in sigs:
100
+
101
+ pol = sig["pol"].values
102
+ res = 100 * sig.downscale_factor.values
103
+
104
+ # delete useless coords : could be problematic to have it later
105
+ if 'downscale_factor' in sig.coords:
106
+ sig = sig.reset_coords("downscale_factor", drop=True)
107
+
108
+ if 'window_size' in sig.coords:
109
+ sig = sig.reset_coords("window_size", drop=True)
110
+ # mask
111
+ sig = xr.where(sig <= 0, 1e-15, sig)
112
+
113
+ # map incidence for detrend
114
+ incidence = xr.DataArray(data=cv2.resize(
115
+ self.xr_dataset_100.incidence.values, sig.shape[::-1], cv2.INTER_NEAREST), dims=sig.dims, coords=sig.coords)
116
+
117
+ sigma0_detrend = xsarsea.sigma0_detrend(sig, incidence)
118
+
119
+ filter_name = str(res)+"_"+str(pol)
120
+ I = sigma0_detrend
121
+ f1, f2, f3, f4, f = xsarsea.gradients.filtering_parameters(I)
122
+ filters[filter_name] = f
123
+
124
+ thresholds = [0.78] # < is unusable
125
+ if dual_pol:
126
+ # Seuil pour crosspol si dual_pol est activé
127
+ thresholds.append(0.71)
128
+
129
+ for idx_filter, filter in enumerate(filters):
130
+ # interp to user resolution and map on dataset grid
131
+ new_dataArrays[filter] = filters[filter].interp(
132
+ line=self.xr_dataset.line, sample=self.xr_dataset.sample, method="nearest")
133
+ new_dataArrays[filter+"_mask"] = xr.where(
134
+ new_dataArrays[filter] > thresholds[idx_filter], True, False)
135
+
136
+ varname_400_copol_mask = f'400_{config["l2_params"]["copol"]}_mask'
137
+ varname_800_crosspol_mask = f'800_{config["l2_params"]["crosspol"]}_mask'
138
+
139
+ # Cas 0 : no heterogeneity
140
+ new_dataArrays["heterogeneity_mask"] = xr.full_like(
141
+ new_dataArrays[varname_400_copol_mask], 0)
142
+
143
+ if dual_pol:
144
+ # Cas 3 : Dual-polarization
145
+ new_dataArrays["heterogeneity_mask"] = xr.where(
146
+ new_dataArrays[varname_400_copol_mask] & new_dataArrays[varname_800_crosspol_mask], 3, new_dataArrays["heterogeneity_mask"])
147
+
148
+ # Cas 1 : Co-polarization only
149
+ new_dataArrays["heterogeneity_mask"] = xr.where(
150
+ new_dataArrays[varname_400_copol_mask] & ~new_dataArrays[varname_800_crosspol_mask], 1, new_dataArrays["heterogeneity_mask"])
151
+
152
+ # Cas 2 : Cross-polarization only
153
+ new_dataArrays["heterogeneity_mask"] = xr.where(
154
+ ~new_dataArrays[varname_400_copol_mask] & new_dataArrays[varname_800_crosspol_mask], 2, new_dataArrays["heterogeneity_mask"])
155
+
156
+ # Attributes
157
+ new_dataArrays["heterogeneity_mask"].attrs["valid_range"] = np.array([
158
+ 0, 3])
159
+ new_dataArrays["heterogeneity_mask"].attrs["flag_values"] = np.array([
160
+ 0, 1, 2, 3])
161
+ new_dataArrays["heterogeneity_mask"].attrs["flag_meanings"] = (
162
+ "homogeneous_NRCS, heterogeneous_from_co-polarization_NRCS, "
163
+ "heterogeneous_from_cross-polarization_NRCS, heterogeneous_from_dual-polarization_NRCS"
164
+ )
165
+ else:
166
+ # no crosspol
167
+ new_dataArrays["heterogeneity_mask"] = xr.where(
168
+ new_dataArrays[varname_400_copol_mask], 1, new_dataArrays["heterogeneity_mask"])
169
+
170
+ # Attributs pour le cas single-pol
171
+ new_dataArrays["heterogeneity_mask"].attrs["valid_range"] = np.array([
172
+ 0, 1])
173
+ new_dataArrays["heterogeneity_mask"].attrs["flag_values"] = np.array([
174
+ 0, 1])
175
+ new_dataArrays["heterogeneity_mask"].attrs["flag_meanings"] = (
176
+ "homogeneous_NRCS, heterogeneous_from_co-polarization_NRCS"
177
+ )
178
+
179
+ # Attributs généraux
180
+ new_dataArrays["heterogeneity_mask"].attrs["long_name"] = "Quality flag taking into account the local heterogeneity"
181
+ return new_dataArrays
182
+
183
+ except Exception as e:
184
+ logging.error("Error in get_heterogeneity_mask: %s", e)
185
+
186
+ new_dataArrays["heterogeneity_mask"] = xr.DataArray(data=np.nan * np.ones([len(self.xr_dataset.coords[dim]) for dim in ['line', 'sample']]),
187
+ dims=[
188
+ 'line', 'sample'],
189
+ coords=[self.xr_dataset.coords[dim]
190
+ for dim in ['line', 'sample']],
191
+ attrs={"comment": "no heterogeneity mask found"})
192
+
193
+ return new_dataArrays
194
+
195
+ def _remove_ambiguity(self, streaks):
196
+ """
197
+ Remove direction ambiguity using ancillary wind data.
198
+
199
+ Parameters
200
+ ----------
201
+ streaks : xarray.Dataset
202
+ Dataset containing the streaks.
203
+
204
+ Returns
205
+ -------
206
+ xarray.Dataset
207
+ Dataset containing the streaks with ambiguity removed.
208
+ """
209
+
210
+ # Load ancillary wind in antenna convention
211
+ ancillary_wind = self.xr_dataset['ancillary_wind'].interp(
212
+ line=streaks.line,
213
+ sample=streaks.sample,
214
+ method='nearest'
215
+ ).compute()
216
+
217
+ # Convert angles to complex numbers
218
+ streaks_c = streaks['weight'] * np.exp(1j * streaks['angle'])
219
+ # Calculate the difference in angle
220
+ diff_angle = xr.apply_ufunc(np.angle, ancillary_wind / streaks_c)
221
+
222
+ # Remove ambiguity
223
+ streaks_c = xr.where(np.abs(diff_angle) > np.pi /
224
+ 2, -streaks_c, streaks_c)
225
+
226
+ # Update streaks with corrected values
227
+ streaks['weight'] = np.abs(streaks_c)
228
+ streaks['angle'] = xr.apply_ufunc(np.angle, streaks_c)
229
+ return streaks
230
+
231
+ def convert_to_meteo_convention(self, streaks):
232
+ """
233
+ Convert wind direction to meteorological convention by creating a new 'angle' DataArray.
234
+
235
+ Parameters
236
+ ----------
237
+ streaks : xarray.Dataset
238
+ Dataset containing the streaks.
239
+
240
+ Returns
241
+ -------
242
+ xarray.Dataset
243
+ Dataset containing the streaks with wind direction in meteorological convention.
244
+
245
+ """
246
+ streaks_meteo = self.xr_dataset[['longitude', 'latitude', 'ground_heading', 'ancillary_wind']].interp(
247
+ line=streaks.line,
248
+ sample=streaks.sample,
249
+ method='nearest')
250
+
251
+ streaks_meteo['angle'] = xsarsea.dir_sample_to_meteo(
252
+ np.rad2deg(streaks['angle']), streaks_meteo['ground_heading'])
253
+ streaks_meteo['angle'].attrs[
254
+ 'winddir_convention'] = "Wind direction in meteorological convention (clockwise, from), ex: 0°=from north, 90°=from east"
255
+
256
+ return streaks_meteo
257
+
258
+ def streaks_smooth_mean(self):
259
+ """
260
+ Compute streaks by smoothing the histograms first and then computing the mean.
261
+
262
+ Parameters
263
+ ----------
264
+ None
265
+
266
+ Returns
267
+ -------
268
+ xarray.DataArray
269
+ DataArray containing the streaks.
270
+ """
271
+
272
+ try:
273
+ hist_smooth = self.hist.copy()
274
+ hist_smooth['weight'] = xsarsea.gradients.circ_smooth(
275
+ hist_smooth['weight'])
276
+
277
+ # Compute the mean across 'downscale_factor', 'window_size', and 'pol'
278
+ hist_smooth_mean = hist_smooth.mean(
279
+ ['downscale_factor', 'window_size', 'pol'])
280
+
281
+ # Select histogram peak
282
+ iangle_smooth_mean = hist_smooth_mean['weight'].fillna(
283
+ 0).argmax(dim='angles')
284
+ streaks_dir_smooth_mean = hist_smooth_mean['angles'].isel(
285
+ angles=iangle_smooth_mean)
286
+ streaks_weight_smooth_mean = hist_smooth_mean['weight'].isel(
287
+ angles=iangle_smooth_mean)
288
+
289
+ # Combine angles and weights into a dataset
290
+ streaks_smooth_mean = xr.Dataset({
291
+ 'angle': streaks_dir_smooth_mean,
292
+ 'weight': streaks_weight_smooth_mean
293
+ })
294
+
295
+ # Remove 'angles' coordinate
296
+ streaks_smooth_mean = streaks_smooth_mean.reset_coords(
297
+ 'angles', drop=True)
298
+
299
+ # Remove ambiguity with ancillary wind
300
+ streaks_smooth_mean = self._remove_ambiguity(
301
+ streaks_smooth_mean)
302
+
303
+ # Convert to meteo convention
304
+ streaks_smooth_mean = self.convert_to_meteo_convention(
305
+ streaks_smooth_mean)
306
+
307
+ # Set attributes
308
+ streaks_smooth_mean['angle'].attrs['description'] = 'Wind direction estimated from local gradient; histograms smoothed first, then mean computed'
309
+
310
+ return streaks_smooth_mean
311
+
312
+ except Exception as e:
313
+ logging.error("Error in streaks_smooth_mean: %s", e)
314
+
315
+ streaks_dir_smooth_mean_interp = xr.DataArray(data=np.nan * np.ones([len(self.xr_dataset.coords[dim]) for dim in ['line', 'sample']]),
316
+ dims=[
317
+ 'line', 'sample'],
318
+ coords=[self.xr_dataset.coords[dim]
319
+ for dim in ['line', 'sample']],
320
+ attrs={"comment": "no streaks_smooth_mean found"})
321
+
322
+ return streaks_dir_smooth_mean_interp
323
+
324
+ def streaks_mean_smooth(self):
325
+ """
326
+ Compute streaks by meaning the histograms first and then smoothing.
327
+
328
+ Parameters
329
+ ----------
330
+ None
331
+
332
+ Returns
333
+ -------
334
+ xarray.DataArray
335
+ DataArray containing the streaks.
336
+ """
337
+ try:
338
+ # Compute the mean of the histograms
339
+ hist_mean = self.hist.copy().mean(
340
+ ['downscale_factor', 'window_size', 'pol'])
341
+
342
+ # Smooth the mean histogram
343
+ hist_mean_smooth = hist_mean.copy()
344
+ hist_mean_smooth['weight'] = xsarsea.gradients.circ_smooth(
345
+ hist_mean['weight'])
346
+
347
+ # Select histogram peak
348
+ iangle_mean_smooth = hist_mean_smooth['weight'].fillna(
349
+ 0).argmax(dim='angles')
350
+ streaks_dir_mean_smooth = hist_mean_smooth['angles'].isel(
351
+ angles=iangle_mean_smooth)
352
+ streaks_weight_mean_smooth = hist_mean_smooth['weight'].isel(
353
+ angles=iangle_mean_smooth)
354
+
355
+ # Combine angles and weights into a dataset
356
+ streaks_mean_smooth = xr.Dataset({
357
+ 'angle': streaks_dir_mean_smooth,
358
+ 'weight': streaks_weight_mean_smooth
359
+ })
360
+
361
+ # Remove 'angles' coordinate
362
+ streaks_mean_smooth = streaks_mean_smooth.reset_coords(
363
+ 'angles', drop=True)
364
+
365
+ # Remove ambiguity with ancillary wind
366
+ streaks_mean_smooth = self._remove_ambiguity(
367
+ streaks_mean_smooth)
368
+
369
+ # Convert to meteo convention
370
+ streaks_mean_smooth = self.convert_to_meteo_convention(
371
+ streaks_mean_smooth)
372
+
373
+ # Set attributes
374
+ streaks_mean_smooth['angle'].attrs['description'] = 'Wind direction estimated from local gradient; histograms mean first, then smooth computed'
375
+
376
+ return streaks_mean_smooth
377
+
378
+ except Exception as e:
379
+ logging.error("Error in streaks_mean_smooth: %s", e)
380
+
381
+ streaks_mean_smooth = xr.DataArray(data=np.nan * np.ones([len(self.xr_dataset.coords[dim]) for dim in ['line', 'sample']]),
382
+ dims=[
383
+ 'line', 'sample'],
384
+ coords=[self.xr_dataset.coords[dim]
385
+ for dim in ['line', 'sample']],
386
+ attrs={"comment": "no streaks_mean_smooth found"})
387
+
388
+ return streaks_mean_smooth
389
+
390
+ def streaks_individual(self):
391
+ """
392
+ Compute streaks by smoothing the histogram.
393
+
394
+ Parameters
395
+ ----------
396
+ None
397
+
398
+ Returns
399
+ -------
400
+ xarray.DataArray
401
+ DataArray containing the individual streaks for each window_size, downscale_factor, polarisation (no combination).
402
+ """
403
+ try:
404
+ # Compute the mean of the histograms
405
+ hist_smooth = self.hist.copy()
406
+ hist_smooth['weight'] = xsarsea.gradients.circ_smooth(
407
+ hist_smooth['weight'])
408
+
409
+ # Select histogram peak for each individual solution
410
+ iangle_individual = hist_smooth['weight'].fillna(
411
+ 0).argmax(dim='angles')
412
+ streaks_dir_individual = hist_smooth['angles'].isel(
413
+ angles=iangle_individual)
414
+ streaks_weight_individual = hist_smooth['weight'].isel(
415
+ angles=iangle_individual)
416
+ # Combine angles and weights into a dataset
417
+ streaks_individual = xr.Dataset({
418
+ 'angle': streaks_dir_individual,
419
+ 'weight': streaks_weight_individual
420
+ })
421
+ # Remove 'angles' coordinate
422
+ streaks_individual = streaks_individual.reset_coords(
423
+ 'angles', drop=True)
424
+
425
+ # Remove ambiguity with ancillary wind for each individual solution
426
+ streaks_individual = self._remove_ambiguity(
427
+ streaks_individual)
428
+
429
+ # Convert to meteo convention
430
+ streaks_individual = self.convert_to_meteo_convention(
431
+ streaks_individual)
432
+
433
+ # Set attributes
434
+ streaks_individual['angle'].attrs['description'] = 'Wind direction estimated from local gradient for each individual solution; histograms smoothed individually'
435
+
436
+ return streaks_individual
437
+
438
+ except Exception as e:
439
+ logging.error("Error in streaks_individual: %s", e)
440
+
441
+ streaks_individual = xr.DataArray(data=np.nan * np.ones([len(self.xr_dataset.coords[dim]) for dim in ['line', 'sample']]),
442
+ dims=[
443
+ 'line', 'sample'],
444
+ coords=[self.xr_dataset.coords[dim]
445
+ for dim in ['line', 'sample']],
446
+ attrs={"comment": "no streaks_individual found"})
447
+
448
+ return streaks_individual