pyadps 0.2.1b0__py3-none-any.whl → 0.3.1b0__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,478 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import plotly.graph_objects as go
5
+ import streamlit as st
6
+ from plotly.subplots import make_subplots
7
+ from plotly_resampler import FigureResampler
8
+ from streamlit.runtime.state import session_state
9
+ from utils.signal_quality import (
10
+ ev_check,
11
+ false_target,
12
+ pg_check,
13
+ echo_check,
14
+ correlation_check,
15
+ )
16
+
17
+ if "flead" not in st.session_state:
18
+ st.write(":red[Please Select Data!]")
19
+ st.stop()
20
+
21
+
22
+ def reset_qctest():
23
+ # Reset Global Test
24
+ st.session_state.isQCTest = False
25
+ # Reset Local Tests
26
+ st.session_state.isQCCheck = False
27
+
28
+ # Reset Data
29
+ st.session_state.isBeamModified = False
30
+ beamdir = st.session_state.ds.fixedleader.system_configuration()["Beam Direction"]
31
+ st.session_state.beam_direction = beamdir
32
+
33
+ # As QC test is not saved the Sensor Page Returns is set to False
34
+ st.session_state.isSensorPageReturn = False
35
+
36
+ # Reset Mask Data
37
+ # Copy the sensor mask if sensor mask completed.
38
+ # Else copy the default mask.
39
+ if st.session_state.isSensorTest:
40
+ st.session_state.qc_mask = np.copy(st.session_state.sensor_mask)
41
+ st.session_state.qc_mask_temp = np.copy(st.session_state.sensor_mask)
42
+ else:
43
+ st.session_state.qc_mask = np.copy(st.session_state.orig_mask)
44
+ st.session_state.qc_mask_temp = np.copy(st.session_state.orig_mask)
45
+
46
+
47
+ def hard_reset(option):
48
+ # Reset Global Test
49
+ st.session_state.isQCTest = False
50
+ # Reset Local Tests
51
+ st.session_state.isQCCheck = False
52
+ st.session_state.isBeamModified = False
53
+ # Reset Data
54
+ beamdir = st.session_state.ds.fixedleader.system_configuration()["Beam Direction"]
55
+ st.session_state.beam_direction = beamdir
56
+
57
+ st.session_state.isSensorPageReturn = False
58
+
59
+ # Reset Mask Data based on user options
60
+ if option == "Sensor Test":
61
+ st.session_state.qc_mask = np.copy(st.session_state.sensor_mask)
62
+ st.session_state.qc_mask_temp = np.copy(st.session_state.sensor_mask)
63
+
64
+ elif option == "Default":
65
+ st.session_state.qc_mask = np.copy(st.session_state.orig_mask)
66
+ st.session_state.qc_mask_temp = np.copy(st.session_state.orig_mask)
67
+
68
+
69
+ def save_qctest():
70
+ st.session_state.qc_mask = np.copy(st.session_state.qc_mask_temp)
71
+ st.session_state.isQCTest = True
72
+ st.session_state.isProfileMask = False
73
+ st.session_state.isGridSave = False
74
+ st.session_state.isVelocityMask = False
75
+ # Indicate previous pages that Test has been carried out
76
+ st.session_state.isSensorPageReturn = True
77
+
78
+
79
+ def qc_submit():
80
+ # st.write(st.session_state.newthresh)
81
+ st.session_state.isQCCheck = True
82
+
83
+ # First Quality check of the page
84
+ mask = np.copy(st.session_state.qc_mask_temp)
85
+ # if st.session_state.isSensorTest:
86
+ # mask = np.copy(st.session_state.sensor_mask)
87
+ # else:
88
+ # mask = np.copy(st.session_state.default_mask)
89
+
90
+ ds = st.session_state.ds
91
+ pgt = st.session_state.pgt
92
+ ct = st.session_state.ct
93
+ et = st.session_state.et
94
+ evt = st.session_state.evt
95
+ ft = st.session_state.ft
96
+ option = st.session_state.option
97
+ mask = pg_check(ds, mask, pgt, threebeam=option)
98
+ mask = correlation_check(ds, mask, ct)
99
+ mask = echo_check(ds, mask, et)
100
+ mask = ev_check(ds, mask, evt)
101
+ mask = false_target(ds, mask, ft, threebeam=True)
102
+ # Store the processed mask in a temporary mask
103
+ st.session_state.qc_mask_temp = mask
104
+
105
+
106
+ if st.session_state.isSensorTest:
107
+ st.write(":grey[Working on a saved mask file ...]")
108
+ if st.session_state.isQCPageReturn:
109
+ st.write(
110
+ ":orange[Warning: QC test already completed. Reset the mask file to change settings.]"
111
+ )
112
+ reset_selectbox = st.selectbox(
113
+ "Choose reset option",
114
+ ("Sensor Test", "Default"),
115
+ index=None,
116
+ placeholder="Reset mask to ...",
117
+ )
118
+ if reset_selectbox is not None:
119
+ hard_reset(reset_selectbox)
120
+ elif st.session_state.isFirstQCVisit:
121
+ reset_qctest()
122
+ st.session_state.isFirstQCVisit = False
123
+ else:
124
+ if st.session_state.isFirstQCVisit:
125
+ # This will rest to the default mask file
126
+ reset_qctest()
127
+ st.session_state.isFirstQCVisit = False
128
+ st.write(":orange[Creating a new mask file ...]")
129
+
130
+
131
+ # Load data
132
+ ds = st.session_state.ds
133
+ flobj = st.session_state.flead
134
+ vlobj = st.session_state.vlead
135
+ velocity = st.session_state.velocity
136
+ echo = st.session_state.echo
137
+ correlation = st.session_state.correlation
138
+ pgood = st.session_state.pgood
139
+ ensembles = st.session_state.head.ensembles
140
+ cells = flobj.field()["Cells"]
141
+ fdata = flobj.fleader
142
+ vdata = vlobj.vleader
143
+ x = np.arange(0, ensembles, 1)
144
+ y = np.arange(0, cells, 1)
145
+
146
+
147
+ @st.cache_data
148
+ def fillplot_plotly(data, colorscale="balance"):
149
+ fig = FigureResampler(go.Figure())
150
+ data1 = np.where(data == -32768, np.nan, data)
151
+ fig.add_trace(
152
+ go.Heatmap(z=data1[:, 0:-1], x=x, y=y, colorscale=colorscale, hoverongaps=False)
153
+ )
154
+ st.plotly_chart(fig)
155
+
156
+
157
+ @st.cache_data
158
+ def lineplot(data, title, slope=None, xaxis="time"):
159
+ if xaxis == "time":
160
+ xdata = st.session_state.date
161
+ else:
162
+ xdata = st.session_state.ensemble_axis
163
+ scatter_trace = go.Scatter(
164
+ x=xdata,
165
+ y=data,
166
+ mode="markers",
167
+ name=title,
168
+ marker=dict(color="blue", size=10), # Customize marker color and size
169
+ )
170
+ # Create the slope line trace
171
+ if slope is not None:
172
+ line_trace = go.Scatter(
173
+ x=xdata,
174
+ y=slope,
175
+ mode="lines",
176
+ name="Slope Line",
177
+ line=dict(color="red", width=2, dash="dash"), # Dashed red line
178
+ )
179
+ fig = go.Figure(data=[scatter_trace, line_trace])
180
+ else:
181
+ fig = go.Figure(data=[scatter_trace])
182
+
183
+ st.plotly_chart(fig)
184
+
185
+
186
+ @st.cache_data
187
+ def plot_noise(dep=0, rec=-1):
188
+ n = dep
189
+ m = rec
190
+ colorleft = [
191
+ "rgb(240, 255, 255)",
192
+ "rgb(115, 147, 179)",
193
+ "rgb(100, 149, 237)",
194
+ "rgb(15, 82, 186)",
195
+ ]
196
+ colorright = [
197
+ "rgb(250, 200, 152)",
198
+ "rgb(255, 165, 0)",
199
+ "rgb(255, 95, 31)",
200
+ "rgb(139, 64, 0)",
201
+ ]
202
+ fig = make_subplots(
203
+ rows=1,
204
+ cols=2,
205
+ subplot_titles=[
206
+ f"Deployment Ensemble ({x[n]+1})",
207
+ f"Recovery Ensemble ({x[m]+1})",
208
+ ],
209
+ )
210
+ for i in range(4):
211
+ fig.add_trace(
212
+ go.Scatter(
213
+ x=echo[i, :, n],
214
+ y=y,
215
+ name=f"Beam (D) {i+1}",
216
+ line=dict(color=colorleft[i]),
217
+ ),
218
+ row=1,
219
+ col=1,
220
+ )
221
+ for i in range(4):
222
+ fig.add_trace(
223
+ go.Scatter(
224
+ x=echo[i, :, m],
225
+ y=y,
226
+ name=f"Beam (R) {i+1}",
227
+ line=dict(color=colorright[i]),
228
+ ),
229
+ row=1,
230
+ col=2,
231
+ )
232
+
233
+ fig.update_layout(height=600, width=800, title_text="Echo Intensity")
234
+ fig.update_xaxes(title="Echo (count)")
235
+ fig.update_yaxes(title="Cells")
236
+ st.plotly_chart(fig)
237
+
238
+
239
+ tab1, tab2, tab3, tab4, tab5 = st.tabs(
240
+ [
241
+ "Noise Floor Identification",
242
+ "QC Tests",
243
+ "Display Mask",
244
+ "Fix Orientation",
245
+ "Save Data",
246
+ ]
247
+ )
248
+ ######### NOISE FLOOR IDENTIFICATION ##############
249
+ with tab1:
250
+ dn = rn = 1
251
+ st.header("Noise Floor Identification", divider="blue")
252
+ st.write(
253
+ """
254
+ If the ADCP has collected data from the air either
255
+ before deployment or after recovery, this data can
256
+ be used to estimate the echo intensity threshold.
257
+ The plots below show the echo intensity from the first
258
+ and last ensembles. The noise level is typically around
259
+ 30-40 counts throughout the entire profile.
260
+ """
261
+ )
262
+ dn = st.number_input("Deployment Ensemble", x[0] + 1, x[-1] + 1, x[0] + 1)
263
+ # r = st.number_input("Recovery Ensemble", -1 * (x[-1] + 1), -1 * (x[0] + 1), -1)
264
+ rn = st.number_input("Recovery Ensemble", x[0] + 1, x[-1] + 1, x[-1] + 1)
265
+ dn = dn - 1
266
+ rn = rn - 1
267
+
268
+ plot_noise(dep=dn, rec=rn)
269
+
270
+
271
+ ################## QC Test ###################
272
+ with tab2:
273
+ st.header("Quality Control Tests", divider="blue")
274
+ st.write("")
275
+
276
+ left, right = st.columns([1, 1])
277
+ with left:
278
+ st.write(""" Teledyne RDI recommends these quality control tests,
279
+ some of which can be configured before deployment.
280
+ The pre-deployment values configured for the ADCP are listed
281
+ in the table below. The noise-floor identification graph above
282
+ can assist in determining the echo intensity threshold.
283
+ For more information about these tests,
284
+ refer to *Acoustic Doppler Current Profiler Principles of
285
+ Operation: A Practical Primer* by Teledyne RDI.""")
286
+ fdata = st.session_state.flead.field()
287
+ st.divider()
288
+ st.write(":blue-background[Additional Information:]")
289
+ st.write(f"Number of Pings per Ensemble: `{fdata["Pings"]}`")
290
+ st.write(f"Number of Beams: `{fdata["Beams"]}`")
291
+ st.divider()
292
+ st.write(":red-background[Thresholds used during deployment:]")
293
+ thresh = pd.DataFrame(
294
+ [
295
+ ["Correlation", fdata["Correlation Thresh"]],
296
+ ["Error Velocity", fdata["Error Velocity Thresh"]],
297
+ ["Echo Intensity", 0],
298
+ ["False Target", fdata["False Target Thresh"]],
299
+ ["Percentage Good", fdata["Percent Good Min"]],
300
+ ],
301
+ columns=["Threshold", "Values"],
302
+ )
303
+
304
+ st.write(thresh)
305
+
306
+ with right:
307
+ with st.form(key="my_form"):
308
+ st.write("Would you like to apply new threshold?")
309
+
310
+ st.session_state.ct = st.number_input(
311
+ "Select Correlation Threshold",
312
+ 0,
313
+ 255,
314
+ fdata["Correlation Thresh"],
315
+ )
316
+
317
+ st.session_state.evt = st.number_input(
318
+ "Select Error Velocity Threshold",
319
+ 0,
320
+ 9999,
321
+ fdata["Error Velocity Thresh"],
322
+ )
323
+
324
+ st.session_state.et = st.number_input(
325
+ "Select Echo Intensity Threshold",
326
+ 0,
327
+ 255,
328
+ 0,
329
+ )
330
+
331
+ st.session_state.ft = st.number_input(
332
+ "Select False Target Threshold",
333
+ 0,
334
+ 255,
335
+ fdata["False Target Thresh"],
336
+ )
337
+
338
+ st.session_state.option = st.selectbox(
339
+ "Would you like to use a three-beam solution?", (True, False)
340
+ )
341
+
342
+ st.session_state.pgt = st.number_input(
343
+ "Select Percent Good Threshold",
344
+ 0,
345
+ 100,
346
+ fdata["Percent Good Min"],
347
+ )
348
+ submit_button = st.form_submit_button(label="Submit", on_click=qc_submit)
349
+
350
+ # mask = st.session_state.qc_mask_temp
351
+ with left:
352
+ if submit_button:
353
+ st.session_state.isQCCheck = True
354
+ st.session_state.newthresh = pd.DataFrame(
355
+ [
356
+ ["Correlation", str(st.session_state.ct)],
357
+ ["Error Velocity", str(st.session_state.evt)],
358
+ ["Echo Intensity", str(st.session_state.et)],
359
+ ["False Target", str(st.session_state.ft)],
360
+ ["Three Beam", str(st.session_state.option)],
361
+ ["Percentage Good", str(st.session_state.pgt)],
362
+ ],
363
+ columns=["Threshold", "Values"],
364
+ )
365
+
366
+ if st.session_state.isQCCheck:
367
+ st.write(":green-background[Current Thresholds]")
368
+ st.write(st.session_state.newthresh)
369
+
370
+
371
+ with tab3:
372
+ st.header("Mask File", divider="blue")
373
+ st.write(
374
+ """
375
+ Display the mask file.
376
+ Ensure to save any necessary changes or apply additional thresholds if needed.
377
+ """
378
+ )
379
+
380
+ leftplot, rightplot = st.columns([1, 1])
381
+ if st.button("Display mask file"):
382
+ with leftplot:
383
+ st.subheader("Default Mask File")
384
+ st.write(
385
+ """
386
+ CAPTION:
387
+ ADCP assigns missing values based on thresholds
388
+ set before deployment. These values cannot be
389
+ recovered and are part of default mask file.
390
+ """
391
+ )
392
+ fillplot_plotly(st.session_state.orig_mask, colorscale="greys")
393
+ with rightplot:
394
+ st.subheader("Updated Mask File")
395
+ # values, counts = np.unique(mask, return_counts=True)
396
+ st.write(
397
+ """
398
+ CAPTION:
399
+ Updated mask displayed after applying threshold.
400
+ If thresholds are not saved, default mask
401
+ is displayed.
402
+ """
403
+ )
404
+ fillplot_plotly(st.session_state.qc_mask_temp, colorscale="greys")
405
+
406
+ with tab4:
407
+ ################## Fix Orientation ###################
408
+ st.subheader("Fix Orientation", divider="orange")
409
+
410
+ if st.session_state.beam_direction == "Up":
411
+ beamalt = "Down"
412
+ else:
413
+ beamalt = "Up"
414
+ st.write(
415
+ f"The current orientation of ADCP is `{st.session_state.beam_direction}`. Use the below option to correct the orientation."
416
+ )
417
+
418
+ beamdir_select = st.radio(f"Change orientation to {beamalt}", ["No", "Yes"])
419
+ if beamdir_select == "Yes":
420
+ st.session_state.beam_direction = beamalt
421
+ st.session_state.isBeamModified = True
422
+ st.write(f"The orientation changed to `{st.session_state.beam_direction}`")
423
+
424
+ with tab5:
425
+ ################## Save Button #############
426
+ st.header("Save Data", divider="blue")
427
+ col1, col2 = st.columns([1, 1])
428
+ with col1:
429
+ save_mask_button = st.button(label="Save Mask Data", on_click=save_qctest)
430
+
431
+ if save_mask_button:
432
+ # st.session_state.qc_mask_temp = mask
433
+ st.success("Mask file saved")
434
+ # Table summarizing changes
435
+ changes_summary = pd.DataFrame(
436
+ [
437
+ [
438
+ "Quality Control Tests",
439
+ "True" if st.session_state.isQCCheck else "False",
440
+ ],
441
+ ["Fix Orientation", st.session_state.beam_direction],
442
+ ],
443
+ columns=["Test", "Status"],
444
+ )
445
+
446
+ # Define a mapping function for styling
447
+ def status_color_map(value):
448
+ if value == "True":
449
+ return "background-color: green; color: white"
450
+ elif value == "False":
451
+ return "background-color: red; color: white"
452
+ elif value == "Up":
453
+ return "background-color: blue; color: white"
454
+ elif value == "Down":
455
+ return "background-color: orange; color: white"
456
+ else:
457
+ return ""
458
+
459
+ # Apply styles using Styler.apply
460
+ styled_table = changes_summary.style.set_properties(
461
+ **{"text-align": "center"}
462
+ )
463
+ styled_table = styled_table.map(status_color_map, subset=["Status"])
464
+
465
+ # Display the styled table
466
+ st.write(styled_table.to_html(), unsafe_allow_html=True)
467
+ else:
468
+ st.warning("Mask data not saved")
469
+ with col2:
470
+ reset_mask_button = st.button("Reset mask Data", on_click=reset_qctest)
471
+ if reset_mask_button:
472
+ # st.session_state.qc_mask_temp = np.copy(st.session_state.orig_mask)
473
+ # st.session_state.isQCCheck = False
474
+ # st.session_state.isQCTest = False
475
+ # st.session_state.isGrid = False
476
+ # st.session_state.isProfileMask = False
477
+ # st.session_state.isVelocityMask = False
478
+ st.success("Mask data is reset to default")