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)