ssscoring.ssscoremultiple
Experimental
Process a group of jumps uploaded from a file uploader.
1# See: https://github.com/pr3d4t0r/SSScoring/blob/master/LICENSE.txtl 2 3""" 4## Experimental 5 6Process a group of jumps uploaded from a file uploader. 7""" 8 9from ssscoring import __VERSION__ 10from ssscoring.appcommon import displayJumpDataIn 11from ssscoring.appcommon import displayTrackOnMap 12from ssscoring.appcommon import fetchInstructionsHTML 13from ssscoring.appcommon import fetchResource 14from ssscoring.appcommon import initFileUploaderState 15from ssscoring.appcommon import interpretJumpResult 16from ssscoring.appcommon import plotJumpResult 17from ssscoring.appcommon import setSideBarAndMain 18from ssscoring.calc import aggregateResults 19from ssscoring.calc import collateAnglesByTimeFromExit 20from ssscoring.calc import dropNonSkydiveDataFrom 21from ssscoring.calc import processAllJumpFiles 22from ssscoring.calc import totalResultsFrom 23from ssscoring.constants import DEFAULT_PLOT_INCREMENT 24from ssscoring.constants import DEFAULT_PLOT_MAX_V_SCALE 25from ssscoring.constants import M_2_FT 26from ssscoring.constants import SPEED_ACCURACY_THRESHOLD 27from ssscoring.datatypes import JumpStatus 28from ssscoring.datatypes import PerformanceWindow 29from ssscoring.mapview import multipleSpeedJumpsTrajectories 30from ssscoring.mapview import speedJumpTrajectory 31from ssscoring.notebook import SPEED_COLORS 32from ssscoring.notebook import graphForwardDisplacement 33from ssscoring.notebook import graphGroundTrack 34from ssscoring.notebook import graphJumpResult 35from ssscoring.notebook import initializeGroundTrackPlot 36from ssscoring.notebook import initializePlot 37# TODO: Remove this if present after 20260531 38# from streamlit_bokeh import streamlit_bokeh 39 40import pandas as pd 41import streamlit as st 42 43 44# +++ implementation +++ 45 46def _selectDZState(*args, **kwargs): 47 if st.session_state.elevation: 48 st.session_state.uploaderKey += 1 49 st.session_state.trackFiles = None 50 51 52def _styleShowMaxIn(scores: pd.Series) -> pd.DataFrame: 53 return [ 54 'background-color: mediumseagreen' if v == scores.max() else \ 55 '' for v in scores ] 56 57 58def _displayAllJumpDataIn(data: pd.DataFrame): 59 if data is not None: 60 columns = [ 'plotTime' ] + [ column for column in data.columns if column != 'plotTime' and column != 'timeUnix' ] 61 st.html('<h3>All jump data from exit</h3>') 62 st.dataframe(data, 63 column_order=columns, 64 column_config={ 65 'plotTime': st.column_config.NumberColumn(format='%.02f'), 66 'speedAngle': st.column_config.NumberColumn(format='%.02f'), 67 'speedAccuracyISC': st.column_config.NumberColumn(format='%.02f'), 68 }, 69 hide_index=True) 70 71 72def _displayScoresIn(scoresData: dict): 73 if scoresData is not None: 74 st.html('<h3>All 3-sec sliding window scores</h3>') 75 data = pd.DataFrame.from_dict({ 'time': scoresData.values(), 'score': scoresData.keys(), }) 76 data.time = data.time.apply(lambda x: '%.2f' % x) 77 st.dataframe(data, hide_index=True) 78 79 80def _displayBadRowsISCAccuracyExceeded(data: pd.DataFrame, window: PerformanceWindow): 81 badRows = data[data.speedAccuracyISC >= SPEED_ACCURACY_THRESHOLD] 82 badRows = dropNonSkydiveDataFrom(badRows) 83 times = pd.to_datetime(badRows.timeUnix, unit='s').dt.strftime('%Y-%m-%d %H:%M:%S.%f').str[:-4] 84 badRows.insert(0, 'time', times) 85 badRows.drop(columns = [ 86 'timeUnix', 87 'altitudeMSL', 88 'altitudeMSLFt', 89 'speedAccuracy', 90 'hMetersPerSecond', 91 'hKMh', 92 'speedAngle', 93 'latitude', 94 'longitude', 95 'verticalAccuracy', ], inplace=True) 96 st.html('<h3>Performance window:<br>start = %.2f m (%.2f ft)<br>end = %.2f m (%.2f ft)<br>validation start = %.2f m (%.2f ft)</h3>' % \ 97 (window.start, M_2_FT*window.start, window.end, M_2_FT*window.end, window.validationStart, M_2_FT*window.validationStart)) 98 st.html('<h3>%d track rows where the ISC speed accuracy threshold was exceeded during the speed run:</h3>' % len(badRows)) 99 st.dataframe(badRows, hide_index=True) 100 101 102 workData = data.copy() 103 workData = dropNonSkydiveDataFrom(workData) 104 times = pd.to_datetime(workData.timeUnix, unit='s').dt.strftime('%Y-%m-%d %H:%M:%S.%f').str[:-4] 105 workData.insert(0, 'time', times) 106 st.html('<h3>Full speed run data (%d rows)</h3>' % len(workData)) 107 st.dataframe(workData, hide_index=True) 108 109 110def _styleShowMinMaxIn(scores: pd.Series) -> pd.DataFrame: 111 return [ 112 'background-color: green' if v == scores.max() else \ 113 'background-color: orangered' if v == scores.min() else \ 114 '' for v in scores ] 115 116 117def _displayJumpsInSet(aggregate: pd.DataFrame): 118 with st.expander('**Jumps in this set**', expanded=True, icon=':material/dataset:'): 119 displayAggregate = aggregate.style.apply(_styleShowMinMaxIn, subset=[ 'score', ]).apply(_styleShowMaxIn, subset=[ 'maxSpeed', ]).format(precision=2) 120 st.dataframe(displayAggregate) 121 122 123def _displaySpeedSummary(aggregate: pd.DataFrame, 124 allJumpsPlot): 125 with st.expander('Speed summary', expanded=True): 126 summary = totalResultsFrom(aggregate) 127 128 st.dataframe( 129 summary.style.format("{:.2f}"), 130 hide_index=True 131 ) 132 st.plotly_chart(allJumpsPlot, width='stretch') 133 134 135def _displaySpeedAngles(jumpResults: dict): 136 with st.expander('**Speed angles**', icon=':material/arrow_back_ios_new:'): 137 angles = collateAnglesByTimeFromExit(jumpResults).style.format(precision=1) 138 st.dataframe(angles) 139 140 141def _displayAllTracksOnMap(jumpResults: dict): 142 with st.expander('**All jumps trajectories**', expanded=True): 143 displayTrackOnMap(multipleSpeedJumpsTrajectories(jumpResults)) 144 145 146def _maxSpeedScaleFrom(jumpResults: dict) -> float: 147 maxScore = max(result.score if result.score != None else 0 for result in jumpResults.values()) 148 try: 149 return DEFAULT_PLOT_MAX_V_SCALE if maxScore <= DEFAULT_PLOT_MAX_V_SCALE else maxScore + DEFAULT_PLOT_INCREMENT 150 except TypeError: 151 return DEFAULT_PLOT_MAX_V_SCALE 152 153 154def main(): 155 st.set_page_config( 156 layout = 'wide', 157 page_title='SSScore %s' % __VERSION__, 158 ) 159 initFileUploaderState('trackFiles') 160 setSideBarAndMain('🔢', False, _selectDZState) 161 162 if st.session_state.trackFiles: 163 jumpResults = processAllJumpFiles(st.session_state.trackFiles, altitudeDZMeters=st.session_state.elevation) 164 allJumpsPlot = initializePlot('All jumps', backgroundColorName='#2c2c2c', yMax=_maxSpeedScaleFrom(jumpResults)) 165 mixColor = 0 166 jumpResultsSubset = dict() 167 resultTags = sorted(list(jumpResults.keys()), reverse=True) 168 tabs = st.tabs(['Totals']+resultTags) 169 index = 1 170 jumpStatus = JumpStatus.OK 171 for tag in resultTags: 172 jumpResult = jumpResults[tag] 173 mixColor = (mixColor+1)%len(SPEED_COLORS) 174 with tabs[index]: 175 jumpStatusInfo,\ 176 scoringInfo,\ 177 badJumpLegend,\ 178 jumpStatus = interpretJumpResult(tag, jumpResult, st.session_state.processBadJump) 179 if jumpStatus != JumpStatus.OK: 180 st.toast('#### %s - %s' % (tag, str(jumpStatus)), icon='⚠️') 181 if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK: 182 jumpResultsSubset[tag] = jumpResult 183 st.html('<h3>'+jumpStatusInfo+scoringInfo+(str(badJumpLegend) if badJumpLegend else '')) 184 st.html("<br>If this was NOT a warm-up file, it's probably an ISC altitude violation; please report to Eugene/pr3d4t0r and attach the TRACK.CSV file</h3>" if jumpStatus in [ JumpStatus.WARM_UP_FILE, ] else '</h3>') 185 if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK: 186 displayJumpDataIn(jumpResult.table) 187 with st.expander('Max score = crosshairs. Max speed = diamond. V-accel = exponential mean average over 4 seconds.', expanded=True): 188 # st.write('Max score = crosshairs. Max speed = diamond. V-accel = exponential mean average over 4 seconds.') 189 plotJumpResult(tag, jumpResult) 190 if jumpResult.data is not None: 191 with st.expander('**Horizontal displacement** - optimal ≦ 500 m from exit', expanded=True): 192 colGroundTrack, colForwardDisplacement = st.columns(2) 193 with colGroundTrack: 194 groundTrackFigure = initializeGroundTrackPlot(tag, backgroundColorName='#2c2c2c') 195 graphGroundTrack(groundTrackFigure, jumpResult) 196 st.plotly_chart(groundTrackFigure, width='stretch') 197 with colForwardDisplacement: 198 displacementFigure = initializePlot(tag, yLabel='forward (m)', backgroundColorName='#2c2c2c', height=450) 199 graphForwardDisplacement(displacementFigure, jumpResult) 200 st.plotly_chart(displacementFigure, width='stretch') 201 graphJumpResult( 202 allJumpsPlot, 203 jumpResult, 204 lineColor=SPEED_COLORS[mixColor], 205 legend='%s = %.2f' % (tag, jumpResult.score if jumpResult.score else -1.0), 206 showIt=False 207 ) 208 with st.expander('Speed run / jump run', expanded=True): 209 st.session_state.displayScorePoint = st.toggle('Display max score / max speed point', value=True, help='Show the fastest speed or score point along the flight path', key=tag) 210 displayTrackOnMap(speedJumpTrajectory(jumpResult, st.session_state.displayScorePoint), st.session_state.displayScorePoint, showJumpRunLegend=True) 211 _displayAllJumpDataIn(jumpResult.data) 212 _displayScoresIn(jumpResult.scores) 213 elif jumpStatus == JumpStatus.SPEED_ACCURACY_EXCEEDS_LIMIT: 214 _displayBadRowsISCAccuracyExceeded(jumpResult.data, jumpResult.window) 215 index += 1 216 with tabs[0]: 217 if len(resultTags): 218 if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK: 219 aggregate = aggregateResults(jumpResultsSubset) 220 if len(aggregate) > 0: 221 _displayJumpsInSet(aggregate) 222 _displaySpeedAngles(jumpResults) 223 _displaySpeedSummary(aggregate, allJumpsPlot) 224 _displayAllTracksOnMap(jumpResults) 225 else: 226 st.write(fetchInstructionsHTML(), unsafe_allow_html=True) 227 228 229if '__main__' == __name__: 230 main()
def
main():
155def main(): 156 st.set_page_config( 157 layout = 'wide', 158 page_title='SSScore %s' % __VERSION__, 159 ) 160 initFileUploaderState('trackFiles') 161 setSideBarAndMain('🔢', False, _selectDZState) 162 163 if st.session_state.trackFiles: 164 jumpResults = processAllJumpFiles(st.session_state.trackFiles, altitudeDZMeters=st.session_state.elevation) 165 allJumpsPlot = initializePlot('All jumps', backgroundColorName='#2c2c2c', yMax=_maxSpeedScaleFrom(jumpResults)) 166 mixColor = 0 167 jumpResultsSubset = dict() 168 resultTags = sorted(list(jumpResults.keys()), reverse=True) 169 tabs = st.tabs(['Totals']+resultTags) 170 index = 1 171 jumpStatus = JumpStatus.OK 172 for tag in resultTags: 173 jumpResult = jumpResults[tag] 174 mixColor = (mixColor+1)%len(SPEED_COLORS) 175 with tabs[index]: 176 jumpStatusInfo,\ 177 scoringInfo,\ 178 badJumpLegend,\ 179 jumpStatus = interpretJumpResult(tag, jumpResult, st.session_state.processBadJump) 180 if jumpStatus != JumpStatus.OK: 181 st.toast('#### %s - %s' % (tag, str(jumpStatus)), icon='⚠️') 182 if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK: 183 jumpResultsSubset[tag] = jumpResult 184 st.html('<h3>'+jumpStatusInfo+scoringInfo+(str(badJumpLegend) if badJumpLegend else '')) 185 st.html("<br>If this was NOT a warm-up file, it's probably an ISC altitude violation; please report to Eugene/pr3d4t0r and attach the TRACK.CSV file</h3>" if jumpStatus in [ JumpStatus.WARM_UP_FILE, ] else '</h3>') 186 if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK: 187 displayJumpDataIn(jumpResult.table) 188 with st.expander('Max score = crosshairs. Max speed = diamond. V-accel = exponential mean average over 4 seconds.', expanded=True): 189 # st.write('Max score = crosshairs. Max speed = diamond. V-accel = exponential mean average over 4 seconds.') 190 plotJumpResult(tag, jumpResult) 191 if jumpResult.data is not None: 192 with st.expander('**Horizontal displacement** - optimal ≦ 500 m from exit', expanded=True): 193 colGroundTrack, colForwardDisplacement = st.columns(2) 194 with colGroundTrack: 195 groundTrackFigure = initializeGroundTrackPlot(tag, backgroundColorName='#2c2c2c') 196 graphGroundTrack(groundTrackFigure, jumpResult) 197 st.plotly_chart(groundTrackFigure, width='stretch') 198 with colForwardDisplacement: 199 displacementFigure = initializePlot(tag, yLabel='forward (m)', backgroundColorName='#2c2c2c', height=450) 200 graphForwardDisplacement(displacementFigure, jumpResult) 201 st.plotly_chart(displacementFigure, width='stretch') 202 graphJumpResult( 203 allJumpsPlot, 204 jumpResult, 205 lineColor=SPEED_COLORS[mixColor], 206 legend='%s = %.2f' % (tag, jumpResult.score if jumpResult.score else -1.0), 207 showIt=False 208 ) 209 with st.expander('Speed run / jump run', expanded=True): 210 st.session_state.displayScorePoint = st.toggle('Display max score / max speed point', value=True, help='Show the fastest speed or score point along the flight path', key=tag) 211 displayTrackOnMap(speedJumpTrajectory(jumpResult, st.session_state.displayScorePoint), st.session_state.displayScorePoint, showJumpRunLegend=True) 212 _displayAllJumpDataIn(jumpResult.data) 213 _displayScoresIn(jumpResult.scores) 214 elif jumpStatus == JumpStatus.SPEED_ACCURACY_EXCEEDS_LIMIT: 215 _displayBadRowsISCAccuracyExceeded(jumpResult.data, jumpResult.window) 216 index += 1 217 with tabs[0]: 218 if len(resultTags): 219 if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK: 220 aggregate = aggregateResults(jumpResultsSubset) 221 if len(aggregate) > 0: 222 _displayJumpsInSet(aggregate) 223 _displaySpeedAngles(jumpResults) 224 _displaySpeedSummary(aggregate, allJumpsPlot) 225 _displayAllTracksOnMap(jumpResults) 226 else: 227 st.write(fetchInstructionsHTML(), unsafe_allow_html=True)