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