pyadps 0.2.1b0__py3-none-any.whl → 0.3.0b0__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,599 @@
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.profile_test import side_lobe_beam_angle
10
+ from utils.signal_quality import default_mask
11
+ from utils.velocity_test import (
12
+ despike,
13
+ flatline, # magnetic_declination,
14
+ magdec,
15
+ velocity_cutoff,
16
+ wmm2020api,
17
+ velocity_modifier,
18
+ )
19
+
20
+ if "flead" not in st.session_state:
21
+ st.write(":red[Please Select Data!]")
22
+ st.stop()
23
+
24
+ flobj = st.session_state.flead
25
+ vlobj = st.session_state.vlead
26
+ fdata = flobj.fleader
27
+ vdata = vlobj.vleader
28
+
29
+
30
+ def reset_velocitytest():
31
+ # Reset Global Test
32
+ st.session_state.isVelocityTest = False
33
+
34
+ # Reset Local Tests
35
+ st.session_state.isMagnetCheck = False
36
+ st.session_state.isDespikeCheck = False
37
+ st.session_state.isFlatlineCheck = False
38
+ st.session_state.isCutoffCheck = False
39
+
40
+ st.session_state.isVelocityModifiedMagnet = False
41
+
42
+ # Page return
43
+ st.session_state.isProfilePageReturn = False
44
+ if not st.session_state.isProfileTest:
45
+ st.session_state.isQCPageReturn = False
46
+ if not st.session_state.isQCTest:
47
+ st.session_state.isSensorPageReturn = False
48
+
49
+ # Data Reset
50
+ if st.session_state.isRegridCheck:
51
+ st.session_state.velocity_magnet = st.session_state.velocity_regrid
52
+ elif st.session_state.isVelocityModifiedSound:
53
+ st.session_state.velocity_magnet = st.session_state.velocity_sensor
54
+ else:
55
+ st.session_state.velocity_magnet = st.session_state.velocity
56
+
57
+ # Reset Mask
58
+ if st.session_state.isProfileTest:
59
+ if st.session_state.isRegridCheck:
60
+ st.session_state.velocity_mask_default = np.copy(
61
+ st.session_state.profile_mask_regrid
62
+ )
63
+ else:
64
+ st.session_state.velocity_mask_default = np.copy(
65
+ st.session_state.profile_mask
66
+ )
67
+ elif st.session_state.isQCTest:
68
+ st.session_state.velocity_mask_default = np.copy(st.session_state.qc_mask)
69
+ elif st.session_state.isSensorTest:
70
+ st.session_state.velocity_mask_default = np.copy(st.session_state.sensor_mask)
71
+ else:
72
+ st.session_state.velocity_mask_default = np.copy(st.session_state.orig_mask)
73
+
74
+ mask = st.session_state.velocity_mask_default
75
+ st.session_state.velocity_mask_temp = np.copy(mask)
76
+ st.session_state.velocity_mask = np.copy(mask)
77
+ st.session_state.velocity_mask_cutoff = np.copy(mask)
78
+ st.session_state.velocity_mask_spike = np.copy(mask)
79
+ st.session_state.velocity_mask_flatline = np.copy(mask)
80
+
81
+
82
+ def hard_reset(option):
83
+ # Reset Global Test
84
+ st.session_state.isVelocityTest = False
85
+
86
+ # Reset Local Tests
87
+ st.session_state.isMagnetCheck = False
88
+ st.session_state.isDespikeCheck = False
89
+ st.session_state.isFlatlineCheck = False
90
+ st.session_state.isCutoffCheck = False
91
+
92
+ # Page return
93
+ st.session_state.isProfilePageReturn = False
94
+ if not st.session_state.isProfileTest:
95
+ st.session_state.isQCPageReturn = False
96
+ if not st.session_state.isQCTest:
97
+ st.session_state.isSensorPageReturn = False
98
+
99
+ # Velocity data reset
100
+ st.session_state.velocity_magnet = st.session_state.velocity
101
+
102
+ if option == "Sensor Test":
103
+ st.session_state.velocity_mask_default = np.copy(st.session_state.sensor_mask)
104
+ if st.session_state.isVelocityModifiedSound:
105
+ st.session_state.velocity_magnet = st.session_state.velocity_sensor
106
+ elif option == "QC Test":
107
+ st.session_state.velocity_mask_default = np.copy(st.session_state.qc_mask)
108
+ elif option == "Profile Test":
109
+ st.session_state.velocity_mask_default = np.copy(st.session_state.profile_mask)
110
+ if st.session_state.isRegridCheck:
111
+ st.session_state.velocity_magnet = st.session_state.velocity_regrid
112
+ else:
113
+ st.session_state.velocity_mask_default = np.copy(st.session_state.orig_mask)
114
+
115
+ st.session_state.velocity_mask = np.copy(st.session_state.velocity_mask_default)
116
+ st.session_state.velocity_mask_temp = np.copy(
117
+ st.session_state.velocity_mask_default
118
+ )
119
+ st.session_state.velocity_mask_cutoff = np.copy(
120
+ st.session_state.velocity_mask_default
121
+ )
122
+ st.session_state.velocity_mask_spike = np.copy(
123
+ st.session_state.velocity_mask_default
124
+ )
125
+ st.session_state.velocity_mask_flatline = np.copy(
126
+ st.session_state.velocity_mask_default
127
+ )
128
+
129
+
130
+ ####### Initialize Mask File ##############
131
+ if (
132
+ st.session_state.isProfileTest
133
+ or st.session_state.isQCTest
134
+ or st.session_state.isSensorTest
135
+ ):
136
+ st.write(":grey[Working on a saved mask file ...]")
137
+ if st.session_state.isVelocityPageReturn:
138
+ st.write(
139
+ ":orange[Warning: Velocity test already completed. Reset to change settings.]"
140
+ )
141
+ reset_selectbox = st.selectbox(
142
+ "Choose reset option",
143
+ ("Profile Test", "QC Test", "Sensor Test", "Default"),
144
+ index=None,
145
+ placeholder="Reset mask to ...",
146
+ )
147
+ if reset_selectbox == "Default":
148
+ st.write("Default mask file selected")
149
+ elif reset_selectbox == "Sensor Test":
150
+ st.write("Sensor Test mask file selected")
151
+ elif reset_selectbox == "QC Test":
152
+ st.write("QC Test mask file selected")
153
+ elif reset_selectbox == "Profile Test":
154
+ st.write("Profile Test mask file selected")
155
+
156
+ if reset_selectbox is not None:
157
+ hard_reset(reset_selectbox)
158
+
159
+ elif st.session_state.isFirstVelocityVisit:
160
+ reset_velocitytest()
161
+ st.session_state.isFirstVelocityVisit = False
162
+ else:
163
+ if st.session_state.isFirstVelocityVisit:
164
+ reset_velocitytest()
165
+ st.session_state.isFirstVelocityVisit = False
166
+ st.write(":grey[Creating a new mask file ...]")
167
+
168
+
169
+ # If data are not regrided use the default one
170
+ # if st.session_state.isGridSave:
171
+ # st.session_state.velocity_magnet = np.copy(st.session_state.velocity_regrid)
172
+ # else:
173
+ # st.session_state.velocity_magnet = np.copy(st.session_state.velocity)
174
+
175
+ velocity = st.session_state.velocity_magnet
176
+
177
+ ensembles = st.session_state.head.ensembles
178
+ cells = flobj.field()["Cells"]
179
+ x = np.arange(0, ensembles, 1)
180
+ y = np.arange(0, cells, 1)
181
+
182
+
183
+ ########### Introduction ##########
184
+ st.header("Velocity Test", divider="orange")
185
+
186
+ st.write(
187
+ """
188
+ The processing in this page apply only to the velocity data.
189
+ """
190
+ )
191
+ tab1, tab2, tab3, tab4, tab5 = st.tabs(
192
+ [
193
+ "Magnetic Declination",
194
+ "Velocity Cutoffs",
195
+ "Despike Data",
196
+ "Remove Flatline",
197
+ "Save & Reset Data",
198
+ ]
199
+ )
200
+
201
+ ############ Magnetic Declination ##############
202
+ # Commenting the wmm2020 c based model if needed can be implemented.
203
+
204
+ # * The magnetic declination is obtained from World Magnetic Model 2020 (WMM2020).
205
+ # The python wrapper module `wmm2020` is available from this [Link](https://github.com/space-physics/wmm2020).
206
+
207
+ with tab1:
208
+ st.header("Magnetic Declination", divider="blue")
209
+ st.write(
210
+ """
211
+ * The pygeomag method uses a python library [pygeomag](https://github.com/boxpet/pygeomag.git) for calculating the magnetic declination.
212
+ * It can work from 2010 till date.
213
+ * The API method utilizes the online magnetic declination service provided by the National Geophysical Data Center (NGDC)
214
+ of the National Oceanic and Atmospheric Administration (NOAA) to calculate the magnetic declination. The service is available at this [link](https://www.ngdc.noaa.gov/geomag/calculators/magcalc.shtml#declination).
215
+ Internet connection is necessary for this method to work.
216
+ * According to the year, different models are used for calculating magnetic declination.
217
+ * From 2025 till date, WMM2025 (World Magnetic Model)
218
+ * From 2019 to 2024 IGRF (International Geomagnetic Reference Field)
219
+ * From 2000 to 2018 EMM (Enhanced Magnetic Model)
220
+ * Before 1999 IGRF
221
+ * In the manual method, the user can directly enter the magnetic declination.
222
+ If the magnetic declination is reset, re-run the remaining tests again.
223
+ """
224
+ )
225
+
226
+ # Selecting the method to calculate magnetic declination.
227
+ # method = st.radio("Select a method", ("WMM2020", "API", "Manual"), horizontal=True)
228
+ method = st.radio("Select a method", ("pygeomag", "API", "Manual"), horizontal=True)
229
+ # method = method - 1
230
+ st.session_state.method = method
231
+
232
+ if "isMagnetButton" not in st.session_state:
233
+ st.session_state.isMagnetButton = False
234
+
235
+ # Track button clicks
236
+ if "isButtonClicked" not in st.session_state:
237
+ st.session_state.isButtonClicked = False
238
+
239
+ def toggle_btns():
240
+ st.session_state.isMagnetButton = not st.session_state.isMagnetButton
241
+ st.session_state.isButtonClicked = not st.session_state.isButtonClicked
242
+
243
+ with st.form(key="magnet_form"):
244
+ if st.session_state.method == "pygeomag":
245
+ # st.session_state.isMagnet = False
246
+ lat = st.number_input("Latitude", -90.0, 90.0, 0.0, step=1.0)
247
+ lon = st.number_input("Longitude", 0.0, 360.0, 0.1, step=1.0, format="%.4f")
248
+ depth = st.number_input("Depth", 0, 1000, 0, step=1)
249
+ year = st.number_input("Year", 2010, 2030, 2025, 1)
250
+
251
+ elif st.session_state.method == "API":
252
+ # st.session_state.isMagnet = False
253
+ lat = st.number_input("Latitude", -90.0, 90.0, 0.0, step=1.0)
254
+ lon = st.number_input("Longitude", 0.0, 360.0, 0.1, step=1.0, format="%.4f")
255
+ year = st.number_input("Year", 1950, 2030, 2025, 1)
256
+ else:
257
+ # st.session_state.isMagnet = False
258
+ mag = [[st.number_input("Declination", -180.0, 180.0, 0.0, 0.1)]]
259
+
260
+ if st.session_state.method == "Manual":
261
+ button_name = "Accept"
262
+ else:
263
+ button_name = "Compute"
264
+
265
+ if st.form_submit_button(
266
+ button_name, on_click=toggle_btns, disabled=st.session_state.isMagnetButton
267
+ ):
268
+ if st.session_state.method == "pygeomag":
269
+ mag = magdec(lat, lon, depth, year)
270
+ st.session_state.velocity_magnet = velocity_modifier(velocity, mag)
271
+ st.session_state.lat = lat
272
+ st.session_state.lon = lon
273
+ st.session_state.year = year
274
+ st.session_state.magnetic_dec_depth = depth
275
+ st.session_state.angle = np.round(mag[0][0], decimals=3)
276
+ st.session_state.isMagnetCheck = True
277
+ st.session_state.isButtonClicked = True
278
+
279
+ if st.session_state.method == "API":
280
+ try:
281
+ mag = wmm2020api(lat, lon, year)
282
+ st.session_state.velocity_magnet = velocity_modifier(velocity, mag)
283
+ st.session_state.lat = lat
284
+ st.session_state.lon = lon
285
+ st.session_state.year = year
286
+ # st.session_state.angle = np.trunc(mag[0][0])
287
+ st.session_state.angle = np.round(mag[0][0], decimals=3)
288
+ st.session_state.isMagnetCheck = True
289
+ st.session_state.isButtonClicked = True
290
+ except:
291
+ st.write(
292
+ ":red[Connection error! please check the internet or use manual method]"
293
+ )
294
+ else:
295
+ st.session_state.velocity_magnet = velocity_modifier(velocity, mag)
296
+ st.session_state.angle = np.round(mag[0][0], decimals=3)
297
+ st.session_state.isMagnetCheck = True
298
+ st.session_state.isButtonClicked = True
299
+
300
+ if st.session_state.isMagnetCheck:
301
+ st.write(f"Magnetic declination: {st.session_state.angle}\u00b0")
302
+ st.write(":green[Magnetic declination correction applied to velocities]")
303
+
304
+ magnet_button_reset = st.button(
305
+ "Reset Magnetic Declination",
306
+ on_click=toggle_btns,
307
+ disabled=not st.session_state.isMagnetButton,
308
+ )
309
+ if magnet_button_reset:
310
+ st.session_state.velocity_magnet = np.copy(velocity)
311
+ st.session_state.isMagnetCheck = False
312
+ st.session_state.isButtonClicked = False
313
+
314
+ with tab2:
315
+ ############# Velocity Cutoffs #################
316
+ st.header("Velocity Cutoffs", divider="blue")
317
+ st.write(
318
+ """
319
+ Drop velocities whose magnitude is larger than the threshold.
320
+ """
321
+ )
322
+ with st.form(key="cutbin_form"):
323
+ maxuvel = st.number_input(
324
+ "Maximum Zonal Velocity Cutoff (cm/s)", 0, 2000, 250, 1
325
+ )
326
+ maxvvel = st.number_input(
327
+ "Maximum Meridional Velocity Cutoff (cm/s)", 0, 2000, 250, 1
328
+ )
329
+ maxwvel = st.number_input(
330
+ "Maximum Vertical Velocity Cutoff (cm/s)", 0, 2000, 15, 1
331
+ )
332
+ submit_cutoff = st.form_submit_button(label="Submit")
333
+
334
+ if submit_cutoff:
335
+ velocity = st.session_state.velocity_magnet
336
+ st.session_state.maxuvel = maxuvel
337
+ st.session_state.maxvvel = maxvvel
338
+ st.session_state.maxwvel = maxwvel
339
+
340
+ st.session_state.velocity_mask_cutoff = velocity_cutoff(
341
+ velocity[0, :, :], st.session_state.velocity_mask_temp, cutoff=maxuvel
342
+ )
343
+ st.session_state.velocity_mask_cutoff = velocity_cutoff(
344
+ velocity[1, :, :], st.session_state.velocity_mask_temp, cutoff=maxvvel
345
+ )
346
+ st.session_state.velocity_mask_cutoff = velocity_cutoff(
347
+ velocity[2, :, :], st.session_state.velocity_mask_temp, cutoff=maxwvel
348
+ )
349
+ st.session_state.velocity_mask_temp = np.copy(
350
+ st.session_state.velocity_mask_cutoff
351
+ )
352
+ st.session_state.isCutoffCheck = True
353
+
354
+ if st.session_state.isCutoffCheck:
355
+ st.success("Cutoff Applied")
356
+ a = {
357
+ "Max. Zonal Velocity": maxuvel,
358
+ "Max. Meridional Velocity": maxvvel,
359
+ "Max. Vertical Velocity": maxwvel,
360
+ }
361
+ st.write(a)
362
+
363
+ def reset_button_cutoff():
364
+ st.session_state.isCutoffCheck = False
365
+ st.session_state.velocity_mask_temp = np.copy(
366
+ st.session_state.velocity_mask_default
367
+ )
368
+ st.session_state.velocity_mask_cutoff = np.copy(
369
+ st.session_state.velocity_mask_default
370
+ )
371
+
372
+ reset_cutoff = st.button("Reset Cutoff to default", on_click=reset_button_cutoff)
373
+ if reset_cutoff:
374
+ st.info("Cutoff Test is reset")
375
+
376
+
377
+ with tab3:
378
+ ############## DESPIKE DATA #################
379
+ st.header("Despike Data", divider="blue")
380
+ st.write("""A rolling median filter is applied to remove spikes from the data.
381
+ The kernal size determines the number of ensembles (time interval) for the filter window.
382
+ The standard deviation specifies the maximum allowable deviation to remove the spike.""")
383
+
384
+ # time_interval = pd.Timedelta(st.session_state.date[-1] - st.session_state.date[0]).seconds/(3600*st.session_state.head.ensembles)
385
+
386
+ st.write("Time interval: ", st.session_state.date[1] - st.session_state.date[0])
387
+
388
+ despike_kernal = st.number_input(
389
+ "Enter Despike Kernal Size for Median Filter",
390
+ 0,
391
+ st.session_state.head.ensembles,
392
+ 5,
393
+ 1,
394
+ )
395
+
396
+ despike_cutoff = st.number_input(
397
+ "Standard Deviation Cutoff for Spike Removal", 0.1, 10.0, 3.0, 0.1
398
+ )
399
+ despike_button = st.button("Despike")
400
+ if despike_button:
401
+ st.session_state.despike_kernal = despike_kernal
402
+ st.session_state.despike_cutoff = despike_cutoff
403
+
404
+ st.session_state.velocity_mask_despike = despike(
405
+ velocity[0, :, :],
406
+ st.session_state.velocity_mask_temp,
407
+ kernal_size=despike_kernal,
408
+ cutoff=despike_cutoff,
409
+ )
410
+ st.session_state.velocity_mask_despike = despike(
411
+ velocity[1, :, :],
412
+ st.session_state.velocity_mask_temp,
413
+ kernal_size=despike_kernal,
414
+ cutoff=despike_cutoff,
415
+ )
416
+
417
+ # Reset the temporary mask
418
+ st.session_state.velocity_mask_temp = np.copy(
419
+ st.session_state.velocity_mask_despike
420
+ )
421
+ st.session_state.isDespikeCheck = True
422
+
423
+ if st.session_state.isDespikeCheck:
424
+ st.success("Data Despiked")
425
+ b = {
426
+ "Kernal Size": despike_kernal,
427
+ "Despike Cutoff": despike_cutoff,
428
+ }
429
+ st.write(b)
430
+
431
+ def reset_button_despike():
432
+ st.session_state.isDespikeCheck = False
433
+ if st.session_state.isCutoffCheck:
434
+ st.session_state.velocity_mask_temp = np.copy(
435
+ st.session_state.velocity_mask_cutoff
436
+ )
437
+ st.session_state.velocity_mask_despike = np.copy(
438
+ st.session_state.velocity_mask_cutoff
439
+ )
440
+ else:
441
+ st.session_state.velocity_mask_temp = np.copy(
442
+ st.session_state.velocity_mask_default
443
+ )
444
+ st.session_state.velocity_mask_despike = np.copy(
445
+ st.session_state.velocity_mask_default
446
+ )
447
+
448
+ reset_despike = st.button("Reset Despike to default", on_click=reset_button_despike)
449
+ if reset_despike:
450
+ st.info("Despike Test is reset")
451
+
452
+ with tab4:
453
+ st.header("Remove Flatline", divider="blue")
454
+
455
+ st.write("""
456
+ Flatline removal detects segments of data where values remain constant over
457
+ a specified interval. The kernel size defines the number of consecutive
458
+ ensembles (time intervals) considered in the check, while the threshold sets
459
+ the maximum allowable variation.
460
+ """)
461
+
462
+ st.write("Time interval: ", st.session_state.date[1] - st.session_state.date[0])
463
+
464
+ flatline_kernal = st.number_input("Enter Flatline Kernal Size", 0, 100, 13, 1)
465
+ flatline_cutoff = st.number_input("Enter Flatline deviation (mm/s)", 0, 100, 1, 1)
466
+
467
+ flatline_button = st.button("Remove Flatline")
468
+
469
+ if flatline_button:
470
+ st.session_state.flatline_kernal = flatline_kernal
471
+ st.session_state.flatline_cutoff = flatline_cutoff
472
+
473
+ st.session_state.velocity_mask_flatline = flatline(
474
+ velocity[0, :, :],
475
+ st.session_state.velocity_mask_temp,
476
+ kernal_size=flatline_kernal,
477
+ cutoff=flatline_cutoff,
478
+ )
479
+ st.session_state.velocity_mask_flatline = flatline(
480
+ velocity[1, :, :],
481
+ st.session_state.velocity_mask_temp,
482
+ kernal_size=flatline_kernal,
483
+ cutoff=flatline_cutoff,
484
+ )
485
+ st.session_state.velocity_mask_flatline = flatline(
486
+ velocity[2, :, :],
487
+ st.session_state.velocity_mask_temp,
488
+ kernal_size=flatline_kernal,
489
+ cutoff=flatline_cutoff,
490
+ )
491
+ # Modify the temporary mask file
492
+ st.session_state.velocity_mask_temp = np.copy(
493
+ st.session_state.velocity_mask_flatline
494
+ )
495
+ st.session_state.isFlatlineCheck = True
496
+
497
+ if st.session_state.isFlatlineCheck:
498
+ st.success("Flatline Removed")
499
+ b = {
500
+ "Kernal Size": flatline_kernal,
501
+ "Flatline Cutoff": flatline_cutoff,
502
+ }
503
+ st.write(b)
504
+
505
+ def reset_button_flatline():
506
+ st.session_state.isFlatlineCheck = False
507
+ if st.sesion_state.isDespikeCheck:
508
+ st.session_state.velocity_mask_temp = np.copy(
509
+ st.session_state.velocity_mask_despike
510
+ )
511
+ st.session_state.velocity_mask_flatline = np.copy(
512
+ st.session_state.velocity_mask_despike
513
+ )
514
+ elif st.session_state.isCutoffCheck:
515
+ st.session_state.velocity_mask_temp = np.copy(
516
+ st.session_state.velocity_mask_cutoff
517
+ )
518
+ st.session_state.velocity_mask_flatline = np.copy(
519
+ st.session_state.velocity_mask_cutoff
520
+ )
521
+ else:
522
+ st.session_state.velocity_mask_temp = np.copy(
523
+ st.session_state.velocity_mask_default
524
+ )
525
+ st.session_state.velocity_mask_flatline = np.copy(
526
+ st.session_state.velocity_mask_default
527
+ )
528
+
529
+ reset_despike = st.button(
530
+ "Reset Flatline to default", on_click=reset_button_flatline
531
+ )
532
+ if reset_despike:
533
+ st.info("Flatline Test is reset")
534
+
535
+
536
+ ##################### SAVE DATA ###################
537
+ with tab5:
538
+ st.header("Save & Reset Data", divider="blue")
539
+
540
+ def save_velocitytest():
541
+ st.session_state.isVelocityTest = True
542
+ st.session_state.isFirstVelocityVisit = False
543
+ st.session_state.velocity_mask = st.session_state.velocity_mask_temp
544
+
545
+ st.session_state.isSensorPageReturn = True
546
+ st.session_state.isQCPageReturn = True
547
+ st.session_state.isProfilePageReturn = True
548
+
549
+ col1, col2 = st.columns([1, 1])
550
+ with col1:
551
+ save_button = st.button(label="Save Data", on_click=save_velocitytest)
552
+ if save_button:
553
+ st.write(":green[Mask data saved]")
554
+ # Status Summary Table
555
+ status_summary = pd.DataFrame(
556
+ [
557
+ [
558
+ "Magnetic Declination",
559
+ "True" if st.session_state.isButtonClicked else "False",
560
+ ],
561
+ [
562
+ "Velocity Cutoffs",
563
+ "True" if st.session_state.isCutoffCheck else "False",
564
+ ],
565
+ [
566
+ "Despike Data",
567
+ "True" if st.session_state.isDespikeCheck else "False",
568
+ ],
569
+ [
570
+ "Remove Flatline",
571
+ "True" if st.session_state.isFlatlineCheck else "False",
572
+ ],
573
+ ],
574
+ columns=["Test", "Status"],
575
+ )
576
+
577
+ # Define a mapping function for styling
578
+ def status_color_map(value):
579
+ if value == "True":
580
+ return "background-color: green; color: white"
581
+ elif value == "False":
582
+ return "background-color: red; color: white"
583
+ else:
584
+ return ""
585
+
586
+ # Apply styles using Styler.apply
587
+ styled_table = status_summary.style.set_properties(
588
+ **{"text-align": "center"}
589
+ )
590
+ styled_table = styled_table.map(status_color_map, subset=["Status"])
591
+
592
+ # Display the styled table
593
+ st.write(styled_table.to_html(), unsafe_allow_html=True)
594
+ else:
595
+ st.write(":red[Data not saved]")
596
+
597
+ with col2:
598
+ st.button(label="Reset Data", on_click=reset_velocitytest)
599
+ st.info("Velocity test reset to default")