1 |
#!/usr/bin/python |
---|
2 |
"""Functions to handle Acoustic Doppler Current Profiler data. |
---|
3 |
|
---|
4 |
As a script (to split a raw PD0 file into waves and currents): |
---|
5 |
|
---|
6 |
python pd0.py [path] rawFile wavesFile currentsFile |
---|
7 |
python pd0.py [-p path] -r rawFile -w wavesFile -c currentsFile |
---|
8 |
python pd0.py [--path=path] \\ |
---|
9 |
--raw=rawFile \\ |
---|
10 |
--waves=wavesFile \\ |
---|
11 |
--currents=currentsFile |
---|
12 |
|
---|
13 |
where: |
---|
14 |
path is a path to prepend to the following |
---|
15 |
rawFile is path of raw PD0 format input file |
---|
16 |
wavesFile is path of waves PD0 format output file |
---|
17 |
currentsFile is path of currents PD0 format output file |
---|
18 |
|
---|
19 |
or (to run the test suite): |
---|
20 |
|
---|
21 |
python pd0.py -t |
---|
22 |
python pd0.py --test |
---|
23 |
|
---|
24 |
or (to see this help message): |
---|
25 |
|
---|
26 |
python pd0.py -h |
---|
27 |
python pd0.py --help |
---|
28 |
|
---|
29 |
As a module: |
---|
30 |
|
---|
31 |
import adcp.pd0 |
---|
32 |
adcp.pd0.split(rawFile,wavesFile,currentsFile) |
---|
33 |
|
---|
34 |
where: |
---|
35 |
rawFile is a file object representing the raw PD0 format input |
---|
36 |
wavesFile is a file object representing the waves PD0 format output |
---|
37 |
currentsFile is a file object representing the currents PD0 format output |
---|
38 |
""" |
---|
39 |
|
---|
40 |
import struct |
---|
41 |
|
---|
42 |
# |
---|
43 |
# The rawfile is assumed to be in PD0 format. |
---|
44 |
# |
---|
45 |
# PD0 format assumes the file is a succession of ensembles. |
---|
46 |
# |
---|
47 |
# Each ensemble starts with a two byte header identifying the type of data |
---|
48 |
# contained in the ensemble. |
---|
49 |
# |
---|
50 |
# Following the header is a two byte length field specifying the length of |
---|
51 |
# the header, length field, and data combined |
---|
52 |
# |
---|
53 |
# Following the length field is raw data for the number of bytes indicated by |
---|
54 |
# the length field |
---|
55 |
# |
---|
56 |
# Following the raw data is a checksum field which is the two least |
---|
57 |
# significant bytes of the sum of the byte values of the header, length field, |
---|
58 |
# and raw data. |
---|
59 |
# |
---|
60 |
def split(rawFile,wavesFile,currentsFile): |
---|
61 |
"""Split PD0 format data into seperate waves and currents |
---|
62 |
|
---|
63 |
split()rawFile,wavesFile,currentsFile -> None |
---|
64 |
""" |
---|
65 |
|
---|
66 |
# header IDs |
---|
67 |
wavesId=0x797f |
---|
68 |
currentsId=0x7f7f |
---|
69 |
|
---|
70 |
# convenience function reused for header, length, and checksum |
---|
71 |
def __nextLittleEndianUnsignedShort(file): |
---|
72 |
"""Get next little endian unsigned short from file""" |
---|
73 |
raw = file.read(2) |
---|
74 |
return (raw, struct.unpack('<H', raw)[0]) |
---|
75 |
|
---|
76 |
# factored for readability |
---|
77 |
def __computeChecksum(header, length, ensemble): |
---|
78 |
"""Compute a checksum from header, length, and ensemble""" |
---|
79 |
cs = 0 |
---|
80 |
for byte in header: |
---|
81 |
value = struct.unpack('B', byte)[0] |
---|
82 |
cs += value |
---|
83 |
for byte in length: |
---|
84 |
value = struct.unpack('B', byte)[0] |
---|
85 |
cs += value |
---|
86 |
for byte in ensemble: |
---|
87 |
value = struct.unpack('B', byte)[0] |
---|
88 |
cs += value |
---|
89 |
return cs & 0xffff |
---|
90 |
|
---|
91 |
# find the first instance of a waves or currents header |
---|
92 |
rawData=rawFile.read() |
---|
93 |
firstWaves = rawData.find(struct.pack('<H',wavesId)) |
---|
94 |
firstCurrents= rawData.find(struct.pack('<H',currentsId)) |
---|
95 |
|
---|
96 |
# bail if neither waves nor currents found |
---|
97 |
if (firstWaves < 0) and (firstCurrents < 0): |
---|
98 |
raise IOError, "Neither waves nor currents header found" |
---|
99 |
|
---|
100 |
# get the starting point by throwing out unfound headers |
---|
101 |
# and selecting the minumum |
---|
102 |
firstEnsemble = min(filter(lambda x: x >= 0,(firstWaves,firstCurrents))) |
---|
103 |
|
---|
104 |
#seeks to the first occurence of a waves or currents data |
---|
105 |
rawFile.seek(firstEnsemble) |
---|
106 |
|
---|
107 |
# loop through raw data |
---|
108 |
rawHeader, header = __nextLittleEndianUnsignedShort(rawFile) |
---|
109 |
|
---|
110 |
while (header == wavesId) or (header == currentsId): |
---|
111 |
# get ensemble length |
---|
112 |
rawLength, length = __nextLittleEndianUnsignedShort(rawFile) |
---|
113 |
# read up to the checksum |
---|
114 |
rawEnsemble = rawFile.read(length-4) |
---|
115 |
# get checksum |
---|
116 |
rawChecksum, checksum = __nextLittleEndianUnsignedShort(rawFile) |
---|
117 |
|
---|
118 |
computedChecksum = __computeChecksum(rawHeader, rawLength, rawEnsemble) |
---|
119 |
|
---|
120 |
if checksum != computedChecksum: |
---|
121 |
raise IOError, "Checksum error" |
---|
122 |
|
---|
123 |
# append to output stream |
---|
124 |
if header == wavesId: |
---|
125 |
wavesFile.write(rawHeader) |
---|
126 |
wavesFile.write(rawLength) |
---|
127 |
wavesFile.write(rawEnsemble) |
---|
128 |
wavesFile.write(rawChecksum) |
---|
129 |
elif header == currentsId: |
---|
130 |
currentsFile.write(rawHeader) |
---|
131 |
currentsFile.write(rawLength) |
---|
132 |
currentsFile.write(rawEnsemble) |
---|
133 |
currentsFile.write(rawChecksum) |
---|
134 |
|
---|
135 |
try: |
---|
136 |
rawHeader, header = __nextLittleEndianUnsignedShort(rawFile) |
---|
137 |
except struct.error: |
---|
138 |
break |
---|
139 |
|
---|
140 |
|
---|
141 |
def test(): |
---|
142 |
"""Execute test suite""" |
---|
143 |
try: |
---|
144 |
import adcp.tests.runalltests as runalltests |
---|
145 |
except: |
---|
146 |
# possible if executed as script |
---|
147 |
import sys,os |
---|
148 |
sys.path.append(os.path.join(os.path.dirname(__file__),'tests')) |
---|
149 |
import runalltests |
---|
150 |
runalltests.runalltests(subset='pd0') |
---|
151 |
|
---|
152 |
class __TestException(Exception): |
---|
153 |
"""Flow control for running as script""" |
---|
154 |
pass |
---|
155 |
|
---|
156 |
# wrapper function |
---|
157 |
def __test(): |
---|
158 |
"""Execute test suite from command line""" |
---|
159 |
test() |
---|
160 |
raise __TestException, 'Wrapper function for command line testing only' |
---|
161 |
|
---|
162 |
def __main(): |
---|
163 |
"""Process as script from command line""" |
---|
164 |
import getopt,os,sys |
---|
165 |
|
---|
166 |
# get the command line options and arguments |
---|
167 |
path = '' |
---|
168 |
rawName,wavesName,currentsName = 3*[None] |
---|
169 |
|
---|
170 |
try: |
---|
171 |
opts, args = getopt.gnu_getopt(sys.argv[1:], |
---|
172 |
'htp:r:w:c:', |
---|
173 |
['help', |
---|
174 |
'test', |
---|
175 |
'path=', |
---|
176 |
'raw=', |
---|
177 |
'waves=', |
---|
178 |
'currents=']) |
---|
179 |
for opt,arg in opts: |
---|
180 |
if opt in ['-h','--help']: |
---|
181 |
raise getopt.GetoptError,'' |
---|
182 |
if opt in ['-t','--test']: |
---|
183 |
__test() |
---|
184 |
elif opt in ['-p','--path']: |
---|
185 |
path = arg |
---|
186 |
elif opt in ['-r','--raw']: |
---|
187 |
rawName = arg |
---|
188 |
elif opt in ['-w','--waves']: |
---|
189 |
wavesName = arg |
---|
190 |
elif opt in ['-c','--currents']: |
---|
191 |
currentsName = arg |
---|
192 |
else: |
---|
193 |
raise getopt.GetoptError,'' |
---|
194 |
if (rawName is None) or \ |
---|
195 |
(wavesName is None) or \ |
---|
196 |
(currentsName is None): |
---|
197 |
if len(args) not in [3, 4]: |
---|
198 |
raise getopt.GetoptError,'' |
---|
199 |
else: |
---|
200 |
if (rawName is not None) or \ |
---|
201 |
(wavesName is not None) or \ |
---|
202 |
(currentsName is not None): |
---|
203 |
raise getopt.GetoptError,'' |
---|
204 |
else: |
---|
205 |
if len(args) == 4: |
---|
206 |
path = args[0] |
---|
207 |
del args[0] |
---|
208 |
rawName = args[0] |
---|
209 |
wavesName = args[1] |
---|
210 |
currentsName = args[2] |
---|
211 |
elif len(args) != 0: |
---|
212 |
raise getopt.GetoptError,'' |
---|
213 |
except getopt.GetoptError: |
---|
214 |
print __doc__ |
---|
215 |
return |
---|
216 |
except __TestException: |
---|
217 |
return |
---|
218 |
|
---|
219 |
# split a raw PD0 file |
---|
220 |
rawName = os.path.join(path, rawName) |
---|
221 |
print 'Raw file path:', rawName |
---|
222 |
wavesName = os.path.join(path, wavesName) |
---|
223 |
print 'Waves file path:', wavesName |
---|
224 |
currentsName = os.path.join(path, currentsName) |
---|
225 |
print 'Currents file path:', currentsName |
---|
226 |
rawFile = open(rawName, 'rb') |
---|
227 |
try: |
---|
228 |
wavesFile = open(wavesName, 'wb') |
---|
229 |
try: |
---|
230 |
currentsFile = open(currentsName, 'wb') |
---|
231 |
try: |
---|
232 |
split(rawFile, wavesFile, currentsFile) |
---|
233 |
finally: |
---|
234 |
currentsFile.close() |
---|
235 |
finally: |
---|
236 |
wavesFile.close() |
---|
237 |
finally: |
---|
238 |
rawFile.close() |
---|
239 |
|
---|
240 |
if __name__ == "__main__": |
---|
241 |
__main() |
---|
242 |
|
---|