shipgrav 1.0.0__py2.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.
shipgrav/utils.py ADDED
@@ -0,0 +1,275 @@
1
+ import numpy as np
2
+ from datetime import datetime
3
+ from pandas import Series
4
+ import re
5
+
6
+ # TODO gaussian filter doesn't *need* time array
7
+
8
+
9
+ def gaussian_filter(x, fl):
10
+ """ Apply a gaussian filter to a vector.
11
+
12
+ The filtering is done via a ring buffer; results are not identical to
13
+ scipy.ndimage.gaussian_filter, which is why this function exists.
14
+ Note that the filter is not applied symmetrically and there is a shift by fl.
15
+
16
+ :param x: time series data to be filtered.
17
+ :type x: array_like
18
+ :param fl: length of the filter in # of samples.
19
+ :type fl: int
20
+
21
+ :return: **xfilt** (*ndarray*) - filtered time series x
22
+ """
23
+
24
+ assert len(x) > fl, 'raw data is shorter than gaussian filter'
25
+
26
+ coeffs = _gaussian_coeffs(fl)
27
+
28
+ # set up ring buffer
29
+ filt_len = len(coeffs)
30
+ ring_buffer = np.zeros(filt_len)
31
+
32
+ xfilt = np.zeros(len(x))
33
+
34
+ for i in range(len(x)):
35
+ ring_buffer = np.append(ring_buffer[1:], x[i])
36
+ filt_vec = ring_buffer*coeffs
37
+
38
+ xfilt[i] = sum(filt_vec)
39
+
40
+ return xfilt
41
+
42
+
43
+ def _gaussian_coeffs(fl):
44
+ """ Calculate a gaussian filter to convolve with things that need filtering.
45
+
46
+ :param fl: length of the filter in # of samples
47
+ :type fl: int
48
+
49
+ :return: **gauss_prms** (*ndarray*) - filter coefficients
50
+ """
51
+ half_sample = np.floor(.5*fl)
52
+ coeff_len = int(half_sample + 1)
53
+
54
+ frac = 6./fl # same as NAVO and LDEO versions, and I think this is the secret sauce?
55
+
56
+ gauss_prms = np.zeros(coeff_len)
57
+ m = 0
58
+ for i in range(coeff_len, 0, -1):
59
+ x = (half_sample - i)*frac
60
+ x = -.5*x**2
61
+ gauss_prms[m] = np.exp(x)
62
+ m += 1
63
+
64
+ # reflect
65
+ gauss_prms = np.hstack(
66
+ (gauss_prms[1:][::-1], gauss_prms[0], gauss_prms[1:]))
67
+
68
+ # normalize
69
+ gauss_prms = gauss_prms/sum(gauss_prms)
70
+
71
+ return gauss_prms
72
+
73
+
74
+ def decode_dgs_status_bits(stat, key='status'):
75
+ """ Decode status bits from integers in DGS gravimeter files.
76
+
77
+ Return flags as a dict (for one integer input) or a dataframe (status df input)
78
+
79
+ :param stat: status bit(s)
80
+ :type stat: int or pd.DataFrame
81
+ :param key: the DataFrame column for the status bits if input is a DataFrame
82
+ :type key: string, optional
83
+
84
+ :return: **stat** (*dict or pd.DataFrame*) - decoded bits
85
+ """
86
+ # flags from Jasmine's code
87
+ # flags = ['clamp','unclamp','GPSsync','feedback','R1','R2','ADlock','Rcvd',\
88
+ # 'NavMod1','NavMod2','Free','SensCom','GPStime','ADsat','Datavalid','PlatCom']
89
+ # flags from DGS documentation?
90
+ flags = ['clamp status', 'GPSsync', 'reserved', 'feedback', 'R1', 'R2', 'ADlock', 'ack host',
91
+ 'NavMod1', 'NavMod2', 'dgs1_trouble', 'dgs2_trouble', 'GPStime', 'ADsat', 'nemo1', 'nemo2']
92
+
93
+ assert type(stat) == int or type(
94
+ stat) == DataFrame, 'bad input type for status bits'
95
+ if type(stat) == int:
96
+ bt = format(stat, '016b')
97
+ out = {}
98
+ for i, f in enumerate(flags):
99
+ out[f] = bt[i]
100
+ return out
101
+ else: # dataframe, add columns
102
+ bts = np.array([format(s, '016b') for s in stat[key]])
103
+ for i, f in enumerate(flags):
104
+ stat.insert(i+1, f, [b[i] for b in bts])
105
+ return stat
106
+
107
+
108
+ def clean_ini_to_toml(ini_file):
109
+ """ Read in a .ini file and try to rewrite as toml-compliant.
110
+
111
+ This uses simple, prescriptive regex stuff to (hopefully) clean out
112
+ ini conventions that don't work with toml.
113
+ It writes out a toml file with the same name as the input, with
114
+ the extension .ini replaced by .toml
115
+
116
+ :param ini_file: path to input ini file
117
+ """
118
+
119
+ with open(ini_file, 'r') as file:
120
+ text = file.read() # just read the whole thing
121
+
122
+ # fix comments (// to #)
123
+ text = re.sub('//', '#', text)
124
+
125
+ # clean out tabs
126
+ text = re.sub('\t', ' ', text)
127
+
128
+ # fix TRUE and FALSE
129
+ text = re.sub('TRUE', 'true', text)
130
+ text = re.sub('FALSE', 'false', text)
131
+
132
+ text = re.sub('$', '', text)
133
+
134
+ # fix unquoted strings and write
135
+ fo = open(ini_file.rstrip('ini')+'toml', 'w')
136
+ lines = text.split('\n')
137
+ for ln in lines:
138
+ if ln.startswith('$'):
139
+ ln = re.sub('\$', '', ln) # clean out $ for talkers
140
+
141
+ if ln.startswith('[') or ln.startswith('#') or ln == '':
142
+ fo.write(ln)
143
+ fo.write('\n')
144
+ continue
145
+
146
+ val = ln.split('=')[1] # get value from key=value pair
147
+ extra_comm = ''
148
+ if '#' in val:
149
+ val = val.split('#')[0]
150
+ extra_comm = '='.join(val.split('#')[1:])
151
+ val = val.strip()
152
+
153
+ if val == 'true' or val == 'false': # skip bools
154
+ fo.write(ln)
155
+ fo.write('\n')
156
+ continue
157
+ if val.startswith("\""): # skip strings already quoted
158
+ fo.write(ln)
159
+ fo.write('\n')
160
+ continue
161
+ try:
162
+ float(val)
163
+ except:
164
+ val = "\"" + val + "\""
165
+ ln = '= '.join((ln.split('=')[0], val))
166
+ if extra_comm != '':
167
+ ln = ' #'.join((ln, extra_comm))
168
+ fo.write(ln)
169
+ fo.write('\n')
170
+
171
+ fo.close()
172
+ return
173
+
174
+
175
+ class _SnappingCursor:
176
+ """A cross-hair cursor that snaps to the closest *x* point on a line
177
+
178
+ This is for a two-axis figure where the first axis (ax1)
179
+ has a cross-hair cursor, and the second axis (ax2) has a dot that's linked
180
+ to the cursor in ax1. Basically, we can plot two different values for one
181
+ time series, and use a cursor over one of them to move a dot around on the other one.
182
+
183
+ The time series with the cross hair cursor must have x values that are unique and sorted.
184
+
185
+ Since this class is designed for a specific use case, there are some hard-coded assumptions
186
+ about the names of columns in the DataFrame being used for plotting.
187
+
188
+ .. The specific use case here is a plot of FAA vs time (so, x unique and sorted) with
189
+ the cursor, and a dot moving around on a map view of a ship track (x not necessarily
190
+ unique or sorted).
191
+
192
+ .. The two are linked via the pandas DataFrame "data", which has columns *tsec*,
193
+ *lon_new*, and *lat_new*
194
+
195
+ The cursor can also register mouse clicks so the user can interactively select points
196
+ that are accessible later.
197
+
198
+ For usage, see the example script interactive_line_pick.py included with the
199
+ shipgrav package
200
+
201
+ Heavily adapted from the matplotlib cross-hair cursor demo at
202
+ https://matplotlib.org/stable/gallery/event_handling/cursor_demo.html
203
+
204
+ :param ax1: first axis, for cursor; x axis unique and sorted (tsec in data)
205
+ :param ax2: second axis, for points; lon_new vs lat_new in data
206
+ :param line: the line plotted in ax1, of type matplotlib.lines.Line2D
207
+ :param dot: single point plotted in ax2 that will move around, also Line2D
208
+ :param data: pd.DataFrame with tsec, lon_new, and lat_new columns that correspond
209
+ to what's plotted in both axes
210
+ """
211
+
212
+ def __init__(self, ax1, ax2, line, dot, data):
213
+ self.ax1 = ax1
214
+ self.dot = dot
215
+ self.data = data
216
+ minx = ax1.get_xlim()[0]
217
+ maxy = ax1.get_ylim()[1]
218
+ self.horizontal_line = ax1.axhline(y=maxy, color='k', lw=0.8, ls='--')
219
+ self.vertical_line = ax1.axvline(x=minx, color='k', lw=0.8, ls='--')
220
+ self.x, self.y = line.get_data()
221
+ self._last_index = None
222
+
223
+ self.x_seg = []
224
+ self.y_seg = []
225
+ self.i_seg = []
226
+ self.scatters = ax2.plot(
227
+ self.x_seg, self.y_seg, marker='o', color='r', linestyle=None)
228
+
229
+ def set_cross_hair_visible(self, visible):
230
+ need_redraw = self.horizontal_line.get_visible() != visible
231
+ self.horizontal_line.set_visible(visible)
232
+ self.vertical_line.set_visible(visible)
233
+ return need_redraw
234
+
235
+ def on_mouse_move(self, event):
236
+ if not event.inaxes == self.ax1:
237
+ self._last_index = None
238
+ need_redraw = self.set_cross_hair_visible(False)
239
+ if need_redraw:
240
+ self.ax1.figure.canvas.draw()
241
+ else:
242
+ self.set_cross_hair_visible(True)
243
+ x, y = event.xdata, event.ydata
244
+ index = min(np.searchsorted(self.x, x), len(self.x) - 1)
245
+ if index == self._last_index:
246
+ return # still on the same data point. Nothing to do.
247
+ self._last_index = index
248
+ x = self.x[index]
249
+ y = self.y[index]
250
+ # update the line positions for the cursor
251
+ self.horizontal_line.set_ydata([y])
252
+ self.vertical_line.set_xdata([x])
253
+
254
+ # and also update ax2 dot
255
+ ind_x = np.argmin(abs(x - self.data['tsec']))
256
+ self.dot.update({'xdata': [self.data.iloc[ind_x]['lon_new'],], 'ydata': [
257
+ self.data.iloc[ind_x]['lat_new'],]})
258
+ self.ax1.figure.canvas.draw()
259
+
260
+ def on_mouse_click(self, event):
261
+ if event.button == 1:
262
+ x, y = event.xdata, event.ydata
263
+ ind_x = np.argmin(abs(x - self.data['tsec']))
264
+ self.x_seg.append(self.data.iloc[ind_x]['lon_new'])
265
+ self.y_seg.append(self.data.iloc[ind_x]['lat_new'])
266
+ self.i_seg.append(ind_x)
267
+ self.scatters[0].update({'xdata': self.x_seg, 'ydata': self.y_seg})
268
+
269
+ if event.button == 3: # pop the last element of the lists
270
+ self.x_seg.pop()
271
+ self.y_seg.pop()
272
+ self.i_seg.pop()
273
+ self.scatters[0].update({'xdata': self.x_seg, 'ydata': self.y_seg})
274
+
275
+ self.ax1.figure.canvas.draw()
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.3
2
+ Name: shipgrav
3
+ Version: 1.0.0
4
+ Summary: Functions for marine gravity data processing and reduction
5
+ Author-email: "Hannah F. Mark" <hmark@whoi.edu>
6
+ Maintainer-email: "Hannah F. Mark" <hmark@whoi.edu>
7
+ License-File: LICENSE
8
+ Keywords: UNOLS,gravimetry,marine gravity
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Dist: matplotlib
13
+ Requires-Dist: numpy
14
+ Requires-Dist: pandas
15
+ Requires-Dist: pyyaml
16
+ Requires-Dist: scipy
17
+ Requires-Dist: statsmodels
18
+ Requires-Dist: tomli
19
+ Description-Content-Type: text/markdown
20
+
21
+ # shipgrav
22
+ [![build status](https://github.com/PFPE/shipgrav/workflows/tests/badge.svg)](https://github.com/PFPE/shipgrav/actions)
23
+
24
+ shipgrav is a Python package designed for reading, processing, and reducing marine gravity data from UNOLS ships. It is created and maintained by PFPE for the marine gravimetry community. The shipgrav repository also contains scripts with example workflows for gravity data processing and reduction.
25
+
26
+ ## Dependencies
27
+ python 3.9+\
28
+ numpy\
29
+ scipy\
30
+ pandas 2.0+\
31
+ statsmodels\
32
+ tomli\
33
+ pyyaml
34
+
35
+ To run the example scripts, you will also need matplotlib.
36
+
37
+ ## Installation
38
+ shipgrav can be installed using `pip`. Detailed instructions are in the [documentation](https://shipgrav.readthedocs.io/).
39
+
40
+ ## Documentation and usage
41
+ The shipgrav documentation is available online at [shipgrav.readthedocs.io](https://shipgrav.readthedocs.io/). It can also be accessed offline by building the contents of the `docs` folder using sphinx.
42
+
43
+ ## Contributing to shipgrav
44
+ Please do! If you have ideas for how to make shipgrav better, you can raise an issue on github or contact PFPE.
@@ -0,0 +1,24 @@
1
+ shipgrav/__init__.py,sha256=oLzfXDKCuKsg3qHtE7Pg_8sxUkavmlfn1VkN5J36nWQ,11043
2
+ shipgrav/database.toml,sha256=qRSZBRsoMsbyhjkaYlXsWtVDz3JAhoW-cYfSoo8U3K4,532
3
+ shipgrav/grav.py,sha256=OQK2HNiZtLlycjVwidpD902K08NaWbZXnjZSbjyPF8I,43410
4
+ shipgrav/io.py,sha256=j9uy5z-CqhkUN4T-3iW5o5-cRLgjoGwfNc4o7cjw_pA,29273
5
+ shipgrav/nav.py,sha256=3E2Il-PhTEPVv8_8PrLVe7h6slHq-2HYX_Q8PdpB6X4,2895
6
+ shipgrav/utils.py,sha256=h5gd8cnWlO1pyAqLkJIVrbFRRLbZzHj-xJ9pWchaHtI,9521
7
+ shipgrav/tests/__init__.py,sha256=mejp00TQif0WLU45j40zfBQXN2RujaOP6ZSTgxUm4A8,367
8
+ shipgrav/tests/__main__.py,sha256=iJ8xSU8ucCmIDcOuv8uasZ9iV5dNR0DDuFRZrG0o2hE,38
9
+ shipgrav/tests/test_grav_data.py,sha256=OnPNW8XIG3DvWOr_trQY71XLzA0rff8VfGd4Az3B37Q,3403
10
+ shipgrav/tests/test_grav_nodata.py,sha256=0z5KUTzT9cJPko_bhyDG4EFm4i5xgOdVhJVKU2cPxX8,1493
11
+ shipgrav/tests/test_io.py,sha256=RBRB2GYtesuSv68lJl5yMOLH2DGFz2AAKADyPKq8Dhk,2259
12
+ shipgrav/tests/test_nav.py,sha256=6Ndn1mHN8kGruwk3R5yJFiK0n9Wt3XcK0GQ9PGbRIP8,1194
13
+ shipgrav/tests/test_utils.py,sha256=10brOKgB5ij-oXqQnfo3CkMmZxSQMxSr_KVLLN1g1u0,969
14
+ shipgrav/tests/ex_files/AT05_01_bgm.RGS,sha256=3MnlHJiIyzYe7KGr-bD8dfQ1nS_-Dt4A4g6TiVuNaBw,515
15
+ shipgrav/tests/ex_files/DGStest_laptop.dat,sha256=kUwAMltUXm32Dv9BnaDakA-KtNAuMxgSC504F8_0aws,353351
16
+ shipgrav/tests/ex_files/IXBlue.yaml,sha256=W-GGSMrSSvIXzX-b4rIU0u6Fv8bgNccZzNjTy-K1dNE,5394
17
+ shipgrav/tests/ex_files/SR2312_dgs_raw.txt,sha256=rTlIn75MBICBjnkjcn9SPCwpC5J3lMs4aq7-y6-dAmU,368
18
+ shipgrav/tests/ex_files/SR2312_mru.txt,sha256=YBM4qy4BS_D0U4ngEyoIFZr4QQbMWQgEO681fVEmgYM,1060
19
+ shipgrav/tests/ex_files/TN400_bgm.Raw,sha256=DL7T2aJednjGLMKJo6K8KcUYLMjQCiv6bsUiczlVwKw,74
20
+ shipgrav/tests/ex_files/TN400_nav.Raw,sha256=66dgRQTXjf0JItMWE1ZGUO6NptrGqV6JyN2wdYe5bW8,194
21
+ shipgrav-1.0.0.dist-info/METADATA,sha256=Ou01ttXgo18YQDfv2EngjSm-paE81HEqzkZN2Fd7H8A,1745
22
+ shipgrav-1.0.0.dist-info/WHEEL,sha256=fl6v0VwpzfGBVsGtkAkhILUlJxROXbA3HvRL6Fe3140,105
23
+ shipgrav-1.0.0.dist-info/licenses/LICENSE,sha256=5X8cMguM-HmKfS_4Om-eBqM6A1hfbgZf6pfx2G24QFI,35150
24
+ shipgrav-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.25.0
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any