pyadps 0.2.0b0__py3-none-any.whl → 0.3.0__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.
Files changed (39) hide show
  1. pyadps/Home_Page.py +11 -5
  2. pyadps/pages/01_Read_File.py +623 -211
  3. pyadps/pages/02_View_Raw_Data.py +97 -41
  4. pyadps/pages/03_Download_Raw_File.py +200 -67
  5. pyadps/pages/04_Sensor_Health.py +905 -0
  6. pyadps/pages/05_QC_Test.py +493 -0
  7. pyadps/pages/06_Profile_Test.py +971 -0
  8. pyadps/pages/07_Velocity_Test.py +600 -0
  9. pyadps/pages/08_Write_File.py +623 -0
  10. pyadps/pages/09_Add-Ons.py +168 -0
  11. pyadps/utils/__init__.py +5 -3
  12. pyadps/utils/autoprocess.py +371 -80
  13. pyadps/utils/logging_utils.py +269 -0
  14. pyadps/utils/metadata/config.ini +22 -4
  15. pyadps/utils/metadata/demo.000 +0 -0
  16. pyadps/utils/metadata/flmeta.json +420 -420
  17. pyadps/utils/metadata/vlmeta.json +611 -565
  18. pyadps/utils/multifile.py +292 -0
  19. pyadps/utils/plotgen.py +505 -3
  20. pyadps/utils/profile_test.py +720 -125
  21. pyadps/utils/pyreadrdi.py +164 -92
  22. pyadps/utils/readrdi.py +436 -186
  23. pyadps/utils/script.py +197 -147
  24. pyadps/utils/sensor_health.py +120 -0
  25. pyadps/utils/signal_quality.py +472 -68
  26. pyadps/utils/velocity_test.py +79 -31
  27. pyadps/utils/writenc.py +222 -39
  28. {pyadps-0.2.0b0.dist-info → pyadps-0.3.0.dist-info}/METADATA +63 -33
  29. pyadps-0.3.0.dist-info/RECORD +35 -0
  30. {pyadps-0.2.0b0.dist-info → pyadps-0.3.0.dist-info}/WHEEL +1 -1
  31. {pyadps-0.2.0b0.dist-info → pyadps-0.3.0.dist-info}/entry_points.txt +1 -0
  32. pyadps/pages/04_QC_Test.py +0 -334
  33. pyadps/pages/05_Profile_Test.py +0 -575
  34. pyadps/pages/06_Velocity_Test.py +0 -341
  35. pyadps/pages/07_Write_File.py +0 -452
  36. pyadps/utils/cutbin.py +0 -413
  37. pyadps/utils/regrid.py +0 -279
  38. pyadps-0.2.0b0.dist-info/RECORD +0 -31
  39. {pyadps-0.2.0b0.dist-info → pyadps-0.3.0.dist-info}/LICENSE +0 -0
@@ -1,122 +1,63 @@
1
- import matplotlib.pyplot as plt
2
1
  import numpy as np
3
- import streamlit as st
4
- from matplotlib.widgets import Button, Slider, TextBox
5
- from pyadps.utils import readrdi as rd
6
-
7
- from .plotgen import plotmask, plotvar
8
-
9
-
10
- class PlotEnds:
11
- def __init__(self, pressure, delta=10):
12
- self.dep = pressure / 980
13
-
14
- self.n = np.size(self.dep)
15
- self.delta = delta
16
- self.nmin = 0
17
- self.nmax = self.nmin + self.delta
18
- self.mmax = 0
19
- self.mmin = self.mmax - self.delta
20
-
21
- self.x = np.arange(0, self.n)
22
-
23
- self.start_ens = 0
24
- self.end_ens = 0
25
-
26
- self.fig, self.axs = plt.subplots(1, 2, figsize=(12, 8))
27
- self.fig.set_facecolor("darkgrey")
28
- plt.subplots_adjust(bottom=0.28, right=0.72)
29
-
30
- self.ax_end = self.fig.add_axes(rect=(0.25, 0.08, 0.47, 0.03))
31
- self.ax_start = self.fig.add_axes(rect=(0.25, 0.15, 0.47, 0.03))
32
- self.ax_button = self.fig.add_axes(rect=(0.81, 0.05, 0.15, 0.075))
33
- # self.ax_depmaxbutton = self.fig.add_axes(rect=(0.68, 0.13, 0.04, 0.02))
34
- # self.ax_depminbutton = self.fig.add_axes(rect=(0.25, 0.13, 0.04, 0.02))
35
- # self.ax_recmaxbutton = self.fig.add_axes(rect=(0.68, 0.06, 0.04, 0.02))
36
- # self.ax_recminbutton = self.fig.add_axes(rect=(0.25, 0.06, 0.04, 0.02))
37
-
38
- # Plot
39
- self.axs[0].scatter(self.x, self.dep, color="k")
40
- self.axs[1].scatter(self.x, self.dep, color="k")
41
-
42
- # Figure Labels
43
- for i in range(2):
44
- self.axs[i].set_xlabel("Ensemble")
45
- self.axs[0].set_xlim([self.nmin - 1, self.nmax])
46
- self.axs[1].set_xlim([self.n - self.delta, self.n])
47
- self.axs[0].set_ylabel("Depth (m)")
48
- self.fig.suptitle("Trim Ends")
49
-
50
- # Display statistics
51
- self.axs[0].text(0.82, 0.60, "Statistics", transform=plt.gcf().transFigure)
52
- self.max = np.round(np.max(self.dep), decimals=2)
53
- self.min = np.round(np.min(self.dep), decimals=2)
54
- self.median = np.round(np.median(self.dep), decimals=2)
55
- self.mean = np.round(np.mean(self.dep), decimals=2)
56
- self.t1 = self.axs[0].text(
57
- 0.75,
58
- 0.50,
59
- f"Dep. Max = {self.max} \nDep. Min = {self.min} \nDep. Median = {self.median}",
60
- transform=plt.gcf().transFigure,
61
- )
62
-
63
- self.sl_start = Slider(
64
- ax=self.ax_start,
65
- label="Dep. Ensemble",
66
- valmin=self.nmin,
67
- valmax=self.nmax,
68
- valinit=0,
69
- valfmt="%i",
70
- valstep=1,
71
- )
72
-
73
- self.sl_end = Slider(
74
- ax=self.ax_end,
75
- label="Rec. Ensemble",
76
- valmin=self.mmin,
77
- valmax=self.mmax,
78
- valinit=0,
79
- valfmt="%i",
80
- valstep=1,
81
- )
82
-
83
- self.sl_start.on_changed(self.update1)
84
- self.sl_end.on_changed(self.update2)
85
- self.button = Button(self.ax_button, "Save & Exit")
86
- # self.depminbutton = Button(self.ax_depminbutton, "<<")
87
- # self.depmaxbutton = Button(self.ax_depmaxbutton, ">>")
88
- # self.recminbutton = Button(self.ax_recminbutton, "<<")
89
- # self.recmaxbutton = Button(self.ax_recmaxbutton, ">>")
90
-
91
- self.button.on_clicked(self.exitwin)
92
-
93
- def update1(self, value):
94
- self.axs[0].scatter(self.x, self.dep, color="k")
95
- self.axs[0].scatter(self.x[0:value], self.dep[0:value], color="r")
96
- self.start_ens = value
97
-
98
- def update2(self, value):
99
- self.axs[1].scatter(self.x, self.dep, color="k")
100
- if value < 0:
101
- self.axs[1].scatter(
102
- self.x[self.n + value : self.n],
103
- self.dep[self.n + value : self.n],
104
- color="r",
105
- )
106
- self.end_ens = value
2
+ import scipy as sp
3
+ from pyadps.utils.readrdi import ReadFile, check_equal
4
+ from .plotgen import PlotEnds
107
5
 
108
- def show(self):
109
- plt.show()
110
6
 
111
- def exitwin(self, event):
112
- plt.close()
7
+ def trim_ends(ds, mask, transducer_depth=None, delta=20, method="Manual"):
8
+ """
9
+ Trim the ends of the data based on the provided method (e.g., manual selection)
10
+ to remove invalid or irrelevant data points during deployment and recovery
11
+ of moorings. This function modifies the mask to reflect the valid data range by marking
12
+ invalid data points as `1` at the ends.
13
+
14
+ Parameters
15
+ ----------
16
+ ds : pyadps.dataset
17
+ The pyadps dataframe is loaded to obtain the data from the variable leader.
18
+ This includes the depth of the transducer and other relevant information
19
+ for trimming the data.
20
+ mask : numpy.ndarray
21
+ A mask array of the same shape as the data, where `1` indicates invalid data
22
+ and `0` indicates valid data. The function modifies the mask based on the trimming
23
+ process.
24
+ method : str, optional
25
+ The method used for trimming the data. Default is "Manual", which allows the user
26
+ to manually select the start and end indices for trimming. Other methods can be
27
+ added if required in the future.
28
+
29
+ Returns
30
+ -------
31
+ numpy.ndarray
32
+ The updated mask array with the ends trimmed. Data points at the beginning and end
33
+ of the array marked as invalid (`1`) based on the trimming results.
34
+
35
+ Notes
36
+ -----
37
+ - The function uses the transducer depth from the `vlobj` to determine the trimming boundaries.
38
+ - When using the "Manual" method, the user is prompted with a plot that allows them to select
39
+ the start and end indices for trimming. These indices are then used to adjust the mask.
40
+ - The mask array is updated in-place to mark the trimmed areas as invalid.
41
+ - The trimming process is based on the variable leader data and the selected method.
42
+
43
+ Example
44
+ -------
45
+ >>> import pyadps
46
+ >>> ds = pyadps.ReadFile("demo.000")
47
+ >>> mask = pyadps.default_mask(ds)
48
+ >>> updated_mask = trim_ends(ds, mask, method="Manual")
49
+ """
113
50
 
51
+ if isinstance(ds, ReadFile):
52
+ vlobj = ds.variableleader
53
+ transducer_depth = vlobj.vleader["Depth of Transducer"]
54
+ elif isinstance(ds, np.ndarray) and ds.ndim == 1:
55
+ transducer_depth = ds
56
+ else:
57
+ raise ValueError("Input must be a 1-D numpy array or a PyADPS instance")
114
58
 
115
- def trim_ends(vlobj, mask, method="Manual"):
116
- transducer_depth = vlobj.vleader["Depth of Transducer"]
117
- # pressure = vlobj.vleader["Pressure"]
118
59
  if method == "Manual":
119
- out = PlotEnds(transducer_depth, delta=20)
60
+ out = PlotEnds(transducer_depth, delta=delta)
120
61
  out.show()
121
62
  if out.start_ens > 0:
122
63
  mask[:, 0 : out.start_ens] = 1
@@ -127,26 +68,112 @@ def trim_ends(vlobj, mask, method="Manual"):
127
68
  return mask
128
69
 
129
70
 
130
- def side_lobe_beam_angle(flobj, vlobj, mask, orientation='default', water_column_depth=0, extra_cells=2):
131
- beam_angle = int(flobj.system_configuration()["Beam Angle"])
132
- cell_size = flobj.field()["Depth Cell Len"]
133
- bin1dist = flobj.field()["Bin 1 Dist"]
134
- cells = flobj.field()["Cells"]
135
- ensembles = flobj.ensembles
136
- transducer_depth = vlobj.vleader["Depth of Transducer"]
137
-
138
- if orientation.lower() == "default":
139
- orientation = flobj.system_configuration()['Beam Direction']
71
+ def side_lobe_beam_angle(
72
+ ds,
73
+ mask,
74
+ orientation="default",
75
+ water_column_depth=0,
76
+ extra_cells=2,
77
+ cells=None,
78
+ cell_size=None,
79
+ bin1dist=None,
80
+ beam_angle=None,
81
+ ):
82
+ """
83
+ Mask the data contaminated due to surface/bottom backscatter based on the
84
+ side lobe beam angle. This function can correct the orientation of the beam
85
+ (upward or downward looking) and can account for deletion of additional cells.
86
+ Water column depth is applicable for downward-looking ADCP.
87
+
88
+ Parameters
89
+ ----------
90
+ ds : pyadps.dataset or np.ndarray
91
+ The pyadps dataframe is loaded to obtain the data from the fixed and variable leader.
92
+ This includes the depth of the transducer and other relevant information
93
+ for trimming the data.
94
+
95
+ If numpy.ndarray is loaded, the value should contain the transducer_depth.
96
+ In such cases provide cells, cell_size, and bin 1 distance.
97
+ Orientiation should be either 'up' or 'down' and not 'default'.
98
+
99
+ mask : numpy.ndarray
100
+ A mask array where invalid or false data points are marked. The mask is updated
101
+ based on the calculated side lobe beam angles.
102
+ orientation : str, optional
103
+ The orientation of the beam. It can be 'default', 'up', or 'down'.
104
+ Default orientation, set before deployment, is obtained from the file.
105
+ If 'up' or 'down' is selected, the function will correct the orientation accordingly.
106
+ water_column_depth : int, optional
107
+ The depth of the water column. This value is used to adjust the beam angle
108
+ calculations. Default is 0.
109
+ extra_cells : int, optional
110
+ The number of extra cells to consider when calculating the beam angle. Default is 2.
111
+ cells: int, optional
112
+ Number of cells
113
+ cell_size: int, optional
114
+ Cell size or depth cell length in cm
115
+ beam_angle: int, optional
116
+ Beam angle in degrees
117
+
118
+ Returns
119
+ -------
120
+ numpy.ndarray
121
+ The updated mask array with side lobe beam angle adjustments. The mask will
122
+ reflect valid and invalid data points based on the calculated beam angles.
123
+
124
+ Notes
125
+ -----
126
+ - The function uses the fixedleader and variableleader to retrieve the necessary information
127
+ for the beam angle calculation. The `mask` array is updated based on these calculations.
128
+ - The `orientation` parameter allows for adjustments to account for upward or downward
129
+ looking ADCPs.
130
+ - The `water_column_depth` permits detecting the bottom of the ocean for downward looking
131
+ ADCP.
132
+ - The `extra_cells` parameter adds additional cells in addition to those masked by beam angle
133
+ calculation.
134
+
135
+ Example
136
+ -------
137
+ >>> import pyadps
138
+ >>> ds = pyadps.ReadFile("demo.000")
139
+ >>> mask = pyadps.default_mask(ds)
140
+ >>> updated_mask = side_lobe_beam_angle(ds, mask, orientation='down', water_column_depth=50, extra_cells=3)
141
+
142
+ >>> transducer_depth = ds.variableleader.transducer_depth
143
+ >>> cells = ds.fixedleader.cells
144
+ >>> cell_size = ds.fixedleader.depth_cell_length
145
+ >>> new_mask = side_lobe_beam_angle(transducer_depth, orientation='up', cells=cells, cell_size=cell_size, bin1dist=bindist)
146
+ """
147
+ if isinstance(ds, ReadFile) or ds.__class__.__name__ == "ReadFile":
148
+ flobj = ds.fixedleader
149
+ vlobj = ds.variableleader
150
+ beam_angle = int(flobj.system_configuration()["Beam Angle"])
151
+ cell_size = flobj.field()["Depth Cell Len"]
152
+ bin1dist = flobj.field()["Bin 1 Dist"]
153
+ cells = flobj.field()["Cells"]
154
+ ensembles = flobj.ensembles
155
+ transducer_depth = vlobj.vleader["Depth of Transducer"]
156
+ if orientation.lower() == "default":
157
+ orientation = flobj.system_configuration()["Beam Direction"]
158
+ elif isinstance(ds, np.ndarray) and np.squeeze(ds).ndim == 1:
159
+ transducer_depth = ds
160
+ ensembles = np.size(ds)
161
+ else:
162
+ raise ValueError("Input must be a 1-D numpy array or a PyADPS instance")
140
163
 
141
164
  if orientation.lower() == "up":
142
165
  sgn = -1
143
166
  water_column_depth = 0
144
- else:
167
+ elif orientation.lower() == "down":
145
168
  sgn = 1
169
+ else:
170
+ raise ValueError("Orientation should be either `up` or `down`")
146
171
 
147
172
  beam_angle = np.deg2rad(beam_angle)
148
173
  depth = transducer_depth / 10
149
- valid_depth = (water_column_depth - sgn*depth) * np.cos(beam_angle) + sgn*bin1dist / 100
174
+ valid_depth = (water_column_depth - sgn * depth) * np.cos(
175
+ beam_angle
176
+ ) + sgn * bin1dist / 100
150
177
  valid_cells = np.trunc(valid_depth * 100 / cell_size) - extra_cells
151
178
 
152
179
  for i in range(ensembles):
@@ -185,3 +212,571 @@ def manual_cut_bins(mask, min_cell, max_cell, min_ensemble, max_ensemble):
185
212
  mask[min_cell:max_cell, min_ensemble:max_ensemble] = 1
186
213
 
187
214
  return mask
215
+
216
+
217
+ def modifiedRegrid2d(
218
+ ds,
219
+ data,
220
+ fill_value,
221
+ end_cell_option="cell",
222
+ trimends=None,
223
+ method="nearest",
224
+ orientation="default",
225
+ boundary_limit=0,
226
+ cells=None,
227
+ cell_size=None,
228
+ bin1dist=None,
229
+ ):
230
+ """
231
+ Modified Regrids 2D data onto a new grid based on specified parameters.
232
+ The function is capable of handling data with non-uniform number of cells
233
+ and Depth cell length.
234
+
235
+ Parameters:
236
+ -----------
237
+ ds : pyadps.dataset or numpy.ndarray
238
+ If pyadps dataframe is loaded, the data from the fixed and variable leader
239
+ is automatically obtained. This includes the depth of the transducer and other relevant information
240
+ for trimming the data.
241
+
242
+ If numpy.ndarray is loaded, the value should contain the transducer_depth.
243
+ In such cases provide cells, cell_size, and bin 1 distance.
244
+ Orientiation should be either 'up' or 'down' and not 'default'.
245
+
246
+
247
+ data : array-like
248
+ The 2D data array to be regridded.
249
+
250
+ fill_value : scalar
251
+ The value used to fill missing or undefined grid points.
252
+
253
+ end_cell_option : str or float, optional, default="cell"
254
+ The depth of the last bin or boundary for the grid.
255
+ Options include:
256
+ - "cell" : Calculates the depth of the default last bin for the grid.
257
+ Truncates to surface for upward ADCP.
258
+ - "surface": The data is gridded till the surface
259
+ - "manual": User-defined depth for the grid.
260
+ Use boundary_limit option to provide the value.
261
+ otherwise, a specific numerical depth value can be provided.
262
+
263
+ trimends : tuple of floats, optional, default=None
264
+ If provided, defines the ensemble range (start, end) for
265
+ calculating the maximum/minimum transducer depth.
266
+ Helps avoiding the deployment or retrieval data.
267
+ E.g. (10, 3000)
268
+
269
+ method : str, optional, default="nearest"
270
+ The interpolation method to use for regridding based
271
+ on scipy.interpolate.interp1d.
272
+ Options include:
273
+ - "nearest" : Nearest neighbor interpolation.
274
+ - "linear" : Linear interpolation.
275
+ - "cubic" : Cubic interpolation.
276
+
277
+ orientation : str, optional, default="up"
278
+ Defines the direction of the regridding for an upward/downward looking ADCP. Options include:
279
+ - "up" : Regrid upwards (for upward-looking ADCP).
280
+ - "down" : Regrid downwards (for downward-looking ADCP).
281
+
282
+ boundary_limit : float, optional, default=0
283
+ The limit for the boundary depth. This restricts the grid regridding to depths beyond the specified limit.
284
+
285
+ cells: int, optional
286
+ Number of cells
287
+
288
+ cell_size: int, optional
289
+ Cell size or depth cell length in cm
290
+
291
+ bin1dist: int, optional
292
+ Distance from the first bin in cm
293
+
294
+
295
+ Returns:
296
+ --------
297
+ z: regridded depth
298
+ regridded_data : array-like
299
+ The regridded 2D data array, based on the specified method,
300
+ orientation, and other parameters.
301
+
302
+ Notes:
303
+ ------
304
+ - If `end_cell_option == boundary`, then `boundary_limit` is used to regrid the data.
305
+ - This function allows for flexible regridding of 2D data to fit a new grid, supporting different interpolation methods.
306
+ - The `boundary_limit` parameter helps restrict regridding to depths above or below a certain threshold.
307
+ """
308
+
309
+ if isinstance(ds, ReadFile) or ds.__class__.__name__ == "ReadFile":
310
+ flobj = ds.fixedleader
311
+ vlobj = ds.variableleader
312
+ # Get values and convert to 'm'
313
+ bin1dist = flobj.field()["Bin 1 Dist"] / 100
314
+ transdepth = vlobj.vleader["Depth of Transducer"] / 10
315
+ cell_size = flobj.field()["Depth Cell Len"] / 100
316
+ cells = flobj.field()["Cells"]
317
+ ensembles = flobj.ensembles
318
+ if orientation.lower() == "default":
319
+ orientation = flobj.system_configuration()["Beam Direction"]
320
+
321
+ elif isinstance(ds, np.ndarray) and np.squeeze(ds).ndim == 1:
322
+ transdepth = ds / 10
323
+ ensembles = np.size(ds)
324
+
325
+ if cells is None:
326
+ raise ValueError("Input must include number of cells.")
327
+
328
+ if cell_size is None:
329
+ raise ValueError("Input must include cell size.")
330
+ else:
331
+ cell_size = cell_size / 100
332
+
333
+ if bin1dist is None:
334
+ raise ValueError("Input must include bin 1 distance.")
335
+ else:
336
+ bin1dist = bin1dist / 100
337
+
338
+ if orientation.lower() != "up" and orientation.lower() != "down":
339
+ raise ValueError("Orientation must be `up` or `down`.")
340
+ else:
341
+ raise ValueError("Input must be a 1-D numpy array or a PyADPS instance")
342
+
343
+ if orientation.lower() == "up":
344
+ sgn = -1
345
+ else:
346
+ sgn = 1
347
+
348
+ # Create a regular grid
349
+
350
+ # Find depth of first cell
351
+ depth = transdepth + sgn * bin1dist
352
+ # print("depth: ", depth)
353
+
354
+ # Find the maximum and minimum depth for first cell for upward
355
+ # looking ADCP (minimum and maximum for downward looking)
356
+ if trimends is not None:
357
+ max_depth = abs(np.min(sgn * depth[trimends[0] : trimends[1]]))
358
+ min_depth = abs(np.max(sgn * depth[trimends[0] : trimends[1]]))
359
+ else:
360
+ max_depth = abs(np.min(sgn * depth))
361
+ min_depth = abs(np.max(sgn * depth))
362
+
363
+ # FIRST CELL
364
+ # Convert the first cell depth to the first regular grid depth
365
+ depthfirstcell = max_depth - max_depth % min(cell_size)
366
+ # print("depthfirstcell: ", depthfirstcell)
367
+
368
+ # LAST CELL
369
+ # Convert the last cell depth to last regular grid depth
370
+ if end_cell_option.lower() == "surface":
371
+ # Added one additional negative cell to accomodate 0 m.
372
+ depthlastcell = sgn * min(cell_size)
373
+ # print("depthlastcell: ", depthlastcell)
374
+ elif end_cell_option.lower() == "cell":
375
+ min_depth_regrid = min_depth - sgn * min_depth % min(cell_size)
376
+ depthlastcell = min_depth_regrid + sgn * (max(cells) + 1) * min(cell_size)
377
+ # print("depthlastcell: ", depthlastcell)
378
+ # Check if this is required. Use 'surface' option
379
+ if depthlastcell < 0:
380
+ depthlastcell = sgn * min(cell_size)
381
+ elif end_cell_option.lower() == "manual":
382
+ if sgn < 0 and boundary_limit > depthfirstcell:
383
+ print(
384
+ "ERROR: For upward looking ADCP, boundary limit should be less than transducer depth"
385
+ )
386
+ return
387
+ if sgn > 0 and boundary_limit < depthfirstcell:
388
+ print(
389
+ "ERROR: For downward looking ADCP, boundary limit should be greater than transducer depth"
390
+ )
391
+ return
392
+ # Set the last grid cell depth
393
+ depthlastcell = boundary_limit
394
+ else:
395
+ print("ERROR: `end_cell_option` not recognized.")
396
+ return
397
+
398
+ # Negative used for upward and positive for downward.
399
+ z = np.arange(sgn * depthfirstcell, sgn * depthlastcell, min(cell_size))
400
+ regbins = len(z)
401
+
402
+ regridded_data = np.zeros((regbins, ensembles))
403
+
404
+ # Create original depth array
405
+ for i, d in enumerate(depth):
406
+ n = d + sgn * cell_size[i] * cells[i]
407
+ # np.arange may include unexpected elements due to floating-point
408
+ # precision issues at the stopping point. Changed to np.linspace.
409
+ #
410
+ # depth_bins = np.arange(sgn*d, sgn*n, cell_size)
411
+ depth_bins = np.linspace(sgn * d, sgn * n, max(cells))
412
+ # print("depth_bins: ", depth_bins, "len: ", len(depth_bins))
413
+ # print("data:", data, "len:", len(data))
414
+ # print("i: ", i)
415
+ f = sp.interpolate.interp1d(
416
+ depth_bins,
417
+ data[:, i],
418
+ kind=method,
419
+ fill_value=fill_value,
420
+ bounds_error=False,
421
+ )
422
+ gridz = f(z)
423
+
424
+ regridded_data[:, i] = gridz
425
+
426
+ return abs(z), regridded_data
427
+
428
+ def regrid2d(
429
+ ds,
430
+ data,
431
+ fill_value,
432
+ end_cell_option="cell",
433
+ trimends=None,
434
+ method="nearest",
435
+ orientation="default",
436
+ boundary_limit=0,
437
+ cells=None,
438
+ cell_size=None,
439
+ bin1dist=None,
440
+ ):
441
+ """
442
+ Regrids 2D data onto a new grid based on specified parameters.
443
+
444
+ Parameters:
445
+ -----------
446
+ ds : pyadps.dataset or numpy.ndarray
447
+ If pyadps dataframe is loaded, the data from the fixed and variable leader
448
+ is automatically obtained. This includes the depth of the transducer and other relevant information
449
+ for trimming the data.
450
+
451
+ If numpy.ndarray is loaded, the value should contain the transducer_depth.
452
+ In such cases provide cells, cell_size, and bin 1 distance.
453
+ Orientiation should be either 'up' or 'down' and not 'default'.
454
+
455
+
456
+ data : array-like
457
+ The 2D data array to be regridded.
458
+
459
+ fill_value : scalar
460
+ The value used to fill missing or undefined grid points.
461
+
462
+ end_cell_option : str or float, optional, default="cell"
463
+ The depth of the last bin or boundary for the grid.
464
+ Options include:
465
+ - "cell" : Calculates the depth of the default last bin for the grid.
466
+ Truncates to surface for upward ADCP.
467
+ - "surface": The data is gridded till the surface
468
+ - "manual": User-defined depth for the grid.
469
+ Use boundary_limit option to provide the value.
470
+ otherwise, a specific numerical depth value can be provided.
471
+
472
+ trimends : tuple of floats, optional, default=None
473
+ If provided, defines the ensemble range (start, end) for
474
+ calculating the maximum/minimum transducer depth.
475
+ Helps avoiding the deployment or retrieval data.
476
+ E.g. (10, 3000)
477
+
478
+ method : str, optional, default="nearest"
479
+ The interpolation method to use for regridding based
480
+ on scipy.interpolate.interp1d.
481
+ Options include:
482
+ - "nearest" : Nearest neighbor interpolation.
483
+ - "linear" : Linear interpolation.
484
+ - "cubic" : Cubic interpolation.
485
+
486
+ orientation : str, optional, default="up"
487
+ Defines the direction of the regridding for an upward/downward looking ADCP. Options include:
488
+ - "up" : Regrid upwards (for upward-looking ADCP).
489
+ - "down" : Regrid downwards (for downward-looking ADCP).
490
+
491
+ boundary_limit : float, optional, default=0
492
+ The limit for the boundary depth. This restricts the grid regridding to depths beyond the specified limit.
493
+
494
+ cells: int, optional
495
+ Number of cells
496
+
497
+ cell_size: int, optional
498
+ Cell size or depth cell length in cm
499
+
500
+ bin1dist: int, optional
501
+ Distance from the first bin in cm
502
+
503
+
504
+ Returns:
505
+ --------
506
+ z: regridded depth
507
+ regridded_data : array-like
508
+ The regridded 2D data array, based on the specified method,
509
+ orientation, and other parameters.
510
+
511
+ Notes:
512
+ ------
513
+ - If `end_cell_option == boundary`, then `boundary_limit` is used to regrid the data.
514
+ - This function allows for flexible regridding of 2D data to fit a new grid, supporting different interpolation methods.
515
+ - The `boundary_limit` parameter helps restrict regridding to depths above or below a certain threshold.
516
+ """
517
+
518
+ if isinstance(ds, ReadFile) or ds.__class__.__name__ == "ReadFile":
519
+ if not (check_equal(ds.fleader['Cells']) or check_equal(ds.fleader['Depth Cell Len'])):
520
+ print("\033[93m Warning: The number of cells or depth cell length are not equal. Using the modifiedRegrid2d function, which may take some time.\033[0m")
521
+ return modifiedRegrid2d(ds, data, fill_value, end_cell_option, trimends, method, orientation,
522
+ boundary_limit, cells, cell_size, bin1dist)
523
+
524
+ flobj = ds.fixedleader
525
+ vlobj = ds.variableleader
526
+ # Get values and convert to 'm'
527
+ bin1dist = flobj.field()["Bin 1 Dist"] / 100
528
+ transdepth = vlobj.vleader["Depth of Transducer"] / 10
529
+ cell_size = flobj.field()["Depth Cell Len"] / 100
530
+ cells = flobj.field()["Cells"]
531
+ ensembles = flobj.ensembles
532
+ if orientation.lower() == "default":
533
+ orientation = flobj.system_configuration()["Beam Direction"]
534
+
535
+ elif isinstance(ds, np.ndarray) and np.squeeze(ds).ndim == 1:
536
+ transdepth = ds / 10
537
+ ensembles = np.size(ds)
538
+
539
+ if cells is None:
540
+ raise ValueError("Input must include number of cells.")
541
+ else:
542
+ if not check_equal(cells):
543
+ print("\033[93m Warning: The number of cells or depth cell length are not equal. Using the modifiedRegrid2d function, which may take some time.\033[0m")
544
+ return modifiedRegrid2d(ds, data, fill_value, end_cell_option, trimends, method, orientation,
545
+ boundary_limit, cells, cell_size, bin1dist)
546
+ cells = cells[0]
547
+
548
+ if cell_size is None:
549
+ raise ValueError("Input must include cell size.")
550
+ else:
551
+ if not check_equal(cell_size):
552
+ # print("\033[93m Warning: The number of cells or depth cell length are not equal. Using the modifiedRegrid2d function, which may take some time.\033[0m")
553
+ return modifiedRegrid2d(ds, data, fill_value, end_cell_option, trimends, method, orientation,
554
+ boundary_limit, cells, cell_size, bin1dist)
555
+ cell_size = cell_size[0] / 100
556
+
557
+ if bin1dist is None:
558
+ raise ValueError("Input must include bin 1 distance.")
559
+ else:
560
+ bin1dist = bin1dist / 100
561
+
562
+ if orientation.lower() != "up" and orientation.lower() != "down":
563
+ raise ValueError("Orientation must be `up` or `down`.")
564
+ else:
565
+ raise ValueError("Input must be a 1-D numpy array or a PyADPS instance")
566
+
567
+ if orientation.lower() == "up":
568
+ sgn = -1
569
+ else:
570
+ sgn = 1
571
+
572
+ # Create a regular grid
573
+
574
+ # Find depth of first cell
575
+ depth = transdepth + sgn * bin1dist
576
+
577
+ # Find the maximum and minimum depth for first cell for upward
578
+ # looking ADCP (minimum and maximum for downward looking)
579
+ if trimends is not None:
580
+ max_depth = abs(np.min(sgn * depth[trimends[0] : trimends[1]]))
581
+ min_depth = abs(np.max(sgn * depth[trimends[0] : trimends[1]]))
582
+ else:
583
+ max_depth = abs(np.min(sgn * depth))
584
+ min_depth = abs(np.max(sgn * depth))
585
+
586
+ # FIRST CELL
587
+ # Convert the first cell depth to the first regular grid depth
588
+ depthfirstcell = max_depth - max_depth % cell_size
589
+
590
+ # LAST CELL
591
+ # Convert the last cell depth to last regular grid depth
592
+ if end_cell_option.lower() == "surface":
593
+ # Added one additional negative cell to accomodate 0 m.
594
+ depthlastcell = sgn * cell_size
595
+ elif end_cell_option.lower() == "cell":
596
+ min_depth_regrid = min_depth - sgn * min_depth % cell_size
597
+ depthlastcell = min_depth_regrid + sgn * (cells + 1) * cell_size
598
+ # Check if this is required. Use 'surface' option
599
+ if depthlastcell < 0:
600
+ depthlastcell = sgn * cell_size
601
+ elif end_cell_option.lower() == "manual":
602
+ if sgn < 0 and boundary_limit > depthfirstcell:
603
+ print(
604
+ "ERROR: For upward looking ADCP, boundary limit should be less than transducer depth"
605
+ )
606
+ return
607
+ if sgn > 0 and boundary_limit < depthfirstcell:
608
+ print(
609
+ "ERROR: For downward looking ADCP, boundary limit should be greater than transducer depth"
610
+ )
611
+ return
612
+ # Set the last grid cell depth
613
+ depthlastcell = boundary_limit
614
+ else:
615
+ print("ERROR: `end_cell_option` not recognized.")
616
+ return
617
+
618
+ # Negative used for upward and positive for downward.
619
+ z = np.arange(sgn * depthfirstcell, sgn * depthlastcell, cell_size)
620
+ regbins = len(z)
621
+
622
+ regridded_data = np.zeros((regbins, ensembles))
623
+
624
+ # Create original depth array
625
+ for i, d in enumerate(depth):
626
+ n = d + sgn * cell_size * cells
627
+ # np.arange may include unexpected elements due to floating-point
628
+ # precision issues at the stopping point. Changed to np.linspace.
629
+ #
630
+ # depth_bins = np.arange(sgn*d, sgn*n, cell_size)
631
+ depth_bins = np.linspace(sgn * d, sgn * n, cells)
632
+ f = sp.interpolate.interp1d(
633
+ depth_bins,
634
+ data[:, i],
635
+ kind=method,
636
+ fill_value=fill_value,
637
+ bounds_error=False,
638
+ )
639
+ gridz = f(z)
640
+
641
+ regridded_data[:, i] = gridz
642
+
643
+ return abs(z), regridded_data
644
+
645
+
646
+ def regrid3d(
647
+ ds,
648
+ data,
649
+ fill_value,
650
+ end_cell_option="cell",
651
+ trimends=None,
652
+ method="nearest",
653
+ orientation="up",
654
+ boundary_limit=0,
655
+ cells=None,
656
+ cell_size=None,
657
+ bin1dist=None,
658
+ beams=None,
659
+ ):
660
+ """
661
+ Regrids 3D data onto a new grid based on specified parameters.
662
+
663
+ Parameters:
664
+ -----------
665
+ ds : pyadps.dataset
666
+ The pyadps dataframe is loaded to obtain the data from the fixed and variable leader.
667
+ This includes the depth of the transducer and other relevant information
668
+ for trimming the data.
669
+
670
+ data : array-like
671
+ The 3D data array to be regridded, with dimensions
672
+ typically representing time, depth, and another axis (e.g., ensembles).
673
+
674
+ fill_value : scalar
675
+ The value used to fill missing or undefined grid points.
676
+
677
+ end_cell_option : str or float, optional, default="cell"
678
+ The depth of the last bin or boundary for the grid.
679
+ Options include:
680
+ - "cell" : Calculates the depth of the default last bin for the grid.
681
+ Truncates to surface for upward ADCP.
682
+ - "surface" : The data is gridded till the surface.
683
+ - "manual" : User-defined depth for the grid.
684
+ Use boundary_limit option to provide the value.
685
+ Otherwise, a specific numerical depth value can be provided.
686
+
687
+ trimends : tuple of integer, optional, default=None
688
+ If provided, defines the ensemble range (start, end) for
689
+ calculating the maximum/minimum transducer depth.
690
+ Helps avoiding the deployment or retrieval data.
691
+ E.g., (10, 3000)
692
+
693
+ method : str, optional, default="nearest"
694
+ The interpolation method to use for regridding based
695
+ on scipy.interpolate.interp1d.
696
+ Options include:
697
+ - "nearest" : Nearest neighbor interpolation.
698
+ - "linear" : Linear interpolation.
699
+ - "cubic" : Cubic interpolation.
700
+
701
+ orientation : str, optional, default="up"
702
+ Defines the direction of the regridding for an upward/downward looking ADCP. Options include:
703
+ - "up" : Regrid upwards (for upward-looking ADCP).
704
+ - "down" : Regrid downwards (for downward-looking ADCP).
705
+
706
+ boundary_limit : float, optional, default=0
707
+ The limit for the boundary depth. This restricts the grid regridding to depths beyond the specified limit.
708
+
709
+ cells: int, optional
710
+ Number of cells
711
+
712
+ cell_size: int, optional
713
+ Cell size or depth cell length in cm
714
+
715
+ bin1dist: int, optional
716
+ Distance from the first bin in cm
717
+
718
+ beams: int, optional
719
+ Number of beams
720
+
721
+
722
+
723
+ Returns:
724
+ --------
725
+ z : array-like
726
+ The regridded depth array.
727
+ regridded_data : array-like
728
+ The regridded 3D data array, based on the specified method,
729
+ orientation, and other parameters.
730
+
731
+ Notes:
732
+ ------
733
+ - If `end_cell_option == boundary`, then `boundary_limit` is used to regrid the data.
734
+ - This function allows for flexible regridding of 3D data to fit a new grid, supporting different interpolation methods.
735
+ - The `boundary_limit` parameter helps restrict regridding to depths above or below a certain threshold.
736
+ - This function is an extension of 2D regridding to handle the time dimension or other additional axes in the data.
737
+ """
738
+
739
+ if isinstance(ds, ReadFile):
740
+ flobj = ds.fixedleader
741
+ beams = flobj.field()["Beams"]
742
+ elif isinstance(ds, np.ndarray) and ds.ndim == 1:
743
+ if beams is None:
744
+ raise ValueError("Input must include number of beams.")
745
+ else:
746
+ raise ValueError("Input must be a 1-D numpy array or a PyADPS instance")
747
+
748
+ z, data_dummy = regrid2d(
749
+ ds,
750
+ data[0, :, :],
751
+ fill_value,
752
+ end_cell_option=end_cell_option,
753
+ trimends=trimends,
754
+ method=method,
755
+ orientation=orientation,
756
+ boundary_limit=boundary_limit,
757
+ cells=cells,
758
+ cell_size=cell_size,
759
+ bin1dist=bin1dist,
760
+ )
761
+
762
+ newshape = np.shape(data_dummy)
763
+ regridded_data = np.zeros((beams, newshape[0], newshape[1]))
764
+ regridded_data[0, :, :] = data_dummy
765
+
766
+ for i in range(beams - 1):
767
+ z, data_dummy = regrid2d(
768
+ ds,
769
+ data[i + 1, :, :],
770
+ fill_value,
771
+ end_cell_option=end_cell_option,
772
+ trimends=trimends,
773
+ method=method,
774
+ orientation=orientation,
775
+ boundary_limit=boundary_limit,
776
+ cells=cells,
777
+ cell_size=cell_size,
778
+ bin1dist=bin1dist,
779
+ )
780
+ regridded_data[i + 1, :, :] = data_dummy
781
+
782
+ return z, regridded_data