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 graphForwardDisplacement 32from ssscoring.notebook import graphGroundTrack 33from ssscoring.notebook import graphJumpResult 34from ssscoring.notebook import initializeGroundTrackPlot 35from ssscoring.notebook import initializePlot 36from ssscoring.notebook import resolveJumpColors 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, tagColors: dict): 142 with st.expander('**All jumps trajectories**', expanded=True): 143 displayTrackOnMap(multipleSpeedJumpsTrajectories(jumpResults, tagColors)) 144 145 146_FILE_ERROR_LABELS = { 147 JumpStatus.UNSUPPORTED_PLD_FORMAT: 'Unsupported file format — not a FlySight v1, v2, or Deep & Steep Insight device', 148 JumpStatus.INVALID_SPEED_FILE: 'Invalid or corrupted file', 149 JumpStatus.WARM_UP_FILE: 'Warm-up file or SMD battery failure', 150 JumpStatus.SPEED_ACCURACY_EXCEEDS_LIMIT: 'Speed accuracy exceeds ISC threshold — RE-JUMP required', 151} 152 153 154def _displayFileErrorsIn(jumpResults: dict, jumpResultsSubset: dict): 155 errorFiles = {tag: result for tag, result in jumpResults.items() if tag not in jumpResultsSubset} 156 if not errorFiles: 157 return 158 with st.expander('**Files not included in scoring**', expanded=True, icon=':material/warning:'): 159 for tag, result in errorFiles.items(): 160 label = _FILE_ERROR_LABELS.get(result.status, 'Did not meet scoring criteria') 161 st.html('<p>⚠ <b>%s</b> — <span style="color: red">%s</span></p>' % (tag, label)) 162 163 164def _maxSpeedScaleFrom(jumpResults: dict) -> float: 165 maxScore = max(result.score if result.score != None else 0 for result in jumpResults.values()) 166 try: 167 return DEFAULT_PLOT_MAX_V_SCALE if maxScore <= DEFAULT_PLOT_MAX_V_SCALE else maxScore + DEFAULT_PLOT_INCREMENT 168 except TypeError: 169 return DEFAULT_PLOT_MAX_V_SCALE 170 171 172def main(): 173 st.set_page_config( 174 layout = 'wide', 175 page_title='SSScore %s' % __VERSION__, 176 ) 177 initFileUploaderState('trackFiles') 178 setSideBarAndMain('🔢', False, _selectDZState) 179 180 if st.session_state.trackFiles: 181 jumpResults = processAllJumpFiles(st.session_state.trackFiles, altitudeDZMeters=st.session_state.elevation) 182 allJumpsPlot = initializePlot('All jumps', backgroundColorName='#2c2c2c', yMax=_maxSpeedScaleFrom(jumpResults)) 183 jumpResultsSubset = dict() 184 resultTags = sorted(list(jumpResults.keys()), reverse=True) 185 tabs = st.tabs(['Totals']+resultTags) 186 index = 1 187 jumpStatus = JumpStatus.OK 188 for tag in resultTags: 189 jumpResult = jumpResults[tag] 190 with tabs[index]: 191 jumpStatusInfo,\ 192 scoringInfo,\ 193 badJumpLegend,\ 194 jumpStatus = interpretJumpResult(tag, jumpResult, st.session_state.processBadJump) 195 if jumpStatus != JumpStatus.OK: 196 st.toast('#### %s - %s' % (tag, str(jumpStatus)), icon='⚠️') 197 showJumpData = False 198 match jumpStatus: 199 case JumpStatus.OK: 200 showJumpData = True 201 case JumpStatus.UNSUPPORTED_PLD_FORMAT: 202 pass 203 case _ if st.session_state.processBadJump: 204 showJumpData = True 205 if showJumpData: 206 jumpResultsSubset[tag] = jumpResult 207 st.html('<h3>'+jumpStatusInfo+scoringInfo+(str(badJumpLegend) if badJumpLegend else '')) 208 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>') 209 if showJumpData: 210 displayJumpDataIn(jumpResult.table) 211 with st.expander('Max score = crosshairs. Max speed = diamond. V-accel = exponential mean average over 4 seconds.', expanded=True): 212 plotJumpResult(tag, jumpResult) 213 if jumpResult.data is not None: 214 with st.expander('**Horizontal displacement** - optimal ≦ 500 m from exit', expanded=True): 215 colGroundTrack, colForwardDisplacement = st.columns(2) 216 with colGroundTrack: 217 groundTrackFigure = initializeGroundTrackPlot(tag, backgroundColorName='#2c2c2c') 218 graphGroundTrack(groundTrackFigure, jumpResult) 219 st.plotly_chart(groundTrackFigure, width='stretch') 220 with colForwardDisplacement: 221 displacementFigure = initializePlot(tag, yLabel='forward (m)', backgroundColorName='#2c2c2c', height=450) 222 graphForwardDisplacement(displacementFigure, jumpResult) 223 st.plotly_chart(displacementFigure, width='stretch') 224 with st.expander('Speed run / jump run', expanded=True): 225 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) 226 displayTrackOnMap(speedJumpTrajectory(jumpResult, st.session_state.displayScorePoint), st.session_state.displayScorePoint, showJumpRunLegend=True) 227 _displayAllJumpDataIn(jumpResult.data) 228 _displayScoresIn(jumpResult.scores) 229 elif jumpStatus == JumpStatus.SPEED_ACCURACY_EXCEEDS_LIMIT: 230 _displayBadRowsISCAccuracyExceeded(jumpResult.data, jumpResult.window) 231 index += 1 232 with tabs[0]: 233 if len(resultTags): 234 _displayFileErrorsIn(jumpResults, jumpResultsSubset) 235 if jumpResultsSubset: 236 tagColors = resolveJumpColors(jumpResultsSubset) 237 for tag in sorted(jumpResultsSubset.keys(), reverse=True): 238 jumpResult = jumpResultsSubset[tag] 239 graphJumpResult( 240 allJumpsPlot, 241 jumpResult, 242 lineColor=tagColors[tag], 243 legend='%s = %.2f' % (tag, jumpResult.score if jumpResult.score else -1.0), 244 showIt=False 245 ) 246 aggregate = aggregateResults(jumpResultsSubset) 247 if len(aggregate) > 0: 248 _displayJumpsInSet(aggregate) 249 _displaySpeedAngles(jumpResults) 250 _displaySpeedSummary(aggregate, allJumpsPlot) 251 _displayAllTracksOnMap(jumpResults, tagColors) 252 else: 253 st.write(fetchInstructionsHTML(), unsafe_allow_html=True) 254 255 256if '__main__' == __name__: 257 main()
def
main():
173def main(): 174 st.set_page_config( 175 layout = 'wide', 176 page_title='SSScore %s' % __VERSION__, 177 ) 178 initFileUploaderState('trackFiles') 179 setSideBarAndMain('🔢', False, _selectDZState) 180 181 if st.session_state.trackFiles: 182 jumpResults = processAllJumpFiles(st.session_state.trackFiles, altitudeDZMeters=st.session_state.elevation) 183 allJumpsPlot = initializePlot('All jumps', backgroundColorName='#2c2c2c', yMax=_maxSpeedScaleFrom(jumpResults)) 184 jumpResultsSubset = dict() 185 resultTags = sorted(list(jumpResults.keys()), reverse=True) 186 tabs = st.tabs(['Totals']+resultTags) 187 index = 1 188 jumpStatus = JumpStatus.OK 189 for tag in resultTags: 190 jumpResult = jumpResults[tag] 191 with tabs[index]: 192 jumpStatusInfo,\ 193 scoringInfo,\ 194 badJumpLegend,\ 195 jumpStatus = interpretJumpResult(tag, jumpResult, st.session_state.processBadJump) 196 if jumpStatus != JumpStatus.OK: 197 st.toast('#### %s - %s' % (tag, str(jumpStatus)), icon='⚠️') 198 showJumpData = False 199 match jumpStatus: 200 case JumpStatus.OK: 201 showJumpData = True 202 case JumpStatus.UNSUPPORTED_PLD_FORMAT: 203 pass 204 case _ if st.session_state.processBadJump: 205 showJumpData = True 206 if showJumpData: 207 jumpResultsSubset[tag] = jumpResult 208 st.html('<h3>'+jumpStatusInfo+scoringInfo+(str(badJumpLegend) if badJumpLegend else '')) 209 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>') 210 if showJumpData: 211 displayJumpDataIn(jumpResult.table) 212 with st.expander('Max score = crosshairs. Max speed = diamond. V-accel = exponential mean average over 4 seconds.', expanded=True): 213 plotJumpResult(tag, jumpResult) 214 if jumpResult.data is not None: 215 with st.expander('**Horizontal displacement** - optimal ≦ 500 m from exit', expanded=True): 216 colGroundTrack, colForwardDisplacement = st.columns(2) 217 with colGroundTrack: 218 groundTrackFigure = initializeGroundTrackPlot(tag, backgroundColorName='#2c2c2c') 219 graphGroundTrack(groundTrackFigure, jumpResult) 220 st.plotly_chart(groundTrackFigure, width='stretch') 221 with colForwardDisplacement: 222 displacementFigure = initializePlot(tag, yLabel='forward (m)', backgroundColorName='#2c2c2c', height=450) 223 graphForwardDisplacement(displacementFigure, jumpResult) 224 st.plotly_chart(displacementFigure, width='stretch') 225 with st.expander('Speed run / jump run', expanded=True): 226 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) 227 displayTrackOnMap(speedJumpTrajectory(jumpResult, st.session_state.displayScorePoint), st.session_state.displayScorePoint, showJumpRunLegend=True) 228 _displayAllJumpDataIn(jumpResult.data) 229 _displayScoresIn(jumpResult.scores) 230 elif jumpStatus == JumpStatus.SPEED_ACCURACY_EXCEEDS_LIMIT: 231 _displayBadRowsISCAccuracyExceeded(jumpResult.data, jumpResult.window) 232 index += 1 233 with tabs[0]: 234 if len(resultTags): 235 _displayFileErrorsIn(jumpResults, jumpResultsSubset) 236 if jumpResultsSubset: 237 tagColors = resolveJumpColors(jumpResultsSubset) 238 for tag in sorted(jumpResultsSubset.keys(), reverse=True): 239 jumpResult = jumpResultsSubset[tag] 240 graphJumpResult( 241 allJumpsPlot, 242 jumpResult, 243 lineColor=tagColors[tag], 244 legend='%s = %.2f' % (tag, jumpResult.score if jumpResult.score else -1.0), 245 showIt=False 246 ) 247 aggregate = aggregateResults(jumpResultsSubset) 248 if len(aggregate) > 0: 249 _displayJumpsInSet(aggregate) 250 _displaySpeedAngles(jumpResults) 251 _displaySpeedSummary(aggregate, allJumpsPlot) 252 _displayAllTracksOnMap(jumpResults, tagColors) 253 else: 254 st.write(fetchInstructionsHTML(), unsafe_allow_html=True)