pyadps 0.1.1__py3-none-any.whl → 0.1.3__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 (33) hide show
  1. pyadps/pages/01_Read_File.py +3 -2
  2. pyadps/pages/03_Download_Raw_File.py +69 -24
  3. pyadps/pages/05_QC_Test.py +20 -3
  4. pyadps/pages/06_Profile_Test.py +10 -10
  5. pyadps/pages/08_Write_File.py +26 -5
  6. pyadps/utils/autoprocess.py +10 -5
  7. pyadps/utils/metadata/flmeta.json +420 -420
  8. pyadps/utils/plotgen.py +1 -1
  9. pyadps/utils/profile_test.py +228 -2
  10. pyadps/utils/pyreadrdi.py +112 -83
  11. pyadps/utils/readrdi.py +34 -3
  12. pyadps/utils/signal_quality.py +16 -4
  13. pyadps/utils/writenc.py +101 -52
  14. {pyadps-0.1.1.dist-info → pyadps-0.1.3.dist-info}/METADATA +1 -1
  15. pyadps-0.1.3.dist-info/RECORD +33 -0
  16. pyadps/pages/__pycache__/__init__.cpython-312.pyc +0 -0
  17. pyadps/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  18. pyadps/utils/__pycache__/autoprocess.cpython-312.pyc +0 -0
  19. pyadps/utils/__pycache__/cutbin.cpython-312.pyc +0 -0
  20. pyadps/utils/__pycache__/plotgen.cpython-312.pyc +0 -0
  21. pyadps/utils/__pycache__/profile_test.cpython-312.pyc +0 -0
  22. pyadps/utils/__pycache__/pyreadrdi.cpython-312.pyc +0 -0
  23. pyadps/utils/__pycache__/readrdi.cpython-312.pyc +0 -0
  24. pyadps/utils/__pycache__/regrid.cpython-312.pyc +0 -0
  25. pyadps/utils/__pycache__/script.cpython-312.pyc +0 -0
  26. pyadps/utils/__pycache__/sensor_health.cpython-312.pyc +0 -0
  27. pyadps/utils/__pycache__/signal_quality.cpython-312.pyc +0 -0
  28. pyadps/utils/__pycache__/velocity_test.cpython-312.pyc +0 -0
  29. pyadps/utils/__pycache__/writenc.cpython-312.pyc +0 -0
  30. pyadps-0.1.1.dist-info/RECORD +0 -47
  31. {pyadps-0.1.1.dist-info → pyadps-0.1.3.dist-info}/LICENSE +0 -0
  32. {pyadps-0.1.1.dist-info → pyadps-0.1.3.dist-info}/WHEEL +0 -0
  33. {pyadps-0.1.1.dist-info → pyadps-0.1.3.dist-info}/entry_points.txt +0 -0
pyadps/utils/plotgen.py CHANGED
@@ -4,7 +4,7 @@ import matplotlib.pyplot as plt
4
4
  from matplotlib.widgets import Button, RadioButtons, Slider, TextBox
5
5
  from matplotlib.widgets import RectangleSelector
6
6
 
7
- mpl.use("TkAgg")
7
+ # mpl.use("TkAgg")
8
8
 
9
9
 
10
10
  class CutBins:
@@ -1,6 +1,6 @@
1
1
  import numpy as np
2
2
  import scipy as sp
3
- from pyadps.utils.readrdi import ReadFile
3
+ from pyadps.utils.readrdi import ReadFile, check_equal
4
4
  from .plotgen import PlotEnds
5
5
 
6
6
 
@@ -214,6 +214,217 @@ def manual_cut_bins(mask, min_cell, max_cell, min_ensemble, max_ensemble):
214
214
  return mask
215
215
 
216
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
+
217
428
  def regrid2d(
218
429
  ds,
219
430
  data,
@@ -305,6 +516,11 @@ def regrid2d(
305
516
  """
306
517
 
307
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
+
308
524
  flobj = ds.fixedleader
309
525
  vlobj = ds.variableleader
310
526
  # Get values and convert to 'm'
@@ -322,11 +538,21 @@ def regrid2d(
322
538
 
323
539
  if cells is None:
324
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]
325
547
 
326
548
  if cell_size is None:
327
549
  raise ValueError("Input must include cell size.")
328
550
  else:
329
- cell_size = cell_size / 100
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
330
556
 
331
557
  if bin1dist is None:
332
558
  raise ValueError("Input must include bin 1 distance.")
pyadps/utils/pyreadrdi.py CHANGED
@@ -3,17 +3,17 @@ pyreadrdi.py
3
3
 
4
4
  Module Overview
5
5
  ---------------
6
- This module provides functionalities to read and parse RDI ADCP files.
7
- It includes functions for reading file headers, fixed and variable leaders,
6
+ This module provides functionalities to read and parse RDI ADCP files.
7
+ It includes functions for reading file headers, fixed and variable leaders,
8
8
  and data types like velocity, correlation, echo intensity, and percent good.
9
- Currently reads only PD0 format.
9
+ Currently reads only PD0 format.
10
10
 
11
11
  Modules
12
12
  -------------------
13
13
  - fileheader: Function to read and parse the file header information.
14
14
  - fixedleader: Function to read and parse the fixed leader section of an RDI file.
15
15
  - variableleader: Function to read and parse the variable leader section of an RDI file.
16
- - datatype: Function to read and parse 3D data types.
16
+ - datatype: Function to read and parse 3D data types.
17
17
  - ErrorCode: Enum class to define and manage error codes for file operations.
18
18
 
19
19
  Creation Date
@@ -57,7 +57,7 @@ Examples
57
57
  >>> vel_data = datatype('example.rdi', "velocity")
58
58
  >>> vel_data = datatype('example.rdi', "echo", beam=4, cell=20)
59
59
 
60
- Other add-on functions and classes inlcude bcolors, safe_open, and ErrorCode.
60
+ Other add-on functions and classes inlcude bcolors, safe_open, and ErrorCode.
61
61
  Examples (add-on)
62
62
  -------------------
63
63
  >>> error = ErrorCode.FILE_NOT_FOUND
@@ -403,72 +403,85 @@ def fileheader(rdi_file):
403
403
  bfile.seek(0, 0)
404
404
  bskip = i = 0
405
405
  hid = [None] * 5
406
- while byt := bfile.read(6):
407
- hid[0], hid[1], hid[2], hid[3], hid[4] = unpack("<BBHBB", byt)
408
- headerid = np.append(headerid, np.int8(hid[0]))
409
- sourceid = np.append(sourceid, np.int16(hid[1]))
410
- byte = np.append(byte, np.int16(hid[2]))
411
- spare = np.append(spare, np.int16(hid[3]))
412
- datatype = np.append(datatype, np.int16(hid[4]))
413
-
414
- # dbyte = bfile.read(2 * datatype[i])
415
- dbyte, error = safe_read(bfile, 2 * datatype[i])
416
- if dbyte is None:
406
+ try:
407
+ while byt := bfile.read(6):
408
+ hid[0], hid[1], hid[2], hid[3], hid[4] = unpack("<BBHBB", byt)
409
+ headerid = np.append(headerid, np.int8(hid[0]))
410
+ sourceid = np.append(sourceid, np.int16(hid[1]))
411
+ byte = np.append(byte, np.int16(hid[2]))
412
+ spare = np.append(spare, np.int16(hid[3]))
413
+ datatype = np.append(datatype, np.int16(hid[4]))
414
+
415
+ # dbyte = bfile.read(2 * datatype[i])
416
+ dbyte, error = safe_read(bfile, 2 * datatype[i])
417
+ if dbyte is None:
418
+ if i == 0:
419
+ error_code = error.code
420
+ dummytuple = ([], [], [], [], [], ensemble, error_code)
421
+ return dummytuple
422
+ else:
423
+ break
424
+
425
+ # Check for id and datatype errors
417
426
  if i == 0:
418
- error_code = error.code
419
- dummytuple = ([], [], [], [], [], ensemble, error_code)
420
- return dummytuple
427
+ if headerid[0] != 127 or sourceid[0] != 127:
428
+ error = ErrorCode.WRONG_RDIFILE_TYPE
429
+ print(bcolors.FAIL + error.message + bcolors.ENDC)
430
+ error_code = error.code
431
+ dummytuple = ([], [], [], [], [], ensemble, error_code)
432
+ return dummytuple
421
433
  else:
422
- break
434
+ if headerid[i] != 127 or sourceid[i] != 127:
435
+ error = ErrorCode.ID_NOT_FOUND
436
+ print(bcolors.FAIL + error.message)
437
+ print(f"Ensembles reset to {i}" + bcolors.ENDC)
438
+ break
439
+
440
+ if datatype[i] != datatype[i - 1]:
441
+ error = ErrorCode.DATATYPE_MISMATCH
442
+ print(bcolors.FAIL + error.message)
443
+ print(f"Data Types for ensemble {i} is {datatype[i - 1]}.")
444
+ print(f"Data Types for ensemble {i + 1} is {datatype[i]}.")
445
+ print(f"Ensembles reset to {i}" + bcolors.ENDC)
446
+ break
423
447
 
424
- # Check for id and datatype errors
425
- if i == 0:
426
- if headerid[0] != 127 or sourceid[0] != 127:
427
- error = ErrorCode.WRONG_RDIFILE_TYPE
428
- print(bcolors.FAIL + error.message + bcolors.ENDC)
448
+ try:
449
+ data = unpack("H" * datatype[i], dbyte)
450
+ address_offset.append(data)
451
+ except:
452
+ error = ErrorCode.FILE_CORRUPTED
429
453
  error_code = error.code
430
454
  dummytuple = ([], [], [], [], [], ensemble, error_code)
431
455
  return dummytuple
432
- else:
433
- if headerid[i] != 127 or sourceid[i] != 127:
434
- error = ErrorCode.ID_NOT_FOUND
435
- print(bcolors.FAIL + error.message + bcolors.ENDC)
436
- break
437
-
438
- if datatype[i] != datatype[i - 1]:
439
- error = ErrorCode.DATATYPE_MISMATCH
440
- print(bcolors.FAIL + error.message)
441
- print(f"Data Types for ensemble {i} is {datatype[i - 1]}.")
442
- print(f"Data Types for ensemble {i + 1} is {datatype[i]}.")
443
- print(f"Ensembles reset to {i}" + bcolors.ENDC)
444
- break
445
-
446
- try:
447
- data = unpack("H" * datatype[i], dbyte)
448
- address_offset.append(data)
449
- except:
450
- error = ErrorCode.FILE_CORRUPTED
451
- error_code = error.code
452
- dummytuple = ([], [], [], [], [], ensemble, error_code)
453
- return dummytuple
454
-
455
- skip_array = [None] * datatype[i]
456
- for dtype in range(datatype[i]):
457
- bseek = int(bskip) + int(address_offset[i][dtype])
458
- bfile.seek(bseek, 0)
459
- readbyte = bfile.read(2)
460
- skip_array[dtype] = int.from_bytes(
461
- readbyte, byteorder="little", signed=False
462
- )
463
-
464
- dataid.append(skip_array)
465
- # bytekip is the number of bytes to skip to reach
466
- # an ensemble from beginning of file.
467
- # ?? Should byteskip be from current position ??
468
- bskip = int(bskip) + int(byte[i]) + 2
469
- bfile.seek(bskip, 0)
470
- byteskip = np.append(byteskip, np.int32(bskip))
471
- i += 1
456
+
457
+ skip_array = [None] * datatype[i]
458
+ for dtype in range(datatype[i]):
459
+ bseek = int(bskip) + int(address_offset[i][dtype])
460
+ bfile.seek(bseek, 0)
461
+ readbyte = bfile.read(2)
462
+ skip_array[dtype] = int.from_bytes(
463
+ readbyte, byteorder="little", signed=False
464
+ )
465
+
466
+ dataid.append(skip_array)
467
+ # bytekip is the number of bytes to skip to reach
468
+ # an ensemble from beginning of file.
469
+ # ?? Should byteskip be from current position ??
470
+ bskip = int(bskip) + int(byte[i]) + 2
471
+ bfile.seek(bskip, 0)
472
+ byteskip = np.append(byteskip, np.int32(bskip))
473
+ i += 1
474
+ except (ValueError, StructError, OverflowError) as e:
475
+ # except:
476
+ print(bcolors.WARNING + "WARNING: The file is broken.")
477
+ print(
478
+ f"Function `fileheader` unable to extract data for ensemble {i + 1}. Total ensembles reset to {i}."
479
+ )
480
+ print(bcolors.UNDERLINE + "Details from struct function" + bcolors.ENDC)
481
+ print(f" Error Type: {type(e).__name__}")
482
+ print(f" Error Details: {e}")
483
+ error = ErrorCode.FILE_CORRUPTED
484
+ ensemble = i
472
485
 
473
486
  ensemble = i
474
487
  bfile.close()
@@ -612,13 +625,14 @@ def fixedleader(rdi_file, byteskip=None, offset=None, idarray=None, ensemble=0):
612
625
 
613
626
  bfile.seek(byteskip[i], 0)
614
627
 
615
- except (ValueError, StructError) as e:
628
+ except (ValueError, StructError, OverflowError) as e:
616
629
  print(bcolors.WARNING + "WARNING: The file is broken.")
617
630
  print(
618
631
  f"Function `fixedleader` unable to extract data for ensemble {i + 1}. Total ensembles reset to {i}."
619
632
  )
620
- print("Details from struct function:")
621
- print(f"An error occurred: {e}" + bcolors.ENDC)
633
+ print(bcolors.UNDERLINE + "Details from struct function" + bcolors.ENDC)
634
+ print(f" Error Type: {type(e).__name__}")
635
+ print(f" Error Details: {e}")
622
636
  error = ErrorCode.FILE_CORRUPTED
623
637
  ensemble = i
624
638
 
@@ -794,13 +808,14 @@ def variableleader(rdi_file, byteskip=None, offset=None, idarray=None, ensemble=
794
808
 
795
809
  bfile.seek(byteskip[i], 0)
796
810
 
797
- except (ValueError, StructError) as e:
811
+ except (ValueError, StructError, OverflowError) as e:
798
812
  print(bcolors.WARNING + "WARNING: The file is broken.")
799
813
  print(
800
814
  f"Function `variableleader` unable to extract data for ensemble {i + 1}. Total ensembles reset to {i}."
801
815
  )
802
- print("Details from struct function:")
803
- print(f"An error occurred: {e}" + bcolors.ENDC)
816
+ print(bcolors.UNDERLINE + "Details from struct function" + bcolors.ENDC)
817
+ print(f" Error Type: {type(e).__name__}")
818
+ print(f" Error Details: {e}")
804
819
  error = ErrorCode.FILE_CORRUPTED
805
820
  ensemble = i
806
821
 
@@ -910,7 +925,9 @@ def datatype(
910
925
  # Velocity is 16 bits and all others are 8 bits.
911
926
  # Create empty array for the chosen variable name.
912
927
  if var_name == "velocity":
913
- var_array = np.full((int(max(beam)), int(max(cell)), ensemble), -32768, dtype="int16")
928
+ var_array = np.full(
929
+ (int(max(beam)), int(max(cell)), ensemble), -32768, dtype="int16"
930
+ )
914
931
  bitstr = "<h"
915
932
  bitint = 2
916
933
  else: # inserted
@@ -957,16 +974,28 @@ def datatype(
957
974
  return (var_array, error.code)
958
975
 
959
976
  # READ DATA
960
- for i in range(ensemble):
961
- bfile.seek(fbyteskip[i], 1)
962
- bdata = bfile.read(2)
963
- for cno in range(int(cell[i])):
964
- for bno in range(int(beam[i])):
965
- bdata = bfile.read(bitint)
966
- varunpack = unpack(bitstr, bdata)
967
- var_array[bno][cno][i] = varunpack[0]
968
- bfile.seek(byteskip[i], 0)
969
- bfile.close()
977
+ i = 0
978
+ try:
979
+ for i in range(ensemble):
980
+ bfile.seek(fbyteskip[i], 1)
981
+ bdata = bfile.read(2)
982
+ for cno in range(int(cell[i])):
983
+ for bno in range(int(beam[i])):
984
+ bdata = bfile.read(bitint)
985
+ varunpack = unpack(bitstr, bdata)
986
+ var_array[bno][cno][i] = varunpack[0]
987
+ bfile.seek(byteskip[i], 0)
988
+ bfile.close()
989
+ except (ValueError, StructError, OverflowError) as e:
990
+ print(bcolors.WARNING + "WARNING: The file is broken.")
991
+ print(
992
+ f"Function `datatype` unable to extract {var_name} for ensemble {i + 1}. Total ensembles reset to {i}."
993
+ )
994
+ print(bcolors.UNDERLINE + "Details from struct function" + bcolors.ENDC)
995
+ print(f" Error Type: {type(e).__name__}")
996
+ print(f" Error Details: {e}")
997
+ error = ErrorCode.FILE_CORRUPTED
998
+ ensemble = i
970
999
 
971
- data = var_array
1000
+ data = var_array[:, :, :ensemble]
972
1001
  return (data, ensemble, cell, beam, error_code)
pyadps/utils/readrdi.py CHANGED
@@ -103,6 +103,7 @@ import sys
103
103
  import numpy as np
104
104
  import pandas as pd
105
105
  from pyadps.utils import pyreadrdi
106
+ from pyadps.utils.pyreadrdi import bcolors
106
107
 
107
108
 
108
109
  class DotDict:
@@ -773,7 +774,7 @@ def vlead_dict(vid):
773
774
  "MPT Minute": "int16",
774
775
  "MPT Second": "int16",
775
776
  "MPT Hundredth": "int16",
776
- "Hdg Std Dev": "int16",
777
+ "Head Std Dev": "int16",
777
778
  "Pitch Std Dev": "int16",
778
779
  "Roll Std Dev": "int16",
779
780
  "ADC Channel 0": "int16",
@@ -1358,7 +1359,7 @@ class ReadFile:
1358
1359
  The RDI ADCP binary file to be read.
1359
1360
  """
1360
1361
 
1361
- def __init__(self, filename):
1362
+ def __init__(self, filename, is_fix_ensemble=True):
1362
1363
  """
1363
1364
  Initializes the ReadFile object and extracts data from the RDI ADCP binary file.
1364
1365
  """
@@ -1534,6 +1535,10 @@ class ReadFile:
1534
1535
  # Add attribute that lists all variables/functions
1535
1536
  self.list_vars = list(vars(self).keys())
1536
1537
 
1538
+ # By default fix ensemble
1539
+ if is_fix_ensemble and not self.isEnsembleEqual:
1540
+ self.fixensemble()
1541
+
1537
1542
  def _copy_attributes_from_var(self):
1538
1543
  for attr_name, attr_value in self.variableleader.__dict__.items():
1539
1544
  # Copy each attribute of var into self
@@ -1552,6 +1557,18 @@ class ReadFile:
1552
1557
  f"'{self.__class__.__name__}' object has no attribute '{name}'"
1553
1558
  )
1554
1559
 
1560
+ def resize_fixedleader(self, newshape):
1561
+ for key in self.fixedleader.fleader:
1562
+ attr_name = key.lower().replace(" ", "_")
1563
+ attr_obj = getattr(self.fixedleader, attr_name)
1564
+ attr_obj.data = attr_obj.data[:newshape]
1565
+
1566
+ def resize_variableleader(self, newshape):
1567
+ for key in self.variableleader.vleader:
1568
+ attr_name = key.lower().replace(" ", "_")
1569
+ attr_obj = getattr(self.variableleader, attr_name)
1570
+ attr_obj.data = attr_obj.data[:newshape]
1571
+
1555
1572
  def fixensemble(self, min_cutoff=0):
1556
1573
  """
1557
1574
  Fixes the ensemble size across all data types in the file if they differ.
@@ -1582,10 +1599,18 @@ class ReadFile:
1582
1599
  self.fileheader.dataid = self.fileheader.dataid[:minens, :]
1583
1600
  if "Fixed Leader" in datatype_array:
1584
1601
  self.fixedleader.data = self.fixedleader.data[:, :minens]
1602
+ self.fixedleader.fleader = {
1603
+ k: v[:minens] for k, v in self.fixedleader.fleader.items()
1604
+ }
1585
1605
  self.fixedleader.ensembles = minens
1606
+ self.resize_fixedleader(minens)
1586
1607
  if "Variable Leader" in datatype_array:
1587
1608
  self.variableleader.data = self.variableleader.data[:, :minens]
1609
+ self.variableleader.vleader = {
1610
+ k: v[:minens] for k, v in self.variableleader.vleader.items()
1611
+ }
1588
1612
  self.variableleader.ensembles = minens
1613
+ self.resize_variableleader(minens)
1589
1614
  if "Velocity" in datatype_array:
1590
1615
  self.velocity.data = self.velocity.data[:, :, :minens]
1591
1616
  self.velocity.ensembles = minens
@@ -1601,7 +1626,13 @@ class ReadFile:
1601
1626
  if "Status" in datatype_array:
1602
1627
  self.status.data = self.status.data[:, :, :minens]
1603
1628
  self.status.ensembles = minens
1604
- print(f"Ensembles fixed to {minens}. All data types have same ensembles.")
1629
+
1630
+ self.time = self.time[:minens]
1631
+ print(
1632
+ bcolors.OKBLUE
1633
+ + f"Ensembles fixed to {minens}. All data types have same ensembles."
1634
+ + bcolors.ENDC
1635
+ )
1605
1636
  else:
1606
1637
  print(
1607
1638
  "WARNING: No response was initiated. All data types have same ensemble."