pys7k 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pys7k-0.1.0/LICENSE +28 -0
- pys7k-0.1.0/MANIFEST.in +2 -0
- pys7k-0.1.0/PKG-INFO +358 -0
- pys7k-0.1.0/PYS7K/__init__.py +22 -0
- pys7k-0.1.0/PYS7K/__main__.py +7 -0
- pys7k-0.1.0/PYS7K/bitfields.py +193 -0
- pys7k-0.1.0/PYS7K/derived.py +500 -0
- pys7k-0.1.0/PYS7K/fields.py +387 -0
- pys7k-0.1.0/PYS7K/frame.py +207 -0
- pys7k-0.1.0/PYS7K/kluster.py +641 -0
- pys7k-0.1.0/PYS7K/optional_data.py +143 -0
- pys7k-0.1.0/PYS7K/records/__init__.py +8 -0
- pys7k-0.1.0/PYS7K/records/base.py +261 -0
- pys7k-0.1.0/PYS7K/records/legacy.py +80 -0
- pys7k-0.1.0/PYS7K/records/sbes.py +192 -0
- pys7k-0.1.0/PYS7K/records/sensors.py +357 -0
- pys7k-0.1.0/PYS7K/records/sonar.py +688 -0
- pys7k-0.1.0/PYS7K/records/status.py +438 -0
- pys7k-0.1.0/PYS7K/records/system.py +117 -0
- pys7k-0.1.0/PYS7K/records/watercolumn.py +828 -0
- pys7k-0.1.0/PYS7K/registry.py +318 -0
- pys7k-0.1.0/PYS7K/s7k.py +816 -0
- pys7k-0.1.0/PYS7K/verification.py +124 -0
- pys7k-0.1.0/README.md +315 -0
- pys7k-0.1.0/pyproject.toml +3 -0
- pys7k-0.1.0/pys7k.egg-info/PKG-INFO +358 -0
- pys7k-0.1.0/pys7k.egg-info/SOURCES.txt +40 -0
- pys7k-0.1.0/pys7k.egg-info/dependency_links.txt +1 -0
- pys7k-0.1.0/pys7k.egg-info/entry_points.txt +2 -0
- pys7k-0.1.0/pys7k.egg-info/requires.txt +2 -0
- pys7k-0.1.0/pys7k.egg-info/top_level.txt +1 -0
- pys7k-0.1.0/requirements.txt +2 -0
- pys7k-0.1.0/setup.cfg +4 -0
- pys7k-0.1.0/setup.py +54 -0
- pys7k-0.1.0/tests/helpers.py +40 -0
- pys7k-0.1.0/tests/test_derived_kluster.py +240 -0
- pys7k-0.1.0/tests/test_fields_frame.py +125 -0
- pys7k-0.1.0/tests/test_real_data.py +135 -0
- pys7k-0.1.0/tests/test_real_data_writers.py +277 -0
- pys7k-0.1.0/tests/test_records.py +335 -0
- pys7k-0.1.0/tests/test_records_phase23.py +350 -0
- pys7k-0.1.0/tests/test_s7k.py +394 -0
pys7k-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Val Schmidt, University of New Hampshire
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
pys7k-0.1.0/MANIFEST.in
ADDED
pys7k-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pys7k
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pure-Python library and command line utility for indexing, reading, and writing Teledyne RESON s7k sonar data files.
|
|
5
|
+
Home-page: https://github.com/valschmidt/pys7k
|
|
6
|
+
Author: Val Schmidt
|
|
7
|
+
Author-email: Val.Schmidt@unh.edu
|
|
8
|
+
License: BSD-3-Clause
|
|
9
|
+
Project-URL: Source, https://github.com/valschmidt/pys7k
|
|
10
|
+
Project-URL: Issue Tracker, https://github.com/valschmidt/pys7k/issues
|
|
11
|
+
Keywords: hydrography multibeam sonar teledyne reson s7k 7k
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: pandas
|
|
29
|
+
Requires-Dist: numpy
|
|
30
|
+
Dynamic: author
|
|
31
|
+
Dynamic: author-email
|
|
32
|
+
Dynamic: classifier
|
|
33
|
+
Dynamic: description
|
|
34
|
+
Dynamic: description-content-type
|
|
35
|
+
Dynamic: home-page
|
|
36
|
+
Dynamic: keywords
|
|
37
|
+
Dynamic: license
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
Dynamic: project-url
|
|
40
|
+
Dynamic: requires-dist
|
|
41
|
+
Dynamic: requires-python
|
|
42
|
+
Dynamic: summary
|
|
43
|
+
|
|
44
|
+
<img src="https://raw.githubusercontent.com/valschmidt/pys7k/main/docs/images/ccom-jhc-logo.png" alt="UNH Center for Coastal and Ocean Mapping / Joint Hydrographic Center" width="96" align="right">
|
|
45
|
+
|
|
46
|
+
# pys7k
|
|
47
|
+
|
|
48
|
+
pys7k is a pure-Python library and command line utility for reading and
|
|
49
|
+
writing Teledyne RESON s7k sonar files. It follows the 7k Data Format
|
|
50
|
+
Definition (DFD) version 3.10. It needs only numpy and pandas.
|
|
51
|
+
|
|
52
|
+
pys7k has two goals.
|
|
53
|
+
|
|
54
|
+
* **Forensic accuracy.** Records are reported exactly as they are stored:
|
|
55
|
+
DFD field names, native units, raw bit fields, reserved fields and all.
|
|
56
|
+
Every record can be written back byte for byte, including record types
|
|
57
|
+
pys7k does not understand.
|
|
58
|
+
* **Ease of use.** Converted values (degrees, travel times, datetimes,
|
|
59
|
+
named flags, water column images) come from a separate set of batch
|
|
60
|
+
functions in `PYS7K.derived`. They never change the raw records.
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
pip install pys7k
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Command line
|
|
69
|
+
|
|
70
|
+
`s7k.py -f FILE -V` indexes a file and prints a table of its record types.
|
|
71
|
+
"Decoded" says whether a type is decoded field by field, and "Real Data"
|
|
72
|
+
whether its decoder has been checked against real files. Add `-c` to
|
|
73
|
+
verify every record's checksum.
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
$ s7k.py -f uam_7125_7027_7028_nav.s7k -V -c
|
|
77
|
+
File: uam_7125_7027_7028_nav.s7k
|
|
78
|
+
File size: 356131 bytes
|
|
79
|
+
Total records: 65
|
|
80
|
+
Protocol version(s): 5
|
|
81
|
+
Devices: 0/0 (Unspecified, 1 records), 7000/0 (7k sonar source, 4 records), 7003/0 (Teledyne PDS, 25 records), 7125/0 (SeaBat 7125, 35 records)
|
|
82
|
+
Time span: 2012-11-28T19:09:22.014999+00:00 to 2012-11-28T19:35:56.467998+00:00 (1594.5 s)
|
|
83
|
+
|
|
84
|
+
Name Count Total Bytes Min Bytes Max Bytes % of File Decoded Real Data Checksum OK Checksum Bad No Checksum
|
|
85
|
+
RecordType
|
|
86
|
+
7028 Snippet Data 5 113078 22598 22642 31.75 yes yes 5 0 0
|
|
87
|
+
7017 Detection Data Setup 5 87858 17558 17592 24.67 yes yes 5 0 0
|
|
88
|
+
7027 Raw Detection Data 5 57089 11409 11431 16.03 yes yes 5 0 0
|
|
89
|
+
...
|
|
90
|
+
7777 Filler (file repair) 1 390 390 390 0.11 raw 0 0 1
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`s7k.py -f FILE -p [RECORDTYPE]` prints records as text for debugging. The
|
|
94
|
+
record type may be a number or a name. `-n` limits the number of records
|
|
95
|
+
and `-r` limits the rows printed for each table. Lines starting with `#`
|
|
96
|
+
are annotations, not stored values.
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
$ s7k.py -f uam_7125_7027_7028_nav.s7k -p 1003 -n 1
|
|
100
|
+
=== 1003 Position record=28 offset=143796 size=104 ===
|
|
101
|
+
-- Frame --
|
|
102
|
+
ProtocolVersion : 5
|
|
103
|
+
Offset : 60
|
|
104
|
+
...
|
|
105
|
+
DeviceIdentifier : 7003
|
|
106
|
+
Flags : 1
|
|
107
|
+
...
|
|
108
|
+
# time: 2012-11-28T19:34:20.691999+00:00
|
|
109
|
+
# device: Teledyne PDS
|
|
110
|
+
-- RTH --
|
|
111
|
+
DatumIdentifier : 0
|
|
112
|
+
Latency : 0.0
|
|
113
|
+
LatitudeOrNorthing : 0.7518556113456243
|
|
114
|
+
LongitudeOrEasting : -1.234361627814439
|
|
115
|
+
HeightRelativeToDatumOrHeight : 0.0
|
|
116
|
+
PositionTypeFlag : 0
|
|
117
|
+
UTMZone : 0
|
|
118
|
+
QualityFlag : 0
|
|
119
|
+
PositioningMethod : 0
|
|
120
|
+
NumberOfSatellites : None
|
|
121
|
+
-- Checksum: 4663 # verifies
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`NumberOfSatellites` is `None` because this 7125 file writes the older
|
|
125
|
+
36-byte 1003 record, which ends before that field.
|
|
126
|
+
|
|
127
|
+
Other options:
|
|
128
|
+
|
|
129
|
+
* `-i` prints the full record index as CSV.
|
|
130
|
+
* `--catalog` compares the file's 7300 catalog with the index.
|
|
131
|
+
* Record types with no decoder are printed as ASCII text, with each
|
|
132
|
+
non-printable byte shown as `.`.
|
|
133
|
+
|
|
134
|
+
## Library
|
|
135
|
+
|
|
136
|
+
### Opening and indexing
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from PYS7K import s7k
|
|
140
|
+
|
|
141
|
+
S = s7k("uam_7125_7027_7028_nav.s7k")
|
|
142
|
+
S.index_file()
|
|
143
|
+
print(S.Index.head(3))
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
Offset Size RecordType DeviceId SystemEnumerator Time ProtocolVersion RecordVersion Flags OptionalDataOffset OptionalDataIdentifier
|
|
148
|
+
0 0 402 7200 7000 0 1.354131e+09 5 1 0 386 7300
|
|
149
|
+
1 402 100 7022 7000 0 1.354130e+09 5 1 1 0 0
|
|
150
|
+
2 502 3944 7001 7000 0 1.354130e+09 5 1 1 0 0
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The file is memory-mapped. Indexing walks the record frames with one
|
|
154
|
+
precompiled `struct` unpack per record, then gathers every frame field at
|
|
155
|
+
once with numpy. A 1 GB file indexes in about 0.05 s.
|
|
156
|
+
|
|
157
|
+
`index_file(full=True)` adds `PingNumber` and `MultiPingSequence` columns
|
|
158
|
+
(-1 where a record has none). `index_file(verify_checksums=True)` adds a
|
|
159
|
+
`ChecksumValid` column. It is missing (NA) for records whose frame says no
|
|
160
|
+
checksum is present. Both together take about 0.25 s per GB.
|
|
161
|
+
|
|
162
|
+
A damaged file does not stop indexing. pys7k skips to the next sync
|
|
163
|
+
pattern and lists each skipped byte range in `S.Gaps`. Use
|
|
164
|
+
`index_file(strict=True)` to raise an error instead.
|
|
165
|
+
|
|
166
|
+
### Reading records
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
for rec in S.iter_records(7027):
|
|
170
|
+
...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`iter_records` accepts record types (numbers or names), devices, and a
|
|
174
|
+
time range. Each record is a dictionary:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
rec = next(S.iter_records(7027))
|
|
178
|
+
{k: type(v).__name__ for k, v in rec.items()}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
{'RecordType': 'int', 'Frame': 'dict', 'RTH': 'dict', 'RD': 'dict',
|
|
183
|
+
'OptionalData': 'NoneType', 'Unparsed': 'dict', 'Checksum': 'int',
|
|
184
|
+
'ChecksumValid': 'bool'}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
* `rec["Frame"]` holds every field of the 64-byte Data Record Frame.
|
|
188
|
+
* `rec["RTH"]` holds the record type header.
|
|
189
|
+
* `rec["RD"]` holds the record data. Repeated structures are dictionaries
|
|
190
|
+
of numpy arrays, for example `rec["RD"]["Detections"]["DetectionPoint"]`.
|
|
191
|
+
* `rec["OptionalData"]` holds the optional data (DFD Appendix A), or `None`.
|
|
192
|
+
* `rec["Unparsed"]` holds any bytes past the known layout. For record types
|
|
193
|
+
with no decoder, `rec["Unparsed"]["Body"]` holds the whole body.
|
|
194
|
+
* `rec["Checksum"]` is the stored checksum, and `rec["ChecksumValid"]`
|
|
195
|
+
whether it verifies (None when Flags bit 0 says there is no checksum).
|
|
196
|
+
|
|
197
|
+
Field sets follow the sizes the record declares. This 2012 file writes
|
|
198
|
+
22-byte 7027 detections, so `Intensity`, `MinLimit` and `MaxLimit` are
|
|
199
|
+
absent:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
rec["RTH"]["DataFieldSize"], sorted(rec["RD"]["Detections"])
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
(22, ['BeamDescriptor', 'DetectionPoint', 'Flags', 'Quality', 'RxAngle', 'Uncertainty'])
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Every record type documented in DFD 3.10 has a decoder. 7038 (raw IQ
|
|
210
|
+
element data) also has one, although DFD 3.10 lists it as reserved. Its
|
|
211
|
+
layout comes from the CCOM 7125 MATLAB reader and was checked against 7125
|
|
212
|
+
files.
|
|
213
|
+
|
|
214
|
+
### Converted values
|
|
215
|
+
|
|
216
|
+
`PYS7K.derived` computes values for many records at once, which is much
|
|
217
|
+
faster than converting each record as it is read.
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from PYS7K import derived as D
|
|
221
|
+
|
|
222
|
+
d = D.read_detections(S, degrees=True)
|
|
223
|
+
d["TwoWayTravelTime"].shape, d["TwoWayTravelTime"][0, :3], d["RxAngle"][0, :3]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
((5, 512), array([0.02279599, 0.02266362, 0.02254943]), array([-60.30000407, -60.20000327, -60.10000247]))
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Available functions:
|
|
231
|
+
|
|
232
|
+
| Function | Records | Returns |
|
|
233
|
+
|---|---|---|
|
|
234
|
+
| `read_attitude` | 1012, 1013, 1016 | Attitude and heading arrays by source |
|
|
235
|
+
| `read_navigation` | 1003, 1015 | Positions (degrees only where geographic) |
|
|
236
|
+
| `read_detections` | 7027 | Ping-by-detection arrays, travel times, flag fields |
|
|
237
|
+
| `read_bathymetry_7006` | 7006 | Ping-by-beam arrays |
|
|
238
|
+
| `read_beam_geometry` | 7004 | Ping-by-beam angles |
|
|
239
|
+
| `read_runtime` | 7503 or 7000 | A DataFrame of settings |
|
|
240
|
+
| `read_svp`, `read_sound_velocity` | 1009, 7610 | Profiles and surface sound velocity |
|
|
241
|
+
| `read_installation` | 7030 | Installation parameters |
|
|
242
|
+
| `read_snippets` | 7028 | Each beam's snippet |
|
|
243
|
+
| `read_sidescan` | 7007, 7057 | Port and starboard series |
|
|
244
|
+
| `read_watercolumn` | 7008, 7018, 7042, 7048 | A samples-by-beams image |
|
|
245
|
+
| `gather_header` | any header-only record | Every header field, read straight from the file |
|
|
246
|
+
| `record_times` | any | Frame times as seconds and datetimes |
|
|
247
|
+
|
|
248
|
+
`PYS7K.bitfields.decode_bits` splits bit fields into named parts, for
|
|
249
|
+
example `decode_bits(rec["RTH"]["ControlFlags"], CONTROL_FLAGS_7000)`.
|
|
250
|
+
|
|
251
|
+
### Writing
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from PYS7K import new, s7k
|
|
255
|
+
|
|
256
|
+
out = s7k("copy.s7k")
|
|
257
|
+
out.OpenFiletoWrite()
|
|
258
|
+
for rec in S.iter_records():
|
|
259
|
+
out.write_record(rec)
|
|
260
|
+
out.closeFile(catalog=True)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`new(record_type)` returns a template record with every field set to zero.
|
|
264
|
+
Fill it in and pass it to `write_record`. The encoder recomputes the
|
|
265
|
+
frame's Size and offsets. The checksum is computed when Flags bit 0 is set
|
|
266
|
+
and written as stored otherwise. A record read with a checksum that did not
|
|
267
|
+
verify keeps its stored checksum, so unmodified records always come back
|
|
268
|
+
byte for byte. Use `write_record(rec, "recompute")` to repair a checksum.
|
|
269
|
+
`closeFile(catalog=True)` appends a 7300 file catalog and points the
|
|
270
|
+
7200 file header at it.
|
|
271
|
+
|
|
272
|
+
## Kluster
|
|
273
|
+
|
|
274
|
+
`PYS7K.kluster.X7kRead` can stand in for NOAA's
|
|
275
|
+
`HSTB.drivers.prr3.X7kRead`. It returns the same dictionary from
|
|
276
|
+
`sequential_read_records()` and keeps prr3's record preferences and
|
|
277
|
+
conversions. On the sample data its ping, attitude and navigation arrays
|
|
278
|
+
match prr3's to float precision. Its module docstring lists nine places
|
|
279
|
+
where it deliberately differs from prr3, for example converting the 7027
|
|
280
|
+
TxAngle to degrees like every other angle.
|
|
281
|
+
|
|
282
|
+
## Notes on the DFD
|
|
283
|
+
|
|
284
|
+
pys7k is experimental. Its interfaces may change between releases. Please
|
|
285
|
+
check results against other tools before relying on them, and report any
|
|
286
|
+
discrepancies.
|
|
287
|
+
|
|
288
|
+
The decoders have been checked against real files from SeaBat 7101, 7111,
|
|
289
|
+
7125, T20-P and T50-R sonars, NORBIT WBMS and STX sonars, and files written
|
|
290
|
+
by Reson RDR 1.2 and 2.0, 7kCenter 3.7 to 6.3, Teledyne PDS 2.3 to 3.9,
|
|
291
|
+
HYPACK and Fledermaus, dated 2005 to 2024. `PYS7K/verification.py` lists
|
|
292
|
+
the evidence for each record type, and the "Real Data" column of
|
|
293
|
+
`s7k.py -V` shows whether a file's record types have been checked this way.
|
|
294
|
+
Record types without real samples (most phase 3 records, the SBES records,
|
|
295
|
+
7047 and others) have been tested with synthetic records
|
|
296
|
+
only.
|
|
297
|
+
|
|
298
|
+
These points were checked against real data.
|
|
299
|
+
|
|
300
|
+
* The checksum flag is Flags bit 0. The DFD's checksum row says bit 1,
|
|
301
|
+
a leftover from the protocol 2 and 3 frames (DFD 0.42, 0.50), which did
|
|
302
|
+
use bit 1. DFD 0.53 moved the flag to bit 0.
|
|
303
|
+
* The 7200, 7300, 7777 and 7038 records carry no checksum. Neither do 7008
|
|
304
|
+
(Reson recorders and HYPACK), 7011, 7018 or 7058 records, nor any NORBIT
|
|
305
|
+
record.
|
|
306
|
+
* The 7503 record has no ReceiveBeamWidth field. Its full 3.10 layout is
|
|
307
|
+
260 bytes.
|
|
308
|
+
* 7k logger files leave the 7200 pointer to the 7300 catalog at zero, and
|
|
309
|
+
RDR 1.2 writes only 6 bytes of 7200 optional data.
|
|
310
|
+
* 7027 detections are 22, 26 or 34 bytes, depending on the writer: the
|
|
311
|
+
DFD 2.20-2.22, 2.31-2.41 and 3.x forms.
|
|
312
|
+
|
|
313
|
+
Writers do not always follow the DFD. pys7k reports what is stored, writes
|
|
314
|
+
it back byte for byte, and points out these cases:
|
|
315
|
+
|
|
316
|
+
* Wrong stored checksums. Records keep their stored checksum on writing
|
|
317
|
+
(`rec["ChecksumValid"]` is False), and `s7k.py -p` recognizes HYPACK's
|
|
318
|
+
habit of storing Size - 4, and PDS 7610 checksums computed before the
|
|
319
|
+
frame's Reserved4 field was filled in.
|
|
320
|
+
* Fledermaus navigation files have no checksum field at all. This is
|
|
321
|
+
detected per file (`S.ChecksumTrailer`).
|
|
322
|
+
* Teledyne PDS 7028 records place their optional data before the offset
|
|
323
|
+
the frame gives. `optional_data.recover_misplaced_optional_data` reads it.
|
|
324
|
+
* A 2011 file stores attitude in degrees, not radians.
|
|
325
|
+
`derived.read_attitude` warns about values that cannot be radians.
|
|
326
|
+
* Some records follow an older DFD rather than 3.10. pys7k picks the
|
|
327
|
+
layout from the record itself: the 44-byte 7041 header of DFD
|
|
328
|
+
2.20-2.31 (7kCenter 5.2), and shorter 7002, 7610 and 1003 records that
|
|
329
|
+
end where the older DFDs ended them.
|
|
330
|
+
* Protocol version 2 files (2003) predate DFD 3.10. They are decoded with
|
|
331
|
+
the 72-byte frame and the 1003/1004 layouts of DFD 0.42.
|
|
332
|
+
* 2010 files store 7048 beam by beam with zero padding, and pad 7012 with
|
|
333
|
+
stale samples. No DFD describes either; both are detected and kept.
|
|
334
|
+
|
|
335
|
+
The older DFDs used for this are archived with MB-System
|
|
336
|
+
(<https://www.mbari.org/technology/mb-system/documentation/data-format-specifications/>).
|
|
337
|
+
`PYS7K/verification.py` lists what they explained and what they did not
|
|
338
|
+
(7026, 7777, 6-byte 7200 optional data, the PDS 7028 placement).
|
|
339
|
+
|
|
340
|
+
## Tests
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
python scripts/make_test_subsets.py --defaults # optional: cut real-data test files into data/
|
|
344
|
+
python scripts/make_test_subsets.py --writers --root DIR # optional: writer samples into data/writers/
|
|
345
|
+
pytest
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Most tests build synthetic records with the writer and always run. Tests
|
|
349
|
+
on real data use the small files in `data/` and are skipped when that
|
|
350
|
+
folder is empty. `make_test_subsets.py --writers` also copies samples of
|
|
351
|
+
files from many writers and systems, named in a local list
|
|
352
|
+
(`data/writer_samples.json`, not in the repository), into `data/writers/`,
|
|
353
|
+
record for record, so their quirks are kept;
|
|
354
|
+
`tests/test_real_data_writers.py` checks each one.
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
BSD 3-Clause. See LICENSE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pys7k: a pure-Python reader and writer for Teledyne RESON s7k sonar files
|
|
3
|
+
(7k Data Format Definition 3.10).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .frame import (FLAG_CHECKSUM_VALID, FLAG_RECORDED_DATA, S7KChecksumError,
|
|
7
|
+
S7KError, S7KFormatError, checksum, epoch_to_time7k,
|
|
8
|
+
time7k_to_epoch)
|
|
9
|
+
from .registry import (DEVICE_IDENTIFIERS, RECORD_CODECS,
|
|
10
|
+
RECORD_TYPE_DESCRIPTIONS, decode_record, encode_record,
|
|
11
|
+
new, record_name)
|
|
12
|
+
from .s7k import main, resolve_record_type, s7k
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"s7k", "main", "new", "decode_record", "encode_record", "record_name",
|
|
18
|
+
"resolve_record_type", "checksum", "time7k_to_epoch", "epoch_to_time7k",
|
|
19
|
+
"RECORD_CODECS", "RECORD_TYPE_DESCRIPTIONS", "DEVICE_IDENTIFIERS",
|
|
20
|
+
"FLAG_CHECKSUM_VALID", "FLAG_RECORDED_DATA",
|
|
21
|
+
"S7KError", "S7KFormatError", "S7KChecksumError",
|
|
22
|
+
]
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Named bit-field specifications from DFD 3.10, and a vectorized decoder.
|
|
3
|
+
|
|
4
|
+
Records keep bit fields as the integers stored in the file. These specs let
|
|
5
|
+
you expand them when you want named values::
|
|
6
|
+
|
|
7
|
+
from PYS7K.bitfields import decode_bits, CONTROL_FLAGS_7000
|
|
8
|
+
decode_bits(rec["RTH"]["ControlFlags"], CONTROL_FLAGS_7000)
|
|
9
|
+
{'AutoRangeMethod': 0, 'AutoBottomDetectionFilterMethod': 0, ...}
|
|
10
|
+
|
|
11
|
+
Each spec is a list of ``(name, first_bit, width)``. ``decode_bits`` works
|
|
12
|
+
on a scalar or on a numpy array (for example a column of per-detection
|
|
13
|
+
flags), returning a dictionary of the same shape.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"decode_bits",
|
|
20
|
+
"CONTROL_FLAGS_7000", "TRANSMIT_FLAGS_7000", "RECEIVE_FLAGS_7000",
|
|
21
|
+
"FLAGS_7006", "QUALITY_7006",
|
|
22
|
+
"FLAGS_7017", "DETECTION_FLAGS_7017",
|
|
23
|
+
"FLAGS_7027", "DETECTION_FLAGS_7027", "QUALITY_7027",
|
|
24
|
+
"FLAGS_7028", "DATA_SAMPLE_TYPE_7008", "FLAGS_7042",
|
|
25
|
+
"FRAME_FLAGS", "BIT_FIELDS",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def decode_bits(value, spec):
|
|
30
|
+
"""
|
|
31
|
+
Split an integer bit field (or an array of them) into named parts.
|
|
32
|
+
|
|
33
|
+
:param value: an int or an integer numpy array.
|
|
34
|
+
:param spec: a list of ``(name, first_bit, width)``.
|
|
35
|
+
:return: a dictionary mapping each name to an int, or to an array the
|
|
36
|
+
shape of ``value``.
|
|
37
|
+
"""
|
|
38
|
+
scalar = np.isscalar(value)
|
|
39
|
+
v = np.asarray(value).astype(np.uint64)
|
|
40
|
+
out = {}
|
|
41
|
+
for name, bit, width in spec:
|
|
42
|
+
part = (v >> np.uint64(bit)) & np.uint64((1 << width) - 1)
|
|
43
|
+
out[name] = int(part) if scalar else part.astype(np.uint32 if width > 8 else np.uint8)
|
|
44
|
+
return out
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Table 5: DRF Flags. Bit 0 is the checksum flag (verified on real data;
|
|
48
|
+
# the DFD's checksum row says "bit 1", which is a typo).
|
|
49
|
+
FRAME_FLAGS = [
|
|
50
|
+
("ChecksumValid", 0, 1),
|
|
51
|
+
("RecordedData", 15, 1),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# Table 39: 7000 Control flags (also used by 7503).
|
|
55
|
+
CONTROL_FLAGS_7000 = [
|
|
56
|
+
("AutoRangeMethod", 0, 4),
|
|
57
|
+
("AutoBottomDetectionFilterMethod", 4, 4),
|
|
58
|
+
("BottomDetectionRangeFilterEnabled", 8, 1),
|
|
59
|
+
("BottomDetectionDepthFilterEnabled", 9, 1),
|
|
60
|
+
("ReceiverGainAutoGain", 10, 1),
|
|
61
|
+
("ReceiverGainFixedGain", 11, 1),
|
|
62
|
+
("ReceiverGainReserved", 12, 1),
|
|
63
|
+
("Reserved13", 13, 1),
|
|
64
|
+
("TriggerOutHighForRxDuration", 14, 1),
|
|
65
|
+
("SystemActive", 15, 1),
|
|
66
|
+
("ReservedBottomDetection", 16, 4),
|
|
67
|
+
("PipeGatingFilter", 20, 1),
|
|
68
|
+
("AdaptiveGateDepthFilterFixed", 21, 1),
|
|
69
|
+
("AdaptiveGate", 22, 1),
|
|
70
|
+
("AdaptiveGateDepthFilter", 23, 1),
|
|
71
|
+
("TriggerOut", 24, 1),
|
|
72
|
+
("TriggerInEdgeNegative", 25, 1),
|
|
73
|
+
("PPSEdgeNegative", 26, 1),
|
|
74
|
+
("TimestampState", 27, 2), # 0 n/a, 1 error, 2 warning, 3 ok
|
|
75
|
+
("DepthFilterFollowsSeafloor", 29, 1),
|
|
76
|
+
("ReducedCoverageForConstantSpacing", 30, 1),
|
|
77
|
+
("Simulator", 31, 1),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# Table 39: Transmit flags.
|
|
81
|
+
TRANSMIT_FLAGS_7000 = [
|
|
82
|
+
("PitchStabilizationMethod", 0, 4),
|
|
83
|
+
("YawStabilizationMethod", 4, 4),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# Table 39: Receive flags.
|
|
87
|
+
RECEIVE_FLAGS_7000 = [
|
|
88
|
+
("RollCompensation", 0, 1),
|
|
89
|
+
("HeaveCompensation", 2, 1),
|
|
90
|
+
("DynamicFocusingMethod", 4, 4),
|
|
91
|
+
("DopplerCompensationMethod", 8, 4),
|
|
92
|
+
("MatchFilteringMethod", 12, 4),
|
|
93
|
+
("TVGMethod", 16, 4),
|
|
94
|
+
("MultiPingMode", 20, 4),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
# Table 46: 7006 Flags, and Table 47: 7006 Quality.
|
|
98
|
+
FLAGS_7006 = [
|
|
99
|
+
("LayerCompensation", 0, 1),
|
|
100
|
+
("XYZCompensation", 1, 1),
|
|
101
|
+
]
|
|
102
|
+
QUALITY_7006 = [
|
|
103
|
+
("BrightnessPass", 0, 1),
|
|
104
|
+
("ColinearityPass", 1, 1),
|
|
105
|
+
("IntensityDetectionUsed", 2, 1),
|
|
106
|
+
("PhaseDetectionUsed", 3, 1),
|
|
107
|
+
("InternalUse", 4, 1),
|
|
108
|
+
("PDSNadirFilterFail", 5, 1),
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
# Table 60: 7017 Flags, and Table 61: per-detection Flags.
|
|
112
|
+
FLAGS_7017 = [
|
|
113
|
+
("UserDepthFilterEnabled", 0, 1),
|
|
114
|
+
("UserRangeFilterEnabled", 1, 1),
|
|
115
|
+
("AutomaticFilterEnabled", 2, 1),
|
|
116
|
+
("NadirSearchLimitsEnabled", 3, 1),
|
|
117
|
+
("AutomaticWindowLimitsEnabled", 4, 1),
|
|
118
|
+
("QualityFilterEnabled", 5, 1),
|
|
119
|
+
("MultiDetectionEnabled", 6, 1),
|
|
120
|
+
]
|
|
121
|
+
DETECTION_FLAGS_7017 = [
|
|
122
|
+
("AutomaticLimitsValid", 0, 1),
|
|
123
|
+
("UserDefinedLimitsValid", 1, 1),
|
|
124
|
+
("QualityType", 2, 7),
|
|
125
|
+
("QualityPassesUserCriteria", 9, 1),
|
|
126
|
+
("MagnitudeDetect", 10, 1),
|
|
127
|
+
("PhaseDetect", 11, 1),
|
|
128
|
+
("DetectionPriority", 16, 4),
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
# Table 71: 7027 Flags, Table 72: per-detection Flags, Table 73: Quality
|
|
132
|
+
# (when the quality type is 1).
|
|
133
|
+
FLAGS_7027 = [
|
|
134
|
+
("UncertaintyMethod", 0, 4), # 0 none, 1 Rob Hare, 2 Ifremer
|
|
135
|
+
("MultiDetectionEnabled", 4, 1),
|
|
136
|
+
("HasSnippetsDetectionPointFlag", 6, 1),
|
|
137
|
+
]
|
|
138
|
+
DETECTION_FLAGS_7027 = [
|
|
139
|
+
("MagnitudeDetect", 0, 1),
|
|
140
|
+
("PhaseDetect", 1, 1),
|
|
141
|
+
("QualityType", 2, 7),
|
|
142
|
+
("DetectionPriority", 9, 4),
|
|
143
|
+
("NotUsedInSnippet", 14, 1),
|
|
144
|
+
]
|
|
145
|
+
QUALITY_7027 = [
|
|
146
|
+
("BrightnessPass", 0, 1),
|
|
147
|
+
("ColinearityPass", 1, 1),
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
# Table 74: 7028 Flags.
|
|
151
|
+
FLAGS_7028 = [("Samples32Bit", 0, 1)]
|
|
152
|
+
|
|
153
|
+
# Table 50: 7008 Data sample type.
|
|
154
|
+
DATA_SAMPLE_TYPE_7008 = [
|
|
155
|
+
("Magnitude", 0, 4), # 0 none, 2 16-bit, 3 32-bit
|
|
156
|
+
("Phase", 4, 4), # 0 none, 2 16-bit, 3 32-bit
|
|
157
|
+
("IQ", 8, 4), # 0 none, 1 16-bit, 2 32-bit
|
|
158
|
+
("ElementData", 12, 3), # 0 beamformed, 1 element data
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
# Table 82: 7042 Flags.
|
|
162
|
+
FLAGS_7042 = [
|
|
163
|
+
("UseMaximumBottomDetection", 0, 1),
|
|
164
|
+
("MagnitudeOnly", 1, 1),
|
|
165
|
+
("Magnitude8BitdB", 2, 1),
|
|
166
|
+
("Reserved3", 3, 1),
|
|
167
|
+
("DownsamplingDivisor", 4, 4),
|
|
168
|
+
("DownsamplingType", 8, 4), # 0 none, 1 middle, 2 peak, 3 average
|
|
169
|
+
("Data32Bit", 12, 1),
|
|
170
|
+
("CompressionFactorAvailable", 13, 1),
|
|
171
|
+
("SegmentNumbersAvailable", 14, 1),
|
|
172
|
+
("FirstSampleContainsRxDelay", 15, 1),
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
# Bit fields indexed by (record type, section, field name), for tools that
|
|
176
|
+
# want to expand every known bit field in a record.
|
|
177
|
+
BIT_FIELDS = {
|
|
178
|
+
(7000, "RTH", "ControlFlags"): CONTROL_FLAGS_7000,
|
|
179
|
+
(7000, "RTH", "TransmitFlags"): TRANSMIT_FLAGS_7000,
|
|
180
|
+
(7000, "RTH", "ReceiveFlags"): RECEIVE_FLAGS_7000,
|
|
181
|
+
(7503, "RTH", "ControlFlags"): CONTROL_FLAGS_7000,
|
|
182
|
+
(7503, "RTH", "TransmitFlags"): TRANSMIT_FLAGS_7000,
|
|
183
|
+
(7503, "RTH", "ReceiveFlags"): RECEIVE_FLAGS_7000,
|
|
184
|
+
(7006, "RTH", "Flags"): FLAGS_7006,
|
|
185
|
+
(7006, "Beams", "Quality"): QUALITY_7006,
|
|
186
|
+
(7008, "RTH", "DataSampleType"): DATA_SAMPLE_TYPE_7008,
|
|
187
|
+
(7017, "RTH", "Flags"): FLAGS_7017,
|
|
188
|
+
(7017, "Detections", "Flags"): DETECTION_FLAGS_7017,
|
|
189
|
+
(7027, "RTH", "Flags"): FLAGS_7027,
|
|
190
|
+
(7027, "Detections", "Flags"): DETECTION_FLAGS_7027,
|
|
191
|
+
(7028, "RTH", "Flags"): FLAGS_7028,
|
|
192
|
+
(7042, "RTH", "Flags"): FLAGS_7042,
|
|
193
|
+
}
|