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,905 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import tempfile
4
+ import os
5
+ import plotly.graph_objects as go
6
+ import streamlit as st
7
+ from plotly_resampler import FigureResampler
8
+ from pyadps.utils import sensor_health
9
+ from utils.sensor_health import sound_speed_correction, tilt_sensor_check
10
+
11
+ if "flead" not in st.session_state:
12
+ st.write(":red[Please Select Data!]")
13
+ st.stop()
14
+
15
+ ds = st.session_state.ds
16
+
17
+
18
+ # ----------------- Functions ---------------
19
+
20
+
21
+ # File Access Function
22
+ @st.cache_data()
23
+ def file_access(uploaded_file):
24
+ """
25
+ Function creates temporary directory to store the uploaded file.
26
+ The path of the file is returned
27
+
28
+ Args:
29
+ uploaded_file (string): Name of the uploaded file
30
+
31
+ Returns:
32
+ path (string): Path of the uploaded file
33
+ """
34
+ temp_dir = tempfile.mkdtemp()
35
+ path = os.path.join(temp_dir, uploaded_file.name)
36
+ with open(path, "wb") as f:
37
+ f.write(uploaded_file.getvalue())
38
+ return path
39
+
40
+
41
+ def status_color_map(value):
42
+ # Define a mapping function for styling
43
+ if value == "True":
44
+ return "background-color: green; color: white"
45
+ elif value == "False":
46
+ return "background-color: red; color: white"
47
+
48
+
49
+ # -------------- Widget Functions -------------
50
+
51
+
52
+ # Depth Tab
53
+ def set_button_upload_depth():
54
+ if st.session_state.uploaded_file_depth is not None:
55
+ st.session_state.pspath = file_access(st.session_state.uploaded_file_depth)
56
+ df_depth = pd.read_csv(st.session_state.pspath, header=None)
57
+ numpy_depth = df_depth.to_numpy()
58
+ st.session_state.df_numpy_depth = np.squeeze(numpy_depth)
59
+ if len(st.session_state.df_numpy_depth) != st.session_state.head.ensembles:
60
+ st.session_state.isDepthModified_ST = False
61
+ else:
62
+ st.session_state.depth = st.session_state.df_numpy_depth
63
+ st.session_state.isDepthModified_ST = True
64
+
65
+
66
+ def set_button_depth():
67
+ # st.session_state.depth = st.session_state.depth * 0 + int(
68
+ # st.session_state.fixeddepth_ST * 10
69
+ # )
70
+ st.session_state.depth = np.full(
71
+ st.session_state.head.ensembles, st.session_state.fixeddepth_ST
72
+ )
73
+ st.session_state.depth *= 10
74
+ st.session_state.isDepthModified_ST = True
75
+
76
+
77
+ def reset_button_depth():
78
+ st.session_state.depth = st.session_state.vlead.depth_of_transducer.data
79
+ st.session_state.isDepthModified_ST = False
80
+
81
+
82
+ # Salinity Tab
83
+ def set_button_upload_salinity():
84
+ if st.session_state.uploaded_file_salinity is not None:
85
+ st.session_state.pspath = file_access(st.session_state.uploaded_file_salinity)
86
+ df_salinity = pd.read_csv(st.session_state.pspath, header=None)
87
+ numpy_salinity = df_salinity.to_numpy()
88
+ st.session_state.df_numpy_salinity = np.squeeze(numpy_salinity)
89
+ if len(st.session_state.df_numpy_salinity) != st.session_state.head.ensembles:
90
+ st.session_state.isSalinityModified_ST = False
91
+ else:
92
+ st.session_state.salinity = st.session_state.df_numpy_salinity
93
+ st.session_state.isSalinityModified_ST = True
94
+
95
+
96
+ def set_button_salinity():
97
+ st.session_state.salinity = np.full(
98
+ st.session_state.head.ensembles, st.session_state.fixedsalinity_ST
99
+ )
100
+ st.session_state.isSalinityModified_ST = True
101
+
102
+
103
+ def reset_button_salinity():
104
+ st.session_state.salinity = st.session_state.vlead.salinity.data
105
+ st.session_state.isSalinityModified_ST = False
106
+
107
+
108
+ # Temperature Tab
109
+ def set_button_upload_temperature():
110
+ if st.session_state.uploaded_file_temperature is not None:
111
+ st.session_state.pspath = file_access(
112
+ st.session_state.uploaded_file_temperature
113
+ )
114
+ df_temperature = pd.read_csv(st.session_state.pspath, header=None)
115
+ numpy_temperature = df_temperature.to_numpy()
116
+ st.session_state.df_numpy_temperature = np.squeeze(numpy_temperature)
117
+ if (
118
+ len(st.session_state.df_numpy_temperature)
119
+ != st.session_state.head.ensembles
120
+ ):
121
+ st.session_state.isTemperatureModified_ST = False
122
+ else:
123
+ st.session_state.temperature = st.session_state.df_numpy_temperature
124
+ st.session_state.isTemperatureModified_ST = True
125
+
126
+
127
+ def set_button_temperature():
128
+ st.session_state.temperature = np.full(
129
+ st.session_state.head.ensembles, fixedtemperature_ST
130
+ )
131
+ st.session_state.isTemperatureModified_ST = True
132
+
133
+
134
+ def reset_button_temperature():
135
+ st.session_state.temperature = st.session_state.vlead.temperature.data
136
+ st.session_state.isTemperatureModified_ST = False
137
+
138
+
139
+ # Corrections/Threshold Tab
140
+ def set_threshold_button():
141
+ if st.session_state.sensor_roll_checkbox:
142
+ rollmask = np.copy(st.session_state.sensor_mask_temp)
143
+ roll = ds.variableleader.roll.data
144
+ updated_rollmask = tilt_sensor_check(
145
+ roll, rollmask, cutoff=st.session_state.roll_cutoff_ST
146
+ )
147
+ st.session_state.sensor_mask_temp = updated_rollmask
148
+ st.session_state.isRollCheck_ST = True
149
+
150
+ if st.session_state.sensor_pitch_checkbox:
151
+ pitchmask = np.copy(st.session_state.sensor_mask_temp)
152
+ pitch = ds.variableleader.pitch.data
153
+ updated_pitchmask = tilt_sensor_check(
154
+ pitch, pitchmask, cutoff=st.session_state.pitch_cutoff_ST
155
+ )
156
+ st.session_state.sensor_mask_temp = updated_pitchmask
157
+ st.session_state.isPitchCheck_ST = True
158
+
159
+ if (
160
+ st.session_state.sensor_fix_velocity_checkbox
161
+ and not st.session_state.sensor_ischeckbox_disabled
162
+ ):
163
+ sound = st.session_state.sound_speed
164
+ t = st.session_state.temperature
165
+ s = st.session_state.salinity
166
+ d = st.session_state.depth
167
+ velocity = sound_speed_correction(
168
+ st.session_state.velocity_sensor, sound, t, s, d
169
+ )
170
+ st.session_state.velocity_temp = velocity
171
+ st.session_state.isVelocityModifiedSound_ST = True
172
+
173
+
174
+ # Save Tab
175
+ def reset_threshold_button():
176
+ st.session_state.isRollCheck_ST = False
177
+ st.session_state.isPitchCheck_ST = False
178
+ st.session_state.isVelocityModifiedSound_ST = False
179
+ st.session_state.sensor_mask_temp = np.copy(st.session_state.orig_mask)
180
+ st.session_state.velocity_temp = np.copy(st.session_state.velocity)
181
+
182
+
183
+ def reset_sensor():
184
+ # Deactivate Global Test
185
+ st.session_state.isSensorTest = False
186
+ # Deactivate Local Tests
187
+ st.session_state.isRollCheck_ST = False
188
+ st.session_state.isPitchCheck_ST = False
189
+ # Deactivate Data Modification Tests
190
+ st.session_state.isDepthModified_ST = False
191
+ st.session_state.isSalinityModified_ST = False
192
+ st.session_state.isTemperatureModified_ST = False
193
+ st.session_state.isVelocityModifiedSound_ST = False
194
+
195
+ # Reset Mask Data
196
+ # `sensor_mask_temp` holds and transfers the mask changes between each section
197
+ st.session_state.sensor_mask_temp = np.copy(st.session_state.orig_mask)
198
+ # `sensor_mask` holds the final changes in the page after applying save button
199
+ st.session_state.sensor_mask = np.copy(st.session_state.orig_mask)
200
+
201
+ # Reset General Data
202
+ #
203
+ # The sensor test includes changes in ADCP data due to sound speed correction
204
+ st.session_state.depth = st.session_state.vlead.depth_of_transducer.data
205
+ st.session_state.salinity = st.session_state.vlead.salinity.data
206
+ st.session_state.temperature = st.session_state.vlead.temperature.data
207
+ # The `velocity_sensor` holds velocity data for correction
208
+ st.session_state.velocity_temp = np.copy(st.session_state.velocity)
209
+ st.session_state.velocity_sensor = np.copy(st.session_state.velocity)
210
+
211
+
212
+ def save_sensor():
213
+ st.session_state.velocity_sensor = np.copy(st.session_state.velocity_temp)
214
+ st.session_state.sensor_mask = np.copy(st.session_state.sensor_mask_temp)
215
+ st.session_state.isSensorTest = True
216
+ # Deactivate Checks for other pages
217
+ st.session_state.isQCTest = False
218
+ st.session_state.isProfileMask = False
219
+ st.session_state.isGridSave = False
220
+ st.session_state.isVelocityMask = False
221
+
222
+
223
+ # Plot Function
224
+ @st.cache_data
225
+ def lineplot(data, title, slope=None, xaxis="time"):
226
+ if xaxis == "time":
227
+ xdata = st.session_state.date
228
+ else:
229
+ xdata = st.session_state.ensemble_axis
230
+ scatter_trace = FigureResampler(go.Figure())
231
+ scatter_trace = go.Scatter(
232
+ x=xdata, y=data, mode="lines", name=title, marker=dict(color="blue", size=10)
233
+ )
234
+ # Create the slope line trace
235
+ if slope is not None:
236
+ line_trace = go.Scatter(
237
+ x=xdata,
238
+ y=slope,
239
+ mode="lines",
240
+ name="Slope Line",
241
+ line=dict(color="red", width=2, dash="dash"),
242
+ )
243
+ fig = go.Figure(data=[scatter_trace, line_trace])
244
+ else:
245
+ fig = go.Figure(data=[scatter_trace])
246
+
247
+ st.plotly_chart(fig)
248
+
249
+
250
+ # Session States
251
+ if not st.session_state.isSensorPageReturn:
252
+ st.write(":grey[Creating a new mask file ...]")
253
+ # Check if any test is carried out using isAnyQCTest().
254
+ # If the page is accessed first time, set all sensor session states
255
+ # to default.
256
+ if st.session_state.isFirstSensorVisit:
257
+ reset_sensor()
258
+ st.session_state.isFirstSensorVisit = False
259
+ else:
260
+ # If the page is revisited, warn the user not to change the settings
261
+ # without resetting the mask file.
262
+ # if st.session_state.isSensorPageReturn:
263
+ st.write(":grey[Working on a saved mask file ...]")
264
+ st.write(
265
+ ":orange[WARNING! Sensor test already completed. Reset to change settings.]"
266
+ )
267
+ reset_button_saved_mask = st.button("Reset Mask Data", on_click=reset_sensor)
268
+
269
+ if reset_button_saved_mask:
270
+ st.write(":green[Mask data is reset to default]")
271
+
272
+ # ------------------------------------
273
+ # -------------WEB PAGES -------------
274
+ # ------------------------------------
275
+
276
+
277
+ # ----------- SENSOR HEALTH ----------
278
+ st.header("Sensor Health", divider="blue")
279
+ st.write(
280
+ """
281
+ The following details can be used to determine whether the
282
+ additional sensors are functioning properly.
283
+ """
284
+ )
285
+
286
+ tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8 = st.tabs(
287
+ [
288
+ "Pressure",
289
+ "Salinity",
290
+ "Temperature",
291
+ "Heading",
292
+ "Roll",
293
+ "Pitch",
294
+ "Corrections",
295
+ "Save/Reset",
296
+ ]
297
+ )
298
+
299
+ # ################## Pressure Sensor Check ###################
300
+ with tab1:
301
+ st.subheader("1. Pressure Sensor Check", divider="orange")
302
+ st.write("""
303
+ Verify whether the pressure sensor is functioning correctly
304
+ or exhibiting drift. The actual deployment depth can be
305
+ cross-checked using the mooring diagram for confirmation.
306
+ To remove outliers, apply the standard deviation method.
307
+ """)
308
+ depth = ds.variableleader.depth_of_transducer
309
+ depth_data = depth.data * depth.scale * 1.0
310
+
311
+ leftd, rightd = st.columns([1, 1])
312
+ # Clean up the deployment and recovery data
313
+ # Compute mean and standard deviation
314
+ depth_median = np.median(depth_data)
315
+ depth_std = np.nanstd(depth_data)
316
+ # Get the number of standard deviation
317
+ with rightd:
318
+ depth_no_std = st.number_input(
319
+ "Standard Deviation Cutoff", 0.01, 10.0, 3.0, 0.1
320
+ )
321
+ depth_xbutton = st.radio(
322
+ "Select an x-axis to plot", ["time", "ensemble"], horizontal=True
323
+ )
324
+ # Local Reset
325
+ depth_reset = st.button("Reset Depth to Default", on_click=reset_button_depth)
326
+ if depth_reset:
327
+ st.success("Depth reset to default")
328
+
329
+ # Mark data above 3 standard deviation as bad
330
+ depth_bad = np.abs(depth_data - depth_median) > depth_no_std * depth_std
331
+ depth_data[depth_bad] = np.nan
332
+ depth_nan = ~np.isnan(depth_data)
333
+ # Remove data that are bad
334
+ depth_x = ds.variableleader.rdi_ensemble.data[depth_nan]
335
+ depth_y = depth_data[depth_nan]
336
+
337
+ # Compute the slope
338
+ depth_slope, depth_intercept = np.polyfit(depth_x, depth_y, 1)
339
+ depth_fitted_line = depth_slope * st.session_state.ensemble_axis + depth_intercept
340
+ depth_change = depth_fitted_line[-1] - depth_fitted_line[0]
341
+ st.session_state.sensor_depth_data = depth_data
342
+
343
+ # Display median and slope
344
+ with leftd:
345
+ st.write(":blue-background[Additional Information:]")
346
+ st.write(
347
+ "**Depth Sensor**: ", st.session_state.flead.ez_sensor()["Depth Sensor"]
348
+ )
349
+ st.write(f"Total ensembles: `{st.session_state.head.ensembles}`")
350
+ st.write(f"**Median depth**: `{depth_median/10} (m)`")
351
+ st.write(f"**Change in depth**: `{np.round(depth_change, 3)} (m)`")
352
+ st.write("**Depth Modified**: ", st.session_state.isDepthModified_ST)
353
+
354
+ # Plot the data
355
+ # label= depth.long_name + ' (' + depth.unit + ')'
356
+ label = depth.long_name + " (m)"
357
+ lineplot(depth_data / 10, label, slope=depth_fitted_line / 10, xaxis=depth_xbutton)
358
+
359
+ st.info(
360
+ """
361
+ If the pressure sensor is not working, upload corrected *CSV*
362
+ file containing the transducer depth. The number of ensembles
363
+ should match the original file. The *CSV* file should contain
364
+ only single column without header.
365
+ """,
366
+ icon="ℹ️",
367
+ )
368
+
369
+ st.session_state.depthoption_ST = st.radio(
370
+ "Select method for depth correction:",
371
+ ["File Upload", "Fixed Value"],
372
+ horizontal=True,
373
+ )
374
+
375
+ if st.session_state.depthoption_ST == "Fixed Value":
376
+ st.session_state.fixeddepth_ST = st.number_input(
377
+ "Enter corrected depth (m): ",
378
+ value=None,
379
+ min_value=0,
380
+ placeholder="Type a number ...",
381
+ )
382
+ st.session_state.isFixedDepth_ST = st.button(
383
+ "Change Depth", on_click=set_button_depth
384
+ )
385
+ if st.session_state.isFixedDepth_ST:
386
+ st.success(f"Depth changed to {st.session_state.fixeddepth_ST}")
387
+ else:
388
+ st.session_state.uploaded_file_depth = st.file_uploader(
389
+ "Upload Corrected Depth File",
390
+ type="csv",
391
+ )
392
+ if st.session_state.uploaded_file_depth is not None:
393
+ # Check if the number of ensembles match and call button function
394
+ st.session_state.isUploadDepth_ST = st.button(
395
+ "Check & Save Depth", on_click=set_button_upload_depth
396
+ )
397
+ if st.session_state.isUploadDepth_ST:
398
+ if (
399
+ len(st.session_state.df_numpy_depth)
400
+ != st.session_state.head.ensembles
401
+ ):
402
+ st.error(
403
+ f"""
404
+ **ERROR: Ensembles not matching.** \\
405
+ \\
406
+ Uploaded file ensemble size is {len(st.session_state.df_numpy_depth)}.
407
+ Actual ensemble size: {st.session_state.head.ensembles}.
408
+ """,
409
+ icon="🚨",
410
+ )
411
+ else:
412
+ lineplot(
413
+ np.squeeze(st.session_state.depth.T),
414
+ title="Modified Depth",
415
+ )
416
+ st.success(" Depth of the transducer modified.", icon="✅")
417
+
418
+
419
+ # ################## Conductivity Sensor Check ###################
420
+ with tab2:
421
+ st.subheader("2. Conductivity Sensor Check", divider="orange")
422
+ st.write("""
423
+ Verify whether the salinity sensor is functioning properly
424
+ or showing signs of drift. If a salinity sensor is unavailable,
425
+ use a constant value. To eliminate outliers, apply the standard
426
+ deviation method.
427
+ """)
428
+ salinity = ds.variableleader.salinity
429
+ salinity_data = salinity.data * salinity.scale * 1.0
430
+
431
+ lefts, rights = st.columns([1, 1])
432
+
433
+ # Clean up the deployment and recovery data
434
+ # Compute mean and standard deviation
435
+ salinity_median = np.nanmedian(salinity_data)
436
+ salinity_std = np.nanstd(salinity_data)
437
+
438
+ with rights:
439
+ salinity_no_std = st.number_input(
440
+ "Standard Deviation Cutoff for salinity", 0.01, 10.0, 3.0, 0.1
441
+ )
442
+ salinity_xbutton = st.radio(
443
+ "Select an x-axis to plot for salinity",
444
+ ["time", "ensemble"],
445
+ horizontal=True,
446
+ )
447
+ salinity_reset = st.button(
448
+ "Reset Salinity to Default", on_click=reset_button_salinity
449
+ )
450
+ if salinity_reset:
451
+ st.success("Salinity reset to default")
452
+
453
+ salinity_bad = (
454
+ np.abs(salinity_data - salinity_median) > salinity_no_std * salinity_std
455
+ )
456
+ salinity_data[salinity_bad] = np.nan
457
+ salinity_nan = ~np.isnan(salinity_data)
458
+
459
+ # Remove data that are bad
460
+ salinity_x = st.session_state.ensemble_axis[salinity_nan]
461
+ salinity_y = salinity_data[salinity_nan]
462
+
463
+ ## Compute the slope
464
+ salinity_slope, salinity_intercept = np.polyfit(salinity_x, salinity_y, 1)
465
+ salinity_fitted_line = (
466
+ salinity_slope * st.session_state.ensemble_axis + salinity_intercept
467
+ )
468
+ salinity_change = salinity_fitted_line[-1] - salinity_fitted_line[0]
469
+
470
+ st.session_state.sensor_salinity_data = salinity_data
471
+
472
+ with lefts:
473
+ st.write(":blue-background[Additional Information:]")
474
+ st.write(
475
+ "Conductivity Sensor: ",
476
+ st.session_state.flead.ez_sensor()["Conductivity Sensor"],
477
+ )
478
+ st.write(f"Total ensembles: `{st.session_state.head.ensembles}`")
479
+ st.write(f"Median salinity: {salinity_median} $^o$C")
480
+ st.write(f"Change in salinity: {salinity_change} $^o$C")
481
+ st.write("**Salinity Modified**: ", st.session_state.isSalinityModified_ST)
482
+
483
+ # Plot the data
484
+ label = salinity.long_name
485
+ salinity_data = np.round(salinity_data)
486
+ salinity_fitted_line = np.round(salinity_fitted_line)
487
+ lineplot(
488
+ np.int32(salinity_data),
489
+ label,
490
+ slope=salinity_fitted_line,
491
+ xaxis=salinity_xbutton,
492
+ )
493
+
494
+ st.info(
495
+ """
496
+ If the salinity values are not correct or the sensor is not
497
+ functioning, change the value or upload a
498
+ corrected *CSV* file containing only the salinity values.
499
+ The *CSV* file must have a single column without a header,
500
+ and the number of ensembles should match the original file.
501
+ These updated temperature values will be used to adjust the
502
+ velocity data and depth cell measurements.
503
+ """,
504
+ icon="ℹ️",
505
+ )
506
+
507
+ st.session_state.salinityoption_ST = st.radio(
508
+ "Select method", ["Fixed Value", "File Upload"], horizontal=True
509
+ )
510
+
511
+ if st.session_state.salinityoption_ST == "Fixed Value":
512
+ st.session_state.fixedsalinity_ST = st.number_input(
513
+ "Enter corrected salinity: ",
514
+ value=None,
515
+ min_value=0.0,
516
+ placeholder="Type a number ...",
517
+ )
518
+ st.session_state.isFixedSalinity_ST = st.button(
519
+ "Change Salinity", on_click=set_button_salinity
520
+ )
521
+ if st.session_state.isFixedSalinity_ST:
522
+ st.success(f"Salinity changed to {st.session_state.fixedsalinity_ST}")
523
+ st.session_state.isSalinityModified_ST = True
524
+ else:
525
+ st.write(f"Total ensembles: `{st.session_state.head.ensembles}`")
526
+
527
+ st.session_state.uploaded_file_salinity = st.file_uploader(
528
+ "Upload Corrected Salinity File",
529
+ type="csv",
530
+ )
531
+ if st.session_state.uploaded_file_salinity is not None:
532
+ st.session_state.isUploadSalinity_ST = st.button(
533
+ "Check & Save Salinity", on_click=set_button_upload_salinity
534
+ )
535
+ if st.session_state.isUploadSalinity_ST:
536
+ if (
537
+ len(st.session_state.df_numpy_salinity)
538
+ != st.session_state.head.ensembles
539
+ ):
540
+ st.session_state.isSalinityModified_ST = False
541
+ st.error(
542
+ f"""
543
+ **ERROR: Ensembles not matching.** \\
544
+ \\
545
+ Uploaded file ensemble size is {len(st.session_state.df_numpy_salinity)}.
546
+ Actual ensemble size is {st.session_state.head.ensembles}.
547
+ """,
548
+ icon="🚨",
549
+ )
550
+ else:
551
+ st.success("Salinity changed.", icon="✅")
552
+ lineplot(
553
+ np.squeeze(st.session_state.df_numpy_salinity.T),
554
+ title="Modified Salinity",
555
+ )
556
+
557
+ # ################## Temperature Sensor Check ###################
558
+ with tab3:
559
+ # ################## Temperature Sensor Check ###################
560
+ st.subheader("3. Temperature Sensor Check", divider="orange")
561
+ st.write("""
562
+ Verify whether the temperature sensor is functioning correctly or exhibiting drift.
563
+ The actual deployment depth can be cross-checked using external data (like CTD cast)
564
+ for confirmation. To remove outliers, apply the standard deviation method.
565
+ """)
566
+ temp = ds.variableleader.temperature
567
+ temp_data = temp.data * temp.scale
568
+
569
+ leftt, rightt = st.columns([1, 1])
570
+ ## Clean up the deployment and recovery data
571
+ # Compute mean and standard deviation
572
+ temp_median = np.nanmedian(temp_data)
573
+ temp_std = np.nanstd(temp_data)
574
+ # Get the number of standard deviation
575
+ with rightt:
576
+ temp_no_std = st.number_input(
577
+ "Standard Deviation Cutoff for Temperature", 0.01, 10.0, 3.0, 0.1
578
+ )
579
+ temp_xbutton = st.radio(
580
+ "Select an x-axis to plot for temperature",
581
+ ["time", "ensemble"],
582
+ horizontal=True,
583
+ )
584
+ temp_reset = st.button(
585
+ "Reset Temperature to Default", on_click=reset_button_temperature
586
+ )
587
+ if temp_reset:
588
+ st.success("Temperature Reset to Default")
589
+
590
+ # Mark data above 3 standard deviation as bad
591
+ temp_bad = np.abs(temp_data - temp_median) > temp_no_std * temp_std
592
+ temp_data[temp_bad] = np.nan
593
+ temp_nan = ~np.isnan(temp_data)
594
+ # Remove data that are bad
595
+ temp_x = st.session_state.ensemble_axis[temp_nan]
596
+ temp_y = temp_data[temp_nan]
597
+ ## Compute the slope
598
+ temp_slope, temp_intercept = np.polyfit(temp_x, temp_y, 1)
599
+ temp_fitted_line = temp_slope * st.session_state.ensemble_axis + temp_intercept
600
+ temp_change = temp_fitted_line[-1] - temp_fitted_line[0]
601
+
602
+ st.session_state.sensor_temp_data = temp_data
603
+
604
+ with leftt:
605
+ st.write(":blue-background[Additional Information:]")
606
+ st.write(
607
+ "Temperature Sensor: ",
608
+ st.session_state.flead.ez_sensor()["Temperature Sensor"],
609
+ )
610
+ st.write(f"Total ensembles: `{st.session_state.head.ensembles}`")
611
+ st.write(f"Median temperature: {temp_median} $^o$C")
612
+ st.write(f"Change in temperature: {np.round(temp_change, 3)} $^o$C")
613
+ st.write(
614
+ "**Temperature Modified**: ", st.session_state.isTemperatureModified_ST
615
+ )
616
+
617
+ # Plot the data
618
+ label = temp.long_name + " (oC)"
619
+ lineplot(temp_data, label, slope=temp_fitted_line, xaxis=temp_xbutton)
620
+
621
+ #
622
+ st.info(
623
+ """
624
+ If the temperature sensor is not functioning, upload a
625
+ corrected *CSV* file containing only the temperature values.
626
+ The *CSV* file must have a single column without a header,
627
+ and the number of ensembles should match the original file.
628
+ These updated temperature values will be used to adjust the
629
+ velocity data and depth cell measurements.
630
+ """,
631
+ icon="ℹ️",
632
+ )
633
+
634
+ st.session_state.temperatureoption_ST = st.radio(
635
+ "Select method for temperature correction:",
636
+ ["File Upload", "Fixed Value"],
637
+ horizontal=True,
638
+ )
639
+
640
+ if st.session_state.temperatureoption_ST == "Fixed Value":
641
+ fixedtemperature_ST = st.number_input(
642
+ "Enter corrected temperature: ",
643
+ value=None,
644
+ min_value=0.0,
645
+ placeholder="Type a number ...",
646
+ )
647
+ st.session_state.isFixedTemperature_ST = st.button(
648
+ "Change Temperature", on_click=set_button_temperature
649
+ )
650
+ if st.session_state.isFixedTemperature_ST:
651
+ st.success(f"Temperature changed to {fixedtemperature_ST}")
652
+ st.session_state.isTemperatureModified_ST = True
653
+
654
+ elif st.session_state.temperatureoption_ST == "File Upload":
655
+ st.write(f"Total ensembles: `{st.session_state.head.ensembles}`")
656
+ st.session_state.uploaded_file_temperature = st.file_uploader(
657
+ "Upload Corrected Temperature File",
658
+ type="csv",
659
+ )
660
+ if st.session_state.uploaded_file_temperature is not None:
661
+ st.session_state.isUploadTemperature_ST = st.button(
662
+ "Check & Save Temperature", on_click=set_button_upload_temperature
663
+ )
664
+
665
+ if st.session_state.isUploadTemperature_ST:
666
+ if (
667
+ len(st.session_state.df_numpy_temperature)
668
+ != st.session_state.head.ensembles
669
+ ):
670
+ st.session_state.isTemperatureModified_ST = False
671
+ st.error(
672
+ f"""
673
+ **ERROR: Ensembles not matching.** \\
674
+ \\
675
+ Uploaded file ensemble size is {len(st.session_state.df_numpy_temperature)}.
676
+ Actual ensemble size is {st.session_state.head.ensembles}.
677
+ """,
678
+ icon="🚨",
679
+ )
680
+ else:
681
+ st.success(" The temperature of transducer modified.", icon="✅")
682
+ st.session_state.temperature = st.session_state.df_numpy_temperature
683
+ st.session_state.isTemperatureModified_ST = True
684
+ lineplot(
685
+ np.squeeze(st.session_state.df_numpy_temperature.T),
686
+ title="Modified Temperature",
687
+ )
688
+
689
+
690
+ # ################## Heading Sensor Check ###################
691
+ with tab4:
692
+ st.warning(
693
+ """
694
+ WARNING: Heading sensor corrections are currently unavailable.
695
+ This feature will be included in a future release.
696
+ """,
697
+ icon="⚠️",
698
+ )
699
+ st.subheader("3. Heading Sensor Check", divider="orange")
700
+ head = ds.variableleader.heading
701
+ head_data = head.data * head.scale
702
+
703
+ # Compute mean
704
+ head_rad = np.radians(head_data)
705
+ head_mean_x = np.mean(np.cos(head_rad))
706
+ head_mean_y = np.mean(np.sin(head_rad))
707
+ head_mean_rad = np.arctan2(head_mean_y, head_mean_x)
708
+ head_mean_deg = np.degrees(head_mean_rad)
709
+
710
+ head_xbutton = st.radio(
711
+ "Select an x-axis to plot for headerature",
712
+ ["time", "ensemble"],
713
+ horizontal=True,
714
+ )
715
+
716
+ st.write(f"Mean heading: {np.round(head_mean_deg, 2)} $^o$")
717
+
718
+ # Plot the data
719
+ label = head.long_name
720
+ lineplot(head_data, label, xaxis=head_xbutton)
721
+
722
+ ################### Tilt Sensor Check: Pitch ###################
723
+ with tab5:
724
+ st.subheader("4. Tilt Sensor Check: Pitch", divider="orange")
725
+ st.warning(
726
+ """
727
+ WARNING: Tilt sensor corrections are currently unavailable.
728
+ This feature will be included in a future release.
729
+ """,
730
+ icon="⚠️",
731
+ )
732
+
733
+ st.write("The tilt sensor should not show much variation.")
734
+
735
+ pitch = ds.variableleader.pitch
736
+ pitch_data = pitch.data * pitch.scale
737
+ # Compute mean
738
+ pitch_rad = np.radians(pitch_data)
739
+ pitch_mean_x = np.mean(np.cos(pitch_rad))
740
+ pitch_mean_y = np.mean(np.sin(pitch_rad))
741
+ pitch_mean_rad = np.arctan2(pitch_mean_y, pitch_mean_x)
742
+ pitch_mean_deg = np.degrees(pitch_mean_rad)
743
+
744
+ pitch_xbutton = st.radio(
745
+ "Select an x-axis to plot for pitcherature",
746
+ ["time", "ensemble"],
747
+ horizontal=True,
748
+ )
749
+ st.write(f"Mean pitch: {np.round(pitch_mean_deg, 2)} $^o$")
750
+
751
+ # Plot the data
752
+ label = pitch.long_name
753
+ lineplot(pitch_data, label, xaxis=pitch_xbutton)
754
+
755
+ ################### Tilt Sensor Check: Roll ###################
756
+ with tab6:
757
+ st.subheader("5. Tilt Sensor Check: Roll", divider="orange")
758
+ st.warning(
759
+ """
760
+ WARNING: Tilt sensor corrections are currently unavailable.
761
+ This feature will be included in a future release.
762
+ """,
763
+ icon="⚠️",
764
+ )
765
+ roll = ds.variableleader.roll
766
+ roll_data = roll.data * roll.scale
767
+ # Compute mean
768
+ roll_rad = np.radians(roll_data)
769
+ roll_mean_x = np.mean(np.cos(roll_rad))
770
+ roll_mean_y = np.mean(np.sin(roll_rad))
771
+ roll_mean_rad = np.arctan2(roll_mean_y, roll_mean_x)
772
+ roll_mean_deg = np.degrees(roll_mean_rad)
773
+
774
+ roll_xbutton = st.radio(
775
+ "Select an x-axis to plot for rollerature",
776
+ ["time", "ensemble"],
777
+ horizontal=True,
778
+ )
779
+ st.write(f"Mean roll: {np.round(roll_mean_deg, 2)} $^o$")
780
+
781
+ # Plot the data
782
+ label = roll.long_name
783
+ lineplot(roll_data, label, xaxis=roll_xbutton)
784
+
785
+
786
+ with tab7:
787
+ st.subheader("Apply Sensor Thresholds/Corrections", divider="orange")
788
+ col1, col2 = st.columns([0.4, 0.6], gap="large")
789
+ with col1:
790
+ st.session_state.roll_cutoff_ST = st.number_input(
791
+ "Enter roll threshold (deg):", min_value=0, max_value=359, value=15
792
+ )
793
+ st.session_state.pitch_cutoff_ST = st.number_input(
794
+ "Enter pitch threshold (deg):", min_value=0, max_value=359, value=15
795
+ )
796
+
797
+ with col2:
798
+ st.write("Select Options:")
799
+
800
+ with st.form("Select options"):
801
+ if (
802
+ st.session_state.isTemperatureModified_ST
803
+ or st.session_state.isSalinityModified_ST
804
+ ):
805
+ st.session_state.sensor_ischeckbox_disabled = False
806
+ else:
807
+ st.session_state.sensor_ischeckbox_disabled = True
808
+ st.info("No velocity corrections required.")
809
+
810
+ st.session_state.sensor_roll_checkbox = st.checkbox("Roll Threshold")
811
+ st.session_state.sensor_pitch_checkbox = st.checkbox("Pitch Threshold")
812
+ st.session_state.sensor_fix_velocity_checkbox = st.checkbox(
813
+ "Fix Velocity", disabled=st.session_state.sensor_ischeckbox_disabled
814
+ )
815
+ # fix_depth_button = st.checkbox(
816
+ # "Fix Depth Cell Size", disabled=is_checkbox_disabled
817
+ # )
818
+
819
+ submitted = st.form_submit_button("Submit", on_click=set_threshold_button)
820
+
821
+ if submitted:
822
+ set_threshold_button()
823
+ # Display Threshold Checks
824
+ if st.session_state.sensor_roll_checkbox:
825
+ st.success("Roll Test Applied")
826
+ if st.session_state.sensor_pitch_checkbox:
827
+ st.success("Pitch Test Applied")
828
+ if (
829
+ st.session_state.sensor_fix_velocity_checkbox
830
+ and not st.session_state.sensor_ischeckbox_disabled
831
+ ):
832
+ st.success("Velocity Correction Applied")
833
+
834
+ reset_button_threshold = st.button(
835
+ "Reset Corrections", on_click=reset_threshold_button
836
+ )
837
+
838
+ if reset_button_threshold:
839
+ st.info("Data reset to defaults")
840
+
841
+ with tab8:
842
+ ################## Save Button #############
843
+ st.header("Save Data", divider="blue")
844
+ col1, col2 = st.columns([1, 1])
845
+ with col1:
846
+ save_mask_button = st.button(label="Save Mask Data", on_click=save_sensor)
847
+ if save_mask_button:
848
+ st.success("Mask file saved")
849
+
850
+ # Table summarizing changes
851
+ changes_summary = pd.DataFrame(
852
+ [
853
+ [
854
+ "Depth Modified",
855
+ "True" if st.session_state.isDepthModified_ST else "False",
856
+ ],
857
+ [
858
+ "Salinity Modified",
859
+ "True" if st.session_state.isSalinityModified_ST else "False",
860
+ ],
861
+ [
862
+ "Temperature Modified",
863
+ "True"
864
+ if st.session_state.isTemperatureModified_ST
865
+ else "False",
866
+ ],
867
+ [
868
+ "Pitch Test",
869
+ "True" if st.session_state.isPitchCheck_ST else "False",
870
+ ],
871
+ [
872
+ "Roll Test",
873
+ "True" if st.session_state.isRollCheck_ST else "False",
874
+ ],
875
+ [
876
+ "Velocity Correction (Sound)",
877
+ "True"
878
+ if st.session_state.isVelocityModifiedSound_ST
879
+ else "False",
880
+ ],
881
+ ],
882
+ columns=["Test", "Status"],
883
+ )
884
+ # Apply styles using Styler.apply
885
+ styled_table = changes_summary.style.set_properties(
886
+ **{"text-align": "center"}
887
+ )
888
+ styled_table = styled_table.map(status_color_map, subset=["Status"])
889
+
890
+ # Display the styled table
891
+ st.write(styled_table.to_html(), unsafe_allow_html=True)
892
+
893
+ else:
894
+ st.warning(" WARNING: Mask data not saved", icon="⚠️")
895
+ with col2:
896
+ # Reset local variables
897
+ reset_mask_button = st.button("Reset mask Data", on_click=reset_sensor)
898
+ if reset_mask_button:
899
+ # Global variables reset
900
+ st.session_state.isSensorTest = False
901
+ st.session_state.isQCTest = False
902
+ st.session_state.isGrid = False
903
+ st.session_state.isProfileMask = False
904
+ st.session_state.isVelocityMask = False
905
+ st.success("Mask data is reset to default")