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 fetchResource
 13from ssscoring.appcommon import initFileUploaderState
 14from ssscoring.appcommon import interpretJumpResult
 15from ssscoring.appcommon import plotJumpResult
 16from ssscoring.appcommon import setSideBarAndMain
 17from ssscoring.calc import aggregateResults
 18from ssscoring.calc import collateAnglesByTimeFromExit
 19from ssscoring.calc import dropNonSkydiveDataFrom
 20from ssscoring.calc import processAllJumpFiles
 21from ssscoring.calc import totalResultsFrom
 22from ssscoring.constants import DEFAULT_PLOT_INCREMENT
 23from ssscoring.constants import DEFAULT_PLOT_MAX_V_SCALE
 24from ssscoring.constants import M_2_FT
 25from ssscoring.constants import SPEED_ACCURACY_THRESHOLD
 26from ssscoring.constants import SSSCORE_INSTRUCTIONS_MD
 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 graphJumpResult
 33from ssscoring.notebook import initializePlot
 34
 35import bokeh.plotting as bp
 36import pandas as pd
 37import streamlit as st
 38
 39
 40# +++ implementation +++
 41
 42def _selectDZState(*args, **kwargs):
 43    if st.session_state.elevation:
 44        st.session_state.uploaderKey += 1
 45        st.session_state.trackFiles = None
 46
 47
 48def _styleShowMaxIn(scores: pd.Series) -> pd.DataFrame:
 49    return [
 50        'background-color: mediumseagreen' if v == scores.max() else \
 51        '' for v in scores ]
 52
 53
 54def _displayAllJumpDataIn(data: pd.DataFrame):
 55    if data is not None:
 56        columns = [ 'plotTime' ] + [ column for column in data.columns if column != 'plotTime' and column != 'timeUnix' ]
 57        st.html('<h3>All jump data from exit</h3>')
 58        st.dataframe(data,
 59            column_order=columns,
 60            column_config={
 61                'plotTime': st.column_config.NumberColumn(format='%.02f'),
 62                'speedAngle': st.column_config.NumberColumn(format='%.02f'),
 63                'speedAccuracyISC': st.column_config.NumberColumn(format='%.02f'),
 64            },
 65            hide_index=True)
 66
 67
 68def _displayScoresIn(scoresData: dict):
 69    if scoresData is not None:
 70        st.html('<h3>All 3-sec sliding window scores</h3>')
 71        data = pd.DataFrame.from_dict({ 'time': scoresData.values(), 'score': scoresData.keys(), })
 72        data.time = data.time.apply(lambda x: '%.2f' % x)
 73        st.dataframe(data, hide_index=True)
 74
 75
 76def _displayBadRowsISCAccuracyExceeded(data: pd.DataFrame, window: PerformanceWindow):
 77    badRows = data[data.speedAccuracyISC >= SPEED_ACCURACY_THRESHOLD]
 78    badRows = dropNonSkydiveDataFrom(badRows)
 79    times = pd.to_datetime(badRows.timeUnix, unit='s').dt.strftime('%Y-%m-%d %H:%M:%S.%f').str[:-4]
 80    badRows.insert(0, 'time', times)
 81    badRows.drop(columns = [
 82        'timeUnix',
 83        'altitudeMSL',
 84        'altitudeMSLFt',
 85        'speedAccuracy',
 86        'hMetersPerSecond',
 87        'hKMh',
 88        'speedAngle',
 89        'latitude',
 90        'longitude',
 91        'verticalAccuracy', ], inplace=True)
 92    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>' % \
 93                    (window.start, M_2_FT*window.start, window.end, M_2_FT*window.end, window.validationStart, M_2_FT*window.validationStart))
 94    st.html('<h3>%d track rows where the ISC speed accuracy threshold was exceeded during the speed run:</h3>' % len(badRows))
 95    st.dataframe(badRows, hide_index=True)
 96
 97
 98    workData = data.copy()
 99    workData = dropNonSkydiveDataFrom(workData)
100    times = pd.to_datetime(workData.timeUnix, unit='s').dt.strftime('%Y-%m-%d %H:%M:%S.%f').str[:-4]
101    workData.insert(0, 'time', times)
102    st.html('<h3>Full speed run data (%d rows)</h3>' % len(workData))
103    st.dataframe(workData, hide_index=True)
104
105
106def _styleShowMinMaxIn(scores: pd.Series) -> pd.DataFrame:
107    return [
108        'background-color: green' if v == scores.max() else \
109        'background-color: orangered' if v == scores.min() else \
110        '' for v in scores ]
111
112
113def _displayJumpsInSet(aggregate: pd.DataFrame):
114    with st.expander('**Jumps in this set**', expanded=True, icon=':material/dataset:'):
115        displayAggregate = aggregate.style.apply(_styleShowMinMaxIn, subset=[ 'score', ]).apply(_styleShowMaxIn, subset=[ 'maxSpeed', ]).format(precision=2)
116        st.dataframe(displayAggregate)
117
118
119def _displaySpeedSummary(aggregate: pd.DataFrame,
120                         allJumpsPlot: bp.Figure):
121    st.html('<h2>Speed summary</h2>')
122    st.dataframe(totalResultsFrom(aggregate), hide_index = True)
123    st.bokeh_chart(allJumpsPlot, use_container_width=True)
124
125
126def _displaySpeedAngles(jumpResults: dict):
127    with st.expander('**Speed angles**', icon=':material/arrow_back_ios_new:'):
128        angles = collateAnglesByTimeFromExit(jumpResults).style.format(precision=1)
129        st.dataframe(angles)
130
131
132def _displayAllTracksOnMap(jumpResults: dict):
133    with st.expander('**All jumps trajectories**'):
134        displayTrackOnMap(multipleSpeedJumpsTrajectories(jumpResults))
135
136
137def _maxSpeedScaleFrom(jumpResults: dict) -> float:
138    maxScore = max(result.score if result.score != None else 0 for result in jumpResults.values())
139    try:
140        return DEFAULT_PLOT_MAX_V_SCALE if maxScore <= DEFAULT_PLOT_MAX_V_SCALE else maxScore + DEFAULT_PLOT_INCREMENT
141    except TypeError:
142        return DEFAULT_PLOT_MAX_V_SCALE
143
144
145def main():
146    st.set_page_config(
147        layout = 'wide',
148        page_title='SSScore %s' % __VERSION__,
149    )
150    initFileUploaderState('trackFiles')
151    setSideBarAndMain('🔢', False, _selectDZState)
152
153    if st.session_state.trackFiles:
154        jumpResults = processAllJumpFiles(st.session_state.trackFiles, altitudeDZMeters=st.session_state.elevation)
155        allJumpsPlot = initializePlot('All jumps', backgroundColorName='#2c2c2c', yMax=_maxSpeedScaleFrom(jumpResults))
156        mixColor = 0
157        jumpResultsSubset = dict()
158        resultTags = sorted(list(jumpResults.keys()), reverse=True)
159        tabs = st.tabs(['Totals']+resultTags)
160        index = 1
161        jumpStatus = JumpStatus.OK
162        for tag in resultTags:
163            jumpResult = jumpResults[tag]
164            mixColor = (mixColor+1)%len(SPEED_COLORS)
165            with tabs[index]:
166                jumpStatusInfo,\
167                scoringInfo,\
168                badJumpLegend,\
169                jumpStatus = interpretJumpResult(tag, jumpResult, st.session_state.processBadJump)
170                if jumpStatus != JumpStatus.OK:
171                    st.toast('#### %s - %s' % (tag, str(jumpStatus)), icon='⚠️')
172                if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK:
173                    jumpResultsSubset[tag] = jumpResult
174                st.html('<h3>'+jumpStatusInfo+scoringInfo+(str(badJumpLegend) if badJumpLegend else ''))
175                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>')
176                if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK:
177                    displayJumpDataIn(jumpResult.table)
178                    st.write('Max score = crosshairs.  Max speed = diamond.')
179                    plotJumpResult(tag, jumpResult)
180                    graphJumpResult(
181                        allJumpsPlot,
182                        jumpResult,
183                        lineColor=SPEED_COLORS[mixColor],
184                        legend='%s = %.2f' % (tag, jumpResult.score if jumpResult.score else -1.0),
185                        showIt=False
186                    )
187                    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)
188                    displayTrackOnMap(speedJumpTrajectory(jumpResult, st.session_state.displayScorePoint), st.session_state.displayScorePoint)
189                    _displayAllJumpDataIn(jumpResult.data)
190                    _displayScoresIn(jumpResult.scores)
191                elif jumpStatus == JumpStatus.SPEED_ACCURACY_EXCEEDS_LIMIT:
192                    _displayBadRowsISCAccuracyExceeded(jumpResult.data, jumpResult.window)
193            index += 1
194        with tabs[0]:
195            if len(resultTags):
196                if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK:
197                    aggregate = aggregateResults(jumpResultsSubset)
198                    if len(aggregate) > 0:
199                        _displayJumpsInSet(aggregate)
200                        _displaySpeedAngles(jumpResults)
201                        _displaySpeedSummary(aggregate, allJumpsPlot)
202                        _displayAllTracksOnMap(jumpResults)
203    else:
204        st.write(fetchResource(SSSCORE_INSTRUCTIONS_MD).read(), unsafe_allow_html=True)
205
206
207if '__main__' == __name__:
208    main()
def main():
146def main():
147    st.set_page_config(
148        layout = 'wide',
149        page_title='SSScore %s' % __VERSION__,
150    )
151    initFileUploaderState('trackFiles')
152    setSideBarAndMain('🔢', False, _selectDZState)
153
154    if st.session_state.trackFiles:
155        jumpResults = processAllJumpFiles(st.session_state.trackFiles, altitudeDZMeters=st.session_state.elevation)
156        allJumpsPlot = initializePlot('All jumps', backgroundColorName='#2c2c2c', yMax=_maxSpeedScaleFrom(jumpResults))
157        mixColor = 0
158        jumpResultsSubset = dict()
159        resultTags = sorted(list(jumpResults.keys()), reverse=True)
160        tabs = st.tabs(['Totals']+resultTags)
161        index = 1
162        jumpStatus = JumpStatus.OK
163        for tag in resultTags:
164            jumpResult = jumpResults[tag]
165            mixColor = (mixColor+1)%len(SPEED_COLORS)
166            with tabs[index]:
167                jumpStatusInfo,\
168                scoringInfo,\
169                badJumpLegend,\
170                jumpStatus = interpretJumpResult(tag, jumpResult, st.session_state.processBadJump)
171                if jumpStatus != JumpStatus.OK:
172                    st.toast('#### %s - %s' % (tag, str(jumpStatus)), icon='⚠️')
173                if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK:
174                    jumpResultsSubset[tag] = jumpResult
175                st.html('<h3>'+jumpStatusInfo+scoringInfo+(str(badJumpLegend) if badJumpLegend else ''))
176                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>')
177                if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK:
178                    displayJumpDataIn(jumpResult.table)
179                    st.write('Max score = crosshairs.  Max speed = diamond.')
180                    plotJumpResult(tag, jumpResult)
181                    graphJumpResult(
182                        allJumpsPlot,
183                        jumpResult,
184                        lineColor=SPEED_COLORS[mixColor],
185                        legend='%s = %.2f' % (tag, jumpResult.score if jumpResult.score else -1.0),
186                        showIt=False
187                    )
188                    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)
189                    displayTrackOnMap(speedJumpTrajectory(jumpResult, st.session_state.displayScorePoint), st.session_state.displayScorePoint)
190                    _displayAllJumpDataIn(jumpResult.data)
191                    _displayScoresIn(jumpResult.scores)
192                elif jumpStatus == JumpStatus.SPEED_ACCURACY_EXCEEDS_LIMIT:
193                    _displayBadRowsISCAccuracyExceeded(jumpResult.data, jumpResult.window)
194            index += 1
195        with tabs[0]:
196            if len(resultTags):
197                if (st.session_state.processBadJump and jumpStatus != JumpStatus.OK) or jumpStatus == JumpStatus.OK:
198                    aggregate = aggregateResults(jumpResultsSubset)
199                    if len(aggregate) > 0:
200                        _displayJumpsInSet(aggregate)
201                        _displaySpeedAngles(jumpResults)
202                        _displaySpeedSummary(aggregate, allJumpsPlot)
203                        _displayAllTracksOnMap(jumpResults)
204    else:
205        st.write(fetchResource(SSSCORE_INSTRUCTIONS_MD).read(), unsafe_allow_html=True)