pyadps 0.3.3b0__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.
@@ -0,0 +1,969 @@
1
+ """
2
+ pyreadrdi.py
3
+
4
+ Module Overview
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,
8
+ and data types like velocity, correlation, echo intensity, and percent good.
9
+ Currently reads only PD0 format.
10
+
11
+ Modules
12
+ -------------------
13
+ - fileheader: Function to read and parse the file header information.
14
+ - fixedleader: Function to read and parse the fixed leader section of an RDI file.
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.
17
+ - ErrorCode: Enum class to define and manage error codes for file operations.
18
+
19
+ Creation Date
20
+ --------------
21
+ 2024-09-01
22
+
23
+ Last Modified Date
24
+ --------------
25
+ 2024-09-01
26
+
27
+ Version
28
+ -------
29
+ 0.2.0
30
+
31
+ Author
32
+ ------
33
+ [P. Amol] <your.email@example.com>
34
+
35
+ License
36
+ -------
37
+ This module is licensed under the MIT License. See LICENSE file for details.
38
+
39
+ Dependencies
40
+ ------------
41
+ - numpy: Required for handling array operations.
42
+ - struct: Required for unpacking binary data.
43
+ - io: Provides file handling capabilities, including file-like object support.
44
+ - enum: Provides support for creating enumerations, used for defining error codes.
45
+
46
+ Usage
47
+ -----
48
+ To use this module, import the necessary functions as follows:
49
+
50
+ >>> from readrdi import fileheader, fixedleader, variableleader, datatype
51
+
52
+ Examples
53
+ --------
54
+ >>> header_data = fileheader('example.rdi')
55
+ >>> fixed_data, ensemble, error_code = fixedleader('example.rdi')
56
+ >>> var_data = variableleader('example.rdi')
57
+ >>> vel_data = datatype('example.rdi', "velocity")
58
+ >>> vel_data = datatype('example.rdi', "echo", beam=4, cell=20)
59
+
60
+ Other add-on functions and classes inlcude bcolors, safe_open, and ErrorCode.
61
+ Examples (add-on)
62
+ -------------------
63
+ >>> error = ErrorCode.FILE_NOT_FOUND
64
+
65
+ """
66
+
67
+ import io
68
+ import os
69
+ import sys
70
+ from enum import Enum
71
+ from struct import error as StructError
72
+ from struct import unpack
73
+
74
+ import numpy as np
75
+
76
+
77
+ class bcolors:
78
+ """
79
+ Terminal color codes for styling console output.
80
+
81
+ This class provides a set of color codes and text formatting options for styling
82
+ terminal or console output. The codes can be used to change the text color and style
83
+ in a terminal that supports ANSI escape sequences.
84
+
85
+ Attributes
86
+ ----------
87
+ HEADER : str
88
+ Color code for magenta text, typically used for headers.
89
+ OKBLUE : str
90
+ Color code for blue text, typically used for general information.
91
+ OKCYAN : str
92
+ Color code for cyan text, used for informational messages.
93
+ OKGREEN : str
94
+ Color code for green text, typically used for success messages.
95
+ WARNING : str
96
+ Color code for yellow text, used for warnings.
97
+ FAIL : str
98
+ Color code for red text, used for errors or failures.
99
+ ENDC : str
100
+ Reset color code to default. Resets the color and formatting.
101
+ BOLD : str
102
+ Bold text formatting code. Makes text bold.
103
+ UNDERLINE : str
104
+ Underlined text formatting code. Underlines the text.
105
+
106
+ Usage
107
+ -----
108
+ To use these color codes, prepend them to your string and append `bcolors.ENDC`
109
+ to reset the formatting. For example:
110
+
111
+ >>> print(f"{bcolors.OKGREEN}Success{bcolors.ENDC}")
112
+ >>> print(f"{bcolors.WARNING}Warning: This is a warning.{bcolors.ENDC}")
113
+
114
+ Examples
115
+ --------
116
+ >>> print(f"{bcolors.OKBLUE}This text is blue.{bcolors.ENDC}")
117
+ >>> print(f"{bcolors.FAIL}This text is red and indicates an error.{bcolors.ENDC}")
118
+ >>> print(f"{bcolors.BOLD}{bcolors.UNDERLINE}Bold and underlined text.{bcolors.ENDC}")
119
+
120
+ Notes
121
+ -----
122
+ These color codes use ANSI escape sequences and may not be supported in all terminal
123
+ environments. The appearance may vary depending on the terminal emulator used.
124
+ """
125
+
126
+ HEADER = "\033[95m"
127
+ OKBLUE = "\033[94m"
128
+ OKCYAN = "\033[96m"
129
+ OKGREEN = "\033[92m"
130
+ WARNING = "\033[93m"
131
+ FAIL = "\033[91m"
132
+ ENDC = "\033[0m"
133
+ BOLD = "\033[1m"
134
+ UNDERLINE = "\033[4m"
135
+
136
+ # HEADER = ""
137
+ # OKBLUE = ""
138
+ # OKCYAN = ""
139
+ # OKGREEN = ""
140
+ # WARNING = ""
141
+ # FAIL = ""
142
+ # ENDC = ""
143
+ # BOLD = ""
144
+ # UNDERLINE = ""
145
+
146
+
147
+ class ErrorCode(Enum):
148
+ """
149
+ Enumeration for error codes with associated messages.
150
+ This enum provides a set of error codes and their corresponding descriptive messages.
151
+ It is used to standardize error reporting and handling within the application.
152
+ Attributes
153
+ ----------
154
+ SUCCESS : tuple
155
+ Represents a successful operation.
156
+ FILE_NOT_FOUND : tuple
157
+ Error code for when a file is not found.
158
+ PERMISSION_DENIED : tuple
159
+ Error code for when access to a resource is denied.
160
+ IO_ERROR: tuple
161
+ Error Code for when the file fails to open.
162
+ OUT_OF_MEMORY : tuple
163
+ Error code for when the system runs out of memory.
164
+ WRONG_RDIFILE_TYPE : tuple
165
+ Error code for when a file type is not supported by RDI or incorrect.
166
+ ID_NOT_FOUND: tuple
167
+ Error code for when RDI file is found but the data type ID does not match.
168
+ FILE_CORRUPTED : tuple
169
+ Error code for when a file is corrupted and cannot be read.
170
+ DATATYPE_MISMATCH: tuple
171
+ Error code for when the data type is not same as the previous ensemble.
172
+ VALUE_ERROR: tuple
173
+ Error code for incorrect argument.
174
+ UNKNOWN_ERROR : tuple
175
+ Error code for an unspecified or unknown error.
176
+
177
+ Methods
178
+ -------
179
+ get_message(code)
180
+ Retrieves the descriptive message corresponding to a given error code.
181
+
182
+ Parameters
183
+ ----------
184
+ code : int
185
+ The error code for which the message is to be retrieved.
186
+
187
+ Returns
188
+ -------
189
+ str
190
+ The descriptive message associated with the provided error code. If the code
191
+ is not valid, returns \"Error: Invalid error code.\"
192
+ """
193
+
194
+ SUCCESS = (0, "Success")
195
+ FILE_NOT_FOUND = (1, "Error: File not found.")
196
+ PERMISSION_DENIED = (2, "Error: Permission denied.")
197
+ IO_ERROR = (3, "IO Error: Unable to open file.")
198
+ OUT_OF_MEMORY = (4, "Error: Out of memory.")
199
+ WRONG_RDIFILE_TYPE = (5, "Error: Wrong RDI File Type.")
200
+ ID_NOT_FOUND = (6, "Error: Data type ID not found.")
201
+ DATATYPE_MISMATCH = (7, "Warning: Data type mismatch.")
202
+ FILE_CORRUPTED = (8, "Warning: File Corrupted.")
203
+ VALUE_ERROR = (9, "Value Error for incorrect argument.")
204
+ UNKNOWN_ERROR = (99, "Unknown error.")
205
+
206
+ def __init__(self, code, message):
207
+ self.code = code
208
+ self.message = message
209
+
210
+ @classmethod
211
+ def get_message(cls, code):
212
+ for error in cls:
213
+ if error.code == code:
214
+ return error.message
215
+ else: # inserted
216
+ return "Error: Invalid error code."
217
+
218
+
219
+ def safe_open(filename, mode="rb"):
220
+ """
221
+ Safely open a file, handling common file-related errors.
222
+
223
+ This function attempts to open a file and handles exceptions that may occur,
224
+ such as the file not being found or a lack of necessary permissions.
225
+ It returns the file object if successful, or an appropriate error message.
226
+
227
+ Parameters
228
+ ----------
229
+ filepath : str
230
+ The path to the file that you want to open.
231
+ mode : str, optional
232
+ The mode in which to open the file (e.g., 'r' for reading, 'w' for writing).
233
+ Defaults to 'r'.
234
+
235
+ Returns
236
+ -------
237
+ file object or None
238
+ If the file is successfully opened, the file object is returned.
239
+ If an error occurs, None is returned and an error message is printed.
240
+
241
+ Raises
242
+ ------
243
+ FileNotFoundError
244
+ If the file does not exist.
245
+ PermissionError
246
+ If the file cannot be opened due to insufficient permissions.
247
+ IOError
248
+ If an I/O error occurs during the opening of the file.
249
+ MemoryError
250
+ If required memory cannot be allocated by Python.
251
+ Exception
252
+ If an unexpected error occurs
253
+
254
+
255
+
256
+ Examples
257
+ --------
258
+ >>> f = safe_open('existing_file.txt')
259
+ >>> if f:
260
+ ... content = f.read()
261
+ ... f.close()
262
+
263
+ >>> safe_open('nonexistent_file.txt')
264
+ Error: File not found.
265
+
266
+ >>> safe_open('/restricted_access_file.txt')
267
+ Error: Permission denied.
268
+ """
269
+ try:
270
+ filename = os.path.abspath(filename)
271
+ file = open(filename, mode)
272
+ return (file, ErrorCode.SUCCESS)
273
+ except FileNotFoundError as e:
274
+ print(f"FileNotFoundError: The file '{filename}' was not found: {e}")
275
+ return (None, ErrorCode.FILE_NOT_FOUND)
276
+ except PermissionError as e:
277
+ print(f"PermissionError: Permission denied for '{filename}': {e}")
278
+ return (None, ErrorCode.PERMISSION_DENIED)
279
+ except IOError as e:
280
+ print(f"IOError: An error occurred trying to open '{filename}': {e}")
281
+ return (None, ErrorCode.IO_ERROR)
282
+ except MemoryError as e:
283
+ print(f"MemoryError: Out of memory '{filename}':{e}")
284
+ return (None, ErrorCode.OUT_OF_MEMORY)
285
+ except Exception as e:
286
+ print(f"An unexpected error occurred: {e}")
287
+ return (None, ErrorCode.UNKNOWN_ERROR)
288
+
289
+
290
+ def safe_read(bfile, num_bytes):
291
+ """
292
+ Safely read a specified number of bytes from a binary file.
293
+
294
+ This function attempts to read `num_bytes` from the provided binary file object.
295
+ It includes error handling for I/O errors, unexpected end-of-file, and other potential issues.
296
+
297
+ Parameters
298
+ ----------
299
+ bfile : file object
300
+ A binary file object opened for reading.
301
+ num_bytes : int
302
+ The number of bytes to read from the file.
303
+
304
+ Returns
305
+ -------
306
+ bytes or None
307
+ The bytes read from the file, or None if an error occurred.
308
+
309
+ Raises
310
+ ------
311
+ IOError
312
+ If an I/O error occurs during the file read operation.
313
+ OSError
314
+ If an operating system-related error occurs.
315
+ ValueError
316
+ If fewer than `num_bytes` are read from the file, indicating an unexpected end of file.
317
+ """
318
+ try:
319
+ readbytes = bfile.read(num_bytes)
320
+
321
+ if len(readbytes) != num_bytes:
322
+ print(f"Unexpected end of file: fewer than {num_bytes} bytes were read.")
323
+ return (None, ErrorCode.FILE_CORRUPTED)
324
+ else:
325
+ return (readbytes, ErrorCode.SUCCESS)
326
+
327
+ except (IOError, OSError) as e:
328
+ print(f"File read error: {e}")
329
+ return (None, ErrorCode.IO_ERROR)
330
+ except ValueError as e:
331
+ print(f"Value error: {e}")
332
+ return (None, ErrorCode.VALUE_ERROR)
333
+
334
+
335
+ def fileheader(rdi_file):
336
+ """
337
+ Parse the binary RDI ADCP file and extract header information.
338
+
339
+ This function reads a binary file and extracts several fields from its header,
340
+ returning them as numpy arrays and integers.
341
+
342
+ Parameters
343
+ ----------
344
+ filename : str
345
+ The path to the binary file to be read.
346
+
347
+ Returns
348
+ -------
349
+ datatype : numpy.ndarray
350
+ A 1D numpy array of type `int16` representing the data type field from the file header.
351
+ byte : numpy.ndarray
352
+ A 1D numpy array of type `int16` representing the byte information from the file header.
353
+ byteskip : numpy.ndarray
354
+ A 1D numpy array of type `int32` indicating how many bytes to skip in the file.
355
+ address_offset : numpy.ndarray
356
+ A 2D numpy array of type `int` representing the address offsets within the file.
357
+ dataid : numpy.ndarray
358
+ A 2D numpy array of type `int` representing the data IDs extracted from the file header.
359
+ ensemble : int
360
+ An integer representing the ensemble information from the file header.
361
+ error_code : int
362
+ An integer representing the error code, where 0 typically indicates success.
363
+
364
+ Raises
365
+ ------
366
+ IOError
367
+ If there is an issue opening or reading from the file.
368
+ ValueError
369
+ If the file does not contain the expected structure or the data cannot be parsed correctly.
370
+
371
+ Notes
372
+ -----
373
+ This function assumes that the file is in a specific RDI binary format and may not work correctly
374
+ if the file format differs.
375
+
376
+ Examples
377
+ --------
378
+ >>> datatype, byte, byteskip, address_offset, dataid, ensemble, error_code = fileheader("data.bin")
379
+ >>> if error_code == 0:
380
+ ... print("File header read successfully.")
381
+ ... else:
382
+ ... print(f"Error code: {error_code}")
383
+ """
384
+
385
+ filename = rdi_file
386
+ headerid = np.array([], dtype="int8")
387
+ sourceid = np.array([], dtype="int8")
388
+ byte = np.array([], dtype="int16")
389
+ spare = np.array([], dtype="int8")
390
+ datatype = np.array([], dtype="int16")
391
+ address_offset = []
392
+ ensemble = 0
393
+ error_code = 0
394
+ dataid = []
395
+ byteskip = np.array([], dtype="int32")
396
+ dummytuple = ([], [], [], [], [], ensemble, error_code)
397
+
398
+ bfile, error = safe_open(filename, mode="rb")
399
+ if bfile is None:
400
+ error_code = error.code
401
+ dummytuple = ([], [], [], [], [], ensemble, error_code)
402
+ return dummytuple
403
+ bfile.seek(0, 0)
404
+ bskip = i = 0
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:
417
+ if i == 0:
418
+ error_code = error.code
419
+ dummytuple = ([], [], [], [], [], ensemble, error_code)
420
+ return dummytuple
421
+ else:
422
+ break
423
+
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)
429
+ error_code = error.code
430
+ dummytuple = ([], [], [], [], [], ensemble, error_code)
431
+ 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
472
+
473
+ ensemble = i
474
+ bfile.close()
475
+ address_offset = np.array(address_offset)
476
+ dataid = np.array(dataid)
477
+ datatype = datatype[0:ensemble]
478
+ byte = byte[0:ensemble]
479
+ byteskip = byteskip[0:ensemble]
480
+ error_code = error.code
481
+ return (datatype, byte, byteskip, address_offset, dataid, ensemble, error_code)
482
+
483
+
484
+ def fixedleader(rdi_file, byteskip=None, offset=None, idarray=None, ensemble=0):
485
+ """
486
+ Parse the fixed leader data from binary RDI ADCP file.
487
+
488
+ This function extracts the fixed leader section of an RDI file. It uses
489
+ optional parameters that can be obtained from the `fileheader` function. The function
490
+ returns data extracted from the file, the ensemble number, and an error code indicating
491
+ the status of the operation.
492
+
493
+ Parameters
494
+ ----------
495
+ rdi_file : str
496
+ The path to the RDI binary file from which to read the fixed leader section.
497
+ byteskip : numpy.ndarray, optional
498
+ Number of bytes to skip before reading the fixed leader section. If not provided,
499
+ defaults to None. Can be obtained from the `fileheader` function.
500
+ offset : numpy.ndarray, optional
501
+ Offset in bytes from the start of the file to the fixed leader section. If not provided,
502
+ defaults to None. Can be obtained from the `fileheader` function.
503
+ idarray : numpy.ndarray, optional
504
+ An optional list of IDs to be processed. If not provided, defaults to None. Can be obtained
505
+ from the `fileheader` function.
506
+ ensemble : int, optional
507
+ The ensemble number to be used or processed. If not provided, defaults to 0. Can be obtained
508
+ from the `fileheader` function.
509
+
510
+ Returns
511
+ -------
512
+ data : numpy.ndarray
513
+ Extracted data from the fixed leader section of the file. The type of `data` depends on the
514
+ implementation and file structure.
515
+ ensemble : int
516
+ The ensemble number processed or retrieved from the file.
517
+ error_code : int
518
+ An error code indicating the status of the operation.
519
+
520
+ Raises
521
+ ------
522
+ FileNotFoundError
523
+ If the RDI file cannot be found.
524
+ PermissionError
525
+ If the file cannot be accessed due to permission issues.
526
+ ValueError
527
+ If provided parameters are of incorrect type or value.
528
+
529
+ Examples
530
+ --------
531
+ >>> data, ensemble, error_code = fixedleader('data.rdi', byteskip=10, offset=50)
532
+ >>> print(data, ensemble, error_code)
533
+ (data_from_file, 0, 0)
534
+
535
+ >>> data, ensemble, error_code = fixedleader('data.rdi', idarray=[1, 2, 3])
536
+ >>> print(data, ensemble, error_code)
537
+ (data_from_file, 0, 0)
538
+ """
539
+
540
+ filename = rdi_file
541
+ error_code = 0
542
+
543
+ if (
544
+ not all((isinstance(v, np.ndarray) for v in (byteskip, offset, idarray)))
545
+ or ensemble == 0
546
+ ):
547
+ _, _, byteskip, offset, idarray, ensemble, error_code = fileheader(filename)
548
+
549
+ fid = [[0] * ensemble for _ in range(36)]
550
+
551
+ bfile, error = safe_open(filename, "rb")
552
+ if bfile is None:
553
+ return (fid, ensemble, error.code)
554
+ if error.code == 0 and error_code != 0:
555
+ error.code = error_code
556
+ error.message = error.get_message(error.code)
557
+
558
+ bfile.seek(0, 0)
559
+ for i in range(ensemble):
560
+ fbyteskip = None
561
+ for count, item in enumerate(idarray[i]):
562
+ if item in (0, 1):
563
+ fbyteskip = offset[0][count]
564
+ if fbyteskip == None:
565
+ error = ErrorCode.ID_NOT_FOUND
566
+ ensemble = i
567
+ print(bcolors.WARNING + error.message)
568
+ print(f"Total ensembles reset to {i}." + bcolors.ENDC)
569
+ break
570
+ else: # inserted
571
+ try:
572
+ bfile.seek(fbyteskip, 1)
573
+ bdata = bfile.read(59)
574
+ # Fixed Leader ID, CPU Version no. & Revision no.
575
+ (fid[0][i], fid[1][i], fid[2][i]) = unpack("<HBB", bdata[0:4])
576
+ if fid[0][i] not in (0, 1):
577
+ error = ErrorCode.ID_NOT_FOUND
578
+ ensemble = i
579
+ print(bcolors.WARNING + error.message)
580
+ print(f"Total ensembles reset to {i}." + bcolors.ENDC)
581
+ break
582
+ # System configuration & Real/Slim flag
583
+ (fid[3][i], fid[4][i]) = unpack("<HB", bdata[4:7])
584
+ # Lag Length, number of beams & Number of cells
585
+ (fid[5][i], fid[6][i], fid[7][i]) = unpack("<BBB", bdata[7:10])
586
+ # Pings per Ensemble, Depth cell length & Blank after transmit
587
+ (fid[8][i], fid[9][i], fid[10][i]) = unpack("<HHH", bdata[10:16])
588
+ # Signal Processing mode, Low correlation threshold & No. of
589
+ # code repetition
590
+ (fid[11][i], fid[12][i], fid[13][i]) = unpack("<BBB", bdata[16:19])
591
+ # Percent good minimum & Error velocity threshold
592
+ (fid[14][i], fid[15][i]) = unpack("<BH", bdata[19:22])
593
+ # Time between ping groups (TP command)
594
+ # Minute, Second, Hundredth
595
+ (fid[16][i], fid[17][i], fid[18][i]) = unpack("<BBB", bdata[22:25])
596
+ # Coordinate transform, Heading alignment & Heading bias
597
+ (fid[19][i], fid[20][i], fid[21][i]) = unpack("<BHH", bdata[25:30])
598
+ # Sensor source & Sensor available
599
+ (fid[22][i], fid[23][i]) = unpack("<BB", bdata[30:32])
600
+ # Bin 1 distance, Transmit pulse length & Reference layer ave
601
+ (fid[24][i], fid[25][i], fid[26][i]) = unpack("<HHH", bdata[32:38])
602
+ # False target threshold, Spare & Transmit lag distance
603
+ (fid[27][i], fid[28][i], fid[29][i]) = unpack("<BBH", bdata[38:42])
604
+ # CPU board serial number (Big Endian)
605
+ (fid[30][i]) = unpack(">Q", bdata[42:50])[0]
606
+ # (fid[30][i], fid[31][i])= struct.unpack('>II', packed_data)
607
+ # fid[30][i] = int.from_bytes(bdata[42:50], byteorder="big", signed=False)
608
+ # System bandwidth, system power & Spare
609
+ (fid[31][i], fid[32][i], fid[33][i]) = unpack("<HBB", bdata[50:54])
610
+ # Instrument serial number & Beam angle
611
+ (fid[34][i], fid[35][i]) = unpack("<LB", bdata[54:59])
612
+
613
+ bfile.seek(byteskip[i], 0)
614
+
615
+ except (ValueError, StructError) as e:
616
+ print(bcolors.WARNING + "WARNING: The file is broken.")
617
+ print(
618
+ f"Function `fixedleader` unable to extract data for ensemble {i + 1}. Total ensembles reset to {i}."
619
+ )
620
+ print("Details from struct function:")
621
+ print(f"An error occurred: {e}" + bcolors.ENDC)
622
+ error = ErrorCode.FILE_CORRUPTED
623
+ ensemble = i
624
+
625
+ except (OSError, io.UnsupportedOperation) as e:
626
+ print(bcolors.WARNING + "WARNING: The file is broken.")
627
+ print(
628
+ f"Function `fixedleader` unable to extract data for ensemble {i + 1}. Total ensembles reset to {i}."
629
+ )
630
+ print(f"File seeking error at iteration {i}: {e}" + bcolors.ENDC)
631
+ error = ErrorCode.FILE_CORRUPTED
632
+ ensemble = i
633
+ bfile.close()
634
+ error_code = error.code
635
+ fid = np.array(fid)
636
+ data = fid[:, :ensemble]
637
+ return (data, ensemble, error_code)
638
+
639
+
640
+ def variableleader(rdi_file, byteskip=None, offset=None, idarray=None, ensemble=0):
641
+ """
642
+ Parse the variable leader data from binary RDI ADCP file.
643
+
644
+ This function extracts the variable leader section of an RDI file. It uses
645
+ optional parameters that can be obtained from the `fileheader` function. The function
646
+ returns data extracted from the file, the ensemble number, and an error code indicating
647
+ the status of the operation.
648
+
649
+ Parameters
650
+ ----------
651
+ rdi_file : str
652
+ The path to the RDI binary file from which to read the fixed leader section.
653
+ byteskip : numpy.ndarray, optional
654
+ Number of bytes to skip before reading the fixed leader section. If not provided,
655
+ defaults to None. Can be obtained from the `fileheader` function.
656
+ offset : numpy.ndarray, optional
657
+ Offset in bytes from the start of the file to the fixed leader section. If not provided,
658
+ defaults to None. Can be obtained from the `fileheader` function.
659
+ idarray : numpy.ndarray, optional
660
+ An optional list of IDs to be processed. If not provided, defaults to None. Can be obtained
661
+ from the `fileheader` function.
662
+ ensemble : int, optional
663
+ The ensemble number to be used or processed. If not provided, defaults to 0. Can be obtained
664
+ from the `fileheader` function.
665
+
666
+ Returns
667
+ -------
668
+ data : numpy.ndarray
669
+ Extracted data from the variable leader section of the file.
670
+ ensemble : int
671
+ The ensemble number processed or retrieved from the file.
672
+ error_code : int
673
+ An error code indicating the status of the operation.
674
+
675
+ Raises
676
+ ------
677
+ FileNotFoundError
678
+ If the RDI file cannot be found.
679
+ PermissionError
680
+ If the file cannot be accessed due to permission issues.
681
+ ValueError
682
+
683
+ If provided parameters are of incorrect type or value.
684
+ Examples
685
+ --------
686
+ >>> data, ensemble, error_code = fixedleader('data.rdi', byteskip=10, offset=50)
687
+ >>> print(data, ensemble, error_code)
688
+ (data_from_file, 0, 0)
689
+
690
+ >>> data, ensemble, error_code = fixedleader('data.rdi', idarray=[1, 2, 3])
691
+ >>> print(data, ensemble, error_code)
692
+ """
693
+
694
+ filename = rdi_file
695
+ error_code = 0
696
+ if (
697
+ not all((isinstance(v, np.ndarray) for v in (byteskip, offset, idarray)))
698
+ or ensemble == 0
699
+ ):
700
+ _, _, byteskip, offset, idarray, ensemble, error_code = fileheader(filename)
701
+ vid = [[0] * ensemble for _ in range(48)]
702
+ bfile, error = safe_open(filename, "rb")
703
+ if bfile is None:
704
+ return (vid, ensemble, error.code)
705
+ if error.code == 0 and error_code != 0:
706
+ error.code = error_code
707
+ error.message = error.get_message(error.code)
708
+ bfile.seek(0, 0)
709
+ for i in range(ensemble):
710
+ fbyteskip = None
711
+ for count, item in enumerate(idarray[i]):
712
+ if item in (128, 129):
713
+ fbyteskip = offset[0][count]
714
+ if fbyteskip == None:
715
+ error = ErrorCode.ID_NOT_FOUND
716
+ ensemble = i
717
+ print(bcolors.WARNING + error.message)
718
+ print(f"Total ensembles reset to {i}." + bcolors.ENDC)
719
+ break
720
+ else:
721
+ try:
722
+ bfile.seek(fbyteskip, 1)
723
+ bdata = bfile.read(65)
724
+ vid[0][i], vid[1][i] = unpack("<HH", bdata[0:4])
725
+ if vid[0][i] not in (128, 129):
726
+ error = ErrorCode.ID_NOT_FOUND
727
+ ensemble = i
728
+ print(bcolors.WARNING + error.message)
729
+ print(f"Total ensembles reset to {i}." + bcolors.ENDC)
730
+ break
731
+ sys.exit(f"Variable Leader not found for Ensemble {i}")
732
+ # Extract WorkHorse ADCP’s real-time clock (RTC)
733
+ # Year, Month, Day, Hour, Minute, Second & Hundredth
734
+ (
735
+ vid[2][i],
736
+ vid[3][i],
737
+ vid[4][i],
738
+ vid[5][i],
739
+ vid[6][i],
740
+ vid[7][i],
741
+ vid[8][i],
742
+ ) = unpack("<BBBBBBB", bdata[4:11])
743
+ # Extract Ensemble # MSB & BIT Result
744
+ (vid[9][i], vid[10][i]) = unpack("<BH", bdata[11:14])
745
+ # Extract sensor variables (directly or derived):
746
+ # Sound Speed, Transducer Depth, Heading,
747
+ # Pitch, Roll, Temperature & Salinity
748
+ (
749
+ vid[11][i],
750
+ vid[12][i],
751
+ vid[13][i],
752
+ vid[14][i],
753
+ vid[15][i],
754
+ vid[16][i],
755
+ vid[17][i],
756
+ ) = unpack("<HHHhhHh", bdata[14:28])
757
+ # Extract [M]inimum Pre-[P]ing Wait [T]ime between ping groups
758
+ # MPT minutes, MPT seconds & MPT hundredth
759
+ (vid[18][i], vid[19][i], vid[20][i]) = unpack("<BBB", bdata[28:31])
760
+ # Extract standard deviation of motion sensors:
761
+ # Heading, Pitch, & Roll
762
+ (vid[21][i], vid[22][i], vid[23][i]) = unpack("<BBB", bdata[31:34])
763
+ # Extract ADC Channels (8)
764
+ (
765
+ vid[24][i],
766
+ vid[25][i],
767
+ vid[26][i],
768
+ vid[27][i],
769
+ vid[28][i],
770
+ vid[29][i],
771
+ vid[30][i],
772
+ vid[31][i],
773
+ ) = unpack("<BBBBBBBB", bdata[34:42])
774
+ # Extract error status word (4)
775
+ (vid[32][i], vid[33][i], vid[34][i], vid[35][i]) = unpack(
776
+ "<BBBB", bdata[42:46]
777
+ )
778
+ # Extract Reserved, Pressure, Pressure Variance & Spare
779
+ (vid[36][i], vid[37][i], vid[38][i], vid[39][i]) = unpack(
780
+ "<HiiB", bdata[46:57]
781
+ )
782
+ # Extract Y2K time
783
+ # Century, Year, Month, Day, Hour, Minute, Second, Hundredth
784
+ (
785
+ vid[40][i],
786
+ vid[41][i],
787
+ vid[42][i],
788
+ vid[43][i],
789
+ vid[44][i],
790
+ vid[45][i],
791
+ vid[46][i],
792
+ vid[47][i],
793
+ ) = unpack("<BBBBBBBB", bdata[57:65])
794
+
795
+ bfile.seek(byteskip[i], 0)
796
+
797
+ except (ValueError, StructError) as e:
798
+ print(bcolors.WARNING + "WARNING: The file is broken.")
799
+ print(
800
+ f"Function `variableleader` unable to extract data for ensemble {i + 1}. Total ensembles reset to {i}."
801
+ )
802
+ print("Details from struct function:")
803
+ print(f"An error occurred: {e}" + bcolors.ENDC)
804
+ error = ErrorCode.FILE_CORRUPTED
805
+ ensemble = i
806
+
807
+ except (OSError, io.UnsupportedOperation) as e:
808
+ print(bcolors.WARNING + "WARNING: The file is broken.")
809
+ print(
810
+ f"Function `variableleader` unable to extract data for ensemble {i + 1}. Total ensembles reset to {i}."
811
+ )
812
+ print(f"File seeking error at iteration {i}: {e}" + bcolors.ENDC)
813
+ error = ErrorCode.FILE_CORRUPTED
814
+ ensemble = i
815
+
816
+ bfile.close()
817
+ error_code = error.code
818
+ vid = np.array(vid, dtype="int32")
819
+ data = vid[:, :ensemble]
820
+ return (data, ensemble, error_code)
821
+
822
+
823
+ def datatype(
824
+ filename,
825
+ var_name,
826
+ cell=0,
827
+ beam=0,
828
+ byteskip=None,
829
+ offset=None,
830
+ idarray=None,
831
+ ensemble=0,
832
+ ):
833
+ """
834
+ Parse 3D data from binary RDI ADCP file.
835
+
836
+ This function extracts 3D data like velocity, echo intensity,
837
+ correlation, percent good, and status from the binary RDI file.
838
+ It uses optional parameters can be obtained from the
839
+ `fileheader` function and `variableleader` functions. The function
840
+ returns data of shape (beam, cell, ensemble). The number of beams,
841
+ cells and ensembles along with error code are also returned.
842
+
843
+ Parameters
844
+ ----------
845
+ filename : TYPE STRING
846
+ RDI ADCP binary file. The function can currently extract Workhorse,
847
+ Ocean Surveyor, and DVS files.
848
+
849
+ var_name : TYPE STRING
850
+ Extracts RDI variables that are functions of beam and cells.
851
+ List of permissible variable names: 'velocity', 'correlation',
852
+ 'echo', 'percent good', 'status'
853
+
854
+ Returns
855
+ -------
856
+ data : numpy.ndarray
857
+ Returns a 3-D array of size (beam, cell, ensemble) for the var_name.
858
+ beam: int
859
+ Returns number of beams.
860
+ cell: int
861
+ Returns number of cells.
862
+ ensemble: int
863
+ Returns number of ensembles.
864
+
865
+ """
866
+
867
+ varid = dict()
868
+
869
+ # Define file ids:
870
+ varid = {
871
+ "velocity": (256, 257),
872
+ "correlation": (512, 513),
873
+ "echo": (768, 769),
874
+ "percent good": (1024, 1025),
875
+ "status": (1280, 1281),
876
+ }
877
+ error_code = 0
878
+
879
+ # Check for optional arguments.
880
+ # -----------------------------
881
+ # These arguments are outputs of fileheader function.
882
+ # Makes the code faster if the fileheader function is already executed.
883
+ if (
884
+ not all((isinstance(v, np.ndarray) for v in (byteskip, offset, idarray)))
885
+ or ensemble == 0
886
+ ):
887
+ _, _, byteskip, offset, idarray, ensemble, error_code = fileheader(filename)
888
+ if error_code > 0 and error_code < 6:
889
+ return ([], error_code)
890
+
891
+ # These arguments are outputs of fixedleader function.
892
+ # Makes the code faster if the fixedheader function is already executed.
893
+ if cell == 0 or beam == 0:
894
+ flead, ensemble, fl_error_code = fixedleader(
895
+ filename,
896
+ byteskip=byteskip,
897
+ offset=offset,
898
+ idarray=idarray,
899
+ ensemble=ensemble,
900
+ )
901
+ cell = int(flead[7][0])
902
+ beam = int(flead[6][0])
903
+ if fl_error_code != 0:
904
+ error_code = fl_error_code
905
+ else:
906
+ cell = int(cell)
907
+ beam = int(beam)
908
+
909
+ # Velocity is 16 bits and all others are 8 bits.
910
+ # Create empty array for the chosen variable name.
911
+ if var_name == "velocity":
912
+ var_array = np.zeros((beam, cell, ensemble), dtype="int16")
913
+ bitstr = "<h"
914
+ bitint = 2
915
+ else: # inserted
916
+ var_array = np.zeros((beam, cell, ensemble), dtype="uint8")
917
+ bitstr = "<B"
918
+ bitint = 1
919
+ # -----------------------------
920
+
921
+ # Read the file in safe mode.
922
+ bfile, error = safe_open(filename, "rb")
923
+ if bfile is None:
924
+ return (var_array, ensemble, error.code)
925
+ if error.code == 0 and error_code != 0:
926
+ error.code = error_code
927
+ error.message = error.get_message(error.code)
928
+
929
+ bfile.seek(0, 0)
930
+ vid = varid.get(var_name)
931
+ # Print error if the variable id is not found.
932
+ if not vid:
933
+ print(
934
+ bcolors.FAIL
935
+ + "ValueError: Invalid variable name. List of permissible variable names: 'velocity', 'correlation', 'echo', 'percent good', 'status'"
936
+ + bcolors.ENDC
937
+ )
938
+ error = ErrorCode.VALUE_ERROR
939
+ return (var_array, error.code)
940
+
941
+ # Checks if variable id is found in address offset
942
+ fbyteskip = None
943
+ for count, item in enumerate(idarray[0][:]):
944
+ if item in vid:
945
+ fbyteskip = offset[0][count]
946
+ break
947
+ if fbyteskip is None:
948
+ print(
949
+ bcolors.FAIL
950
+ + "ERROR: Variable ID not found in address offset."
951
+ + bcolors.ENDC
952
+ )
953
+ error = ErrorCode.ID_NOT_FOUND
954
+ return (var_array, error.code)
955
+
956
+ # READ DATA
957
+ for i in range(ensemble):
958
+ bfile.seek(fbyteskip, 1)
959
+ bdata = bfile.read(2)
960
+ for cno in range(cell):
961
+ for bno in range(beam):
962
+ bdata = bfile.read(bitint)
963
+ varunpack = unpack(bitstr, bdata)
964
+ var_array[bno][cno][i] = varunpack[0]
965
+ bfile.seek(byteskip[i], 0)
966
+ bfile.close()
967
+
968
+ data = var_array
969
+ return (data, ensemble, cell, beam, error_code)