ssscoring.appcommon

Common functions, classes, and objects to all Streamlit apps in the SSScoring package.

  1# See: https://github.com/pr3d4t0r/SSScoring/blob/master/LICENSE.txt
  2
  3"""
  4Common functions, classes, and objects to all Streamlit apps in the SSScoring
  5package.
  6"""
  7
  8from importlib_resources import files
  9from io import StringIO
 10
 11from ssscoring import __VERSION__
 12from ssscoring.calc import isValidMaximumAltitude
 13from ssscoring.calc import isValidMinimumAltitude
 14from ssscoring.constants import FLYSIGHT_FILE_ENCODING
 15from ssscoring.constants import M_2_FT
 16from ssscoring.datatypes import JumpResults
 17from ssscoring.datatypes import JumpStatus
 18from ssscoring.errors import SSScoringError
 19from ssscoring.notebook import SPEED_COLORS
 20from ssscoring.notebook import graphAltitude
 21from ssscoring.notebook import graphAngle
 22from ssscoring.notebook import graphJumpResult
 23from ssscoring.notebook import initializeExtraYRanges
 24from ssscoring.notebook import initializePlot
 25
 26import os
 27
 28import bokeh.models as bm
 29import pandas as pd
 30import pydeck as pdk
 31import streamlit as st
 32
 33
 34# *** constants ***
 35
 36DEFAULT_DATA_LAKE = './data'
 37"""
 38Default data lake directory when reading files from the local file system.
 39"""
 40
 41DZ_DIRECTORY = 'drop-zones-loc-elev.csv'
 42"""
 43The CSV file database dump of the drop zones directory.
 44"""
 45
 46RESOURCES = 'ssscoring.resources'
 47"""
 48The package resources in the manifest or package wheel resources.
 49"""
 50
 51STREAMLIT_SIG_KEY = 'HOSTNAME'
 52"""
 53Environment key used by the Streamlig.app environment when running an
 54application.
 55"""
 56
 57STREAMLIT_SIG_VALUE = 'streamlit'
 58"""
 59Expected value associate with the environment variable `STREAMLIT_SIG_KEY` when
 60running in a Streamlit.app environment.
 61"""
 62
 63
 64# *** implementation ***
 65
 66def isStreamlitHostedApp() -> bool:
 67    """
 68    Detect if the hosting environment is a native Python system or a Streamlit
 69    app environment hosted by streamlit.io or Snowflake.
 70
 71    Returns
 72    -------
 73    `True` if the app is running in the Streamlit app or Snoflake app
 74    environment, otherwise `False`.
 75    """
 76    keys = tuple(os.environ.keys())
 77    if STREAMLIT_SIG_KEY not in keys:
 78        return False
 79    if os.environ[STREAMLIT_SIG_KEY] == STREAMLIT_SIG_VALUE:
 80        return True
 81    return False
 82
 83
 84@st.cache_data
 85def initDropZonesFromResource(resourceName: str) -> pd.DataFrame:
 86    """
 87    Get the DZs directory from a CSV enclosed in the distribution package as a
 88    resource.  The resources package is fixed to `ssscoring.resources`, the
 89    default resource file is defined by `DZ_DIRECTORY` but can be anything.
 90
 91    Arguments
 92    ---------
 93        resourceName
 94    A string representing the resource file name, usually a CSV file.
 95
 96    Returns
 97    -------
 98    The global drop zones directory as a dataframe.
 99
100    Raises
101    ------
102    `SSScoringError` if the resource dataframe isn't the global drop zones
103    directory or the file is invalid in any way.
104    """
105    try:
106        buffer = StringIO(files(RESOURCES).joinpath(resourceName).read_bytes().decode(FLYSIGHT_FILE_ENCODING))
107        dropZones = pd.read_csv(buffer, sep=',')
108    except Exception as e:
109        raise SSScoringError('Invalid resource - %s' % str(e))
110
111    if 'dropZone' not in dropZones.columns:
112        raise SSScoringError('dropZone object is a dataframe but not the drop zones directory')
113
114    return dropZones
115
116
117def displayJumpDataIn(resultsTable: pd.DataFrame):
118    """
119    Display the individual results, as a table.
120
121    Arguments
122    ---------
123        resultsTable: pd.DataFrame
124    The results from a speed skydiving jump.
125
126    See
127    ---
128    `ssscoring.datatypes.JumpResults`
129    """
130    table = resultsTable.copy()
131    table.vKMh = table.vKMh.apply(lambda x: round(x, 2))
132    table.hKMh = table.hKMh.apply(lambda x: round(x, 2))
133    table.deltaV = table.deltaV.apply(lambda x: round(x, 2))
134    table.deltaAngle = table.deltaAngle.apply(lambda x: round(x, 2))
135    table['altitude (ft)'] = table['altitude (ft)'].apply(lambda x: round(x, 1))
136    table.index = ['']*len(table)
137    st.dataframe(table, hide_index=True)
138
139
140def interpretJumpResult(tag: str,
141                        jumpResult: JumpResults,
142                        processBadJump: bool):
143    """
144    Interpret the jump results and generate the corresponding labels and
145    warnings if a jump is invalid, or only "somewhat valid" according to ISC
146    rules.  The caller turns results display on/off depending on training vs
147    in-competition settins.  Because heuristics are a beautiful thing.
148
149    Arguments
150    ---------
151        tag
152    A string that identifies a specific jump and the FlySight version that
153    generated the corresponding track file.  Often in the form: `HH-mm-ss:vX`
154    where `X` is the FlySight hardware version.
155
156        jumpResult
157    An instance of `ssscoring.datatypes.JumpResults` with jump data.
158
159        processBadJump
160    If `True`, generate end-user warnings as part of its results processing if
161    the jump is invalid because of ISC rules, but set the status to OK so that
162    the jump may be displayed.
163
164    Returns
165    -------
166    A `tuple` of these objects:
167
168    - `jumpStatusInfo` - the jump status, in human-readable form
169    - `scoringInfo` - Max speed, scoring window, etc.
170    - `badJumpLegend` - A warning or error if the jump is invalid according to
171      ISC rules
172    - `jumpStatus` - An instance of `JumpStatus` that may have been overriden to
173      `OK` if `processBadJump` was set to `True` and the jump was invalid.  Used
174      only for display.  Use `jumpResult.status` to determine the actual result
175      of the jump from a strict scoring perspective.  `jumpStatus` is  used
176      for display override purposes only.
177    """
178    maxSpeed = jumpResult.maxSpeed
179    window = jumpResult.window
180    jumpStatus = jumpResult.status
181    jumpStatusInfo = ''
182    if jumpResult.status == JumpStatus.WARM_UP_FILE:
183        badJumpLegend = '<span style="color: red">Warm up file or SMD ran out of battery - nothing to do<br>'
184        scoringInfo = ''
185    elif jumpResult.status == JumpStatus.SPEED_ACCURACY_EXCEEDS_LIMIT:
186        badJumpLegend = '<span style="color: red">%s - RE-JUMP: speed accuracy exceeds ISC threshold<br>' % tag
187        scoringInfo = ''
188    else:
189        scoringInfo = 'Max speed = {0:,.0f}; '.format(maxSpeed)+('exit at %d m (%d ft)<br>Validation window starts at %d m (%d ft)<br>End scoring window at %d m (%d ft)<br>' % \
190                        (window.start, M_2_FT*window.start, window.validationStart, M_2_FT*window.validationStart, window.end, M_2_FT*window.end))
191    if (processBadJump and jumpStatus != JumpStatus.OK and jumpStatus != JumpStatus.WARM_UP_FILE) or jumpStatus == JumpStatus.OK:
192        jumpStatusInfo = '<span style="color: %s">%s jump - %s - %.02f km/h</span><br>' % ('green', tag, 'VALID', jumpResult.score)
193        belowMaxAltitude = isValidMaximumAltitude(jumpResult.data.altitudeAGL.max())
194        badJumpLegend = None
195        if not isValidMinimumAltitude(jumpResult.data.altitudeAGL.max()):
196            badJumpLegend = '<span style="color: yellow"><span style="font-weight: bold">Warning:</span> exit altitude AGL was lower than the minimum scoring altitude<br>'
197            jumpStatus = JumpStatus.ALTITUDE_EXCEEDS_MINIMUM
198        if not belowMaxAltitude:
199            jumpStatusInfo = '<span style="color: %s">%s jump - %s - %.02f km/h</span><br>' % ('red', tag, 'INVALID', jumpResult.score)
200            badJumpLegend = '<span style="color: red"><span style="font-weight: bold">RE-JUMP:</span> exit altitude AGL exceeds the maximum altitude<br>'
201            jumpStatus = JumpStatus.ALTITUDE_EXCEEDS_MAXIMUM
202    return jumpStatusInfo, scoringInfo, badJumpLegend, jumpStatus
203
204
205def plotJumpResult(tag: str,
206                   jumpResult: JumpResults):
207    """
208    Plot the jump results including altitude, horizontal speed, time, etc. for
209    evaluation and interpretation.
210
211    Arguments
212    ---------
213        tag
214    A string that identifies a specific jump and the FlySight version that
215    generated the corresponding track file.  Often in the form: `HH-mm-ss:vX`
216    where `X` is the FlySight hardware version.
217
218        jumpResult
219    An instance of `ssscoring.datatypes.JumpResults` with jump data.
220    """
221    plot = initializePlot(tag)
222    plot = initializeExtraYRanges(plot, startY=min(jumpResult.data.altitudeAGLFt)-500.0, endY=max(jumpResult.data.altitudeAGLFt)+500.0)
223    graphAltitude(plot, jumpResult)
224    graphAngle(plot, jumpResult)
225    hoverValue = bm.HoverTool(tooltips=[('time', '@x{0.0}s'), ('y-val', '@y{0.00}')])
226    plot.add_tools(hoverValue)
227    graphJumpResult(plot, jumpResult, lineColor=SPEED_COLORS[0])
228    st.bokeh_chart(plot, use_container_width=True)
229
230
231def initFileUploaderState(filesObject:str, uploaderKey:str ='uploaderKey'):
232    """
233    Initialize the session state for the Streamlit app uploader so that
234    selections can be cleared in callbacks later.
235
236    **Important**: `initFileUploaderState()` __must__ be called after setting the
237    page configuration (per Streamlit architecture rules) and before adding any
238    widgets to the sidebars, containers, or main application.
239
240    Argument
241    --------
242        filesObject
243    A `str` name that is either `'trackFile'` for the single track file process
244    or `'trackFiles'` for the app page that handles more than one track file at
245    a time.
246
247        uploaderKey
248    A unique identifier for the uploader key component, usually set to
249    `'uploaderKey'` but can be any arbitrary name.  This value must match the
250    `file_uploader(..., key=uploaderKey,...)` value.
251
252    """
253    if filesObject not in st.session_state:
254        st.session_state[filesObject] = None
255    if uploaderKey not in st.session_state:
256        st.session_state[uploaderKey] = 0
257
258
259def displayTrackOnMap(deck: pdk.Deck):
260    """
261    Displays a track map drawn using PyDeck.
262
263    Arguments
264    ---------
265        deck
266    A PyDeck initialized with map layers.
267    """
268    st.write('Brightest green point shows the max speed point.  Exit at orange point.  Each track dot is 4 m in diameter.')
269    st.pydeck_chart(deck)
270
271
272def setSideBarAndMain(icon: str, singleTrack: bool, selectDZState):
273    """
274    Set all the interactive and navigational components for the app's side bar.
275
276    Arguments
277    ---------
278        icon
279    A meaningful Emoji associated with the the side bar's title.
280
281        singleTrack
282    A flag for allowing selection of a single or multiple track files in the
283    corresponding selector component.  Determines whether the side bar is used
284    for the single- or multiple selection application.
285
286        selectDZState
287    A callback for the drop zone selector selection box, affected by events in
288    the main application.
289
290    Notes
291    -----
292    All the aplication level values associated with the components and
293    selections from the side bar are stored in `st.session_state` and visible
294    across the whole application.
295
296    **Do not** cache calls to `ssscoring.appcommon.setSideBarAndMain()` because
297    this can result in unpredictable behavior since the cache may never be
298    cleared until application reload.
299    """
300    dropZones = initDropZonesFromResource(DZ_DIRECTORY)
301    dropZone = None
302    elevation = None
303    st.sidebar.title('%s SSScore %s' % (icon, __VERSION__))
304    st.session_state.processBadJump = st.sidebar.checkbox('Process bad jumps', value=True, help='Display results from invalid jumps')
305    dropZone = st.sidebar.selectbox('Select the drop zone:', dropZones.dropZone, index=None, on_change=selectDZState, disabled=(elevation != None and elevation != 0.0))
306    elevation = st.sidebar.number_input('...or enter the DZ elevation in meters:', min_value=0.0, max_value=4000.0, value='min', format='%.2f', disabled=(dropZone != None), on_change=selectDZState)
307    if dropZone:
308        st.session_state.elevation = dropZones[dropZones.dropZone == dropZone ].iloc[0].elevation
309    elif elevation != None and elevation != 0.0:
310        st.session_state.elevation= elevation
311    else:
312        st.session_state.elevation = None
313        st.session_state.trackFiles = None
314    st.sidebar.metric('Elevation', value='%.1f m' % (0.0 if st.session_state.elevation == None else st.session_state.elevation))
315    if singleTrack:
316        trackFile = st.sidebar.file_uploader('Track file', [ 'CSV' ], disabled=st.session_state.elevation == None, key = st.session_state.uploaderKey)
317        if trackFile:
318            st.session_state.trackFile = trackFile
319    else:
320        trackFiles = st.sidebar.file_uploader(
321            'Track files',
322            [ 'CSV' ],
323            disabled=st.session_state.elevation == None,
324            accept_multiple_files=True,
325            key = st.session_state.uploaderKey
326        )
327        if trackFiles:
328            st.session_state.trackFiles = trackFiles
329    st.sidebar.button('Clear', on_click=selectDZState)
330    st.sidebar.link_button('Report missing DZ', 'https://github.com/pr3d4t0r/SSScoring/issues/new?template=report-missing-dz.md', icon=':material/breaking_news_alt_1:')
331    st.sidebar.link_button('Feature request or bug report', 'https://github.com/pr3d4t0r/SSScoring/issues/new?template=Blank+issue', icon=':material/breaking_news_alt_1:')
DEFAULT_DATA_LAKE = './data'

Default data lake directory when reading files from the local file system.

DZ_DIRECTORY = 'drop-zones-loc-elev.csv'

The CSV file database dump of the drop zones directory.

RESOURCES = 'ssscoring.resources'

The package resources in the manifest or package wheel resources.

STREAMLIT_SIG_KEY = 'HOSTNAME'

Environment key used by the Streamlig.app environment when running an application.

STREAMLIT_SIG_VALUE = 'streamlit'

Expected value associate with the environment variable STREAMLIT_SIG_KEY when running in a Streamlit.app environment.

def isStreamlitHostedApp() -> bool:
67def isStreamlitHostedApp() -> bool:
68    """
69    Detect if the hosting environment is a native Python system or a Streamlit
70    app environment hosted by streamlit.io or Snowflake.
71
72    Returns
73    -------
74    `True` if the app is running in the Streamlit app or Snoflake app
75    environment, otherwise `False`.
76    """
77    keys = tuple(os.environ.keys())
78    if STREAMLIT_SIG_KEY not in keys:
79        return False
80    if os.environ[STREAMLIT_SIG_KEY] == STREAMLIT_SIG_VALUE:
81        return True
82    return False

Detect if the hosting environment is a native Python system or a Streamlit app environment hosted by streamlit.io or Snowflake.

Returns

True if the app is running in the Streamlit app or Snoflake app environment, otherwise False.

@st.cache_data
def initDropZonesFromResource(resourceName: str) -> pandas.core.frame.DataFrame:
 85@st.cache_data
 86def initDropZonesFromResource(resourceName: str) -> pd.DataFrame:
 87    """
 88    Get the DZs directory from a CSV enclosed in the distribution package as a
 89    resource.  The resources package is fixed to `ssscoring.resources`, the
 90    default resource file is defined by `DZ_DIRECTORY` but can be anything.
 91
 92    Arguments
 93    ---------
 94        resourceName
 95    A string representing the resource file name, usually a CSV file.
 96
 97    Returns
 98    -------
 99    The global drop zones directory as a dataframe.
100
101    Raises
102    ------
103    `SSScoringError` if the resource dataframe isn't the global drop zones
104    directory or the file is invalid in any way.
105    """
106    try:
107        buffer = StringIO(files(RESOURCES).joinpath(resourceName).read_bytes().decode(FLYSIGHT_FILE_ENCODING))
108        dropZones = pd.read_csv(buffer, sep=',')
109    except Exception as e:
110        raise SSScoringError('Invalid resource - %s' % str(e))
111
112    if 'dropZone' not in dropZones.columns:
113        raise SSScoringError('dropZone object is a dataframe but not the drop zones directory')
114
115    return dropZones

Get the DZs directory from a CSV enclosed in the distribution package as a resource. The resources package is fixed to ssscoring.resources, the default resource file is defined by DZ_DIRECTORY but can be anything.

Arguments

resourceName

A string representing the resource file name, usually a CSV file.

Returns

The global drop zones directory as a dataframe.

Raises

SSScoringError if the resource dataframe isn't the global drop zones directory or the file is invalid in any way.

def displayJumpDataIn(resultsTable: pandas.core.frame.DataFrame):
118def displayJumpDataIn(resultsTable: pd.DataFrame):
119    """
120    Display the individual results, as a table.
121
122    Arguments
123    ---------
124        resultsTable: pd.DataFrame
125    The results from a speed skydiving jump.
126
127    See
128    ---
129    `ssscoring.datatypes.JumpResults`
130    """
131    table = resultsTable.copy()
132    table.vKMh = table.vKMh.apply(lambda x: round(x, 2))
133    table.hKMh = table.hKMh.apply(lambda x: round(x, 2))
134    table.deltaV = table.deltaV.apply(lambda x: round(x, 2))
135    table.deltaAngle = table.deltaAngle.apply(lambda x: round(x, 2))
136    table['altitude (ft)'] = table['altitude (ft)'].apply(lambda x: round(x, 1))
137    table.index = ['']*len(table)
138    st.dataframe(table, hide_index=True)

Display the individual results, as a table.

Arguments

resultsTable: pd.DataFrame

The results from a speed skydiving jump.

See

ssscoring.datatypes.JumpResults

def interpretJumpResult( tag: str, jumpResult: ssscoring.datatypes.JumpResults, processBadJump: bool):
141def interpretJumpResult(tag: str,
142                        jumpResult: JumpResults,
143                        processBadJump: bool):
144    """
145    Interpret the jump results and generate the corresponding labels and
146    warnings if a jump is invalid, or only "somewhat valid" according to ISC
147    rules.  The caller turns results display on/off depending on training vs
148    in-competition settins.  Because heuristics are a beautiful thing.
149
150    Arguments
151    ---------
152        tag
153    A string that identifies a specific jump and the FlySight version that
154    generated the corresponding track file.  Often in the form: `HH-mm-ss:vX`
155    where `X` is the FlySight hardware version.
156
157        jumpResult
158    An instance of `ssscoring.datatypes.JumpResults` with jump data.
159
160        processBadJump
161    If `True`, generate end-user warnings as part of its results processing if
162    the jump is invalid because of ISC rules, but set the status to OK so that
163    the jump may be displayed.
164
165    Returns
166    -------
167    A `tuple` of these objects:
168
169    - `jumpStatusInfo` - the jump status, in human-readable form
170    - `scoringInfo` - Max speed, scoring window, etc.
171    - `badJumpLegend` - A warning or error if the jump is invalid according to
172      ISC rules
173    - `jumpStatus` - An instance of `JumpStatus` that may have been overriden to
174      `OK` if `processBadJump` was set to `True` and the jump was invalid.  Used
175      only for display.  Use `jumpResult.status` to determine the actual result
176      of the jump from a strict scoring perspective.  `jumpStatus` is  used
177      for display override purposes only.
178    """
179    maxSpeed = jumpResult.maxSpeed
180    window = jumpResult.window
181    jumpStatus = jumpResult.status
182    jumpStatusInfo = ''
183    if jumpResult.status == JumpStatus.WARM_UP_FILE:
184        badJumpLegend = '<span style="color: red">Warm up file or SMD ran out of battery - nothing to do<br>'
185        scoringInfo = ''
186    elif jumpResult.status == JumpStatus.SPEED_ACCURACY_EXCEEDS_LIMIT:
187        badJumpLegend = '<span style="color: red">%s - RE-JUMP: speed accuracy exceeds ISC threshold<br>' % tag
188        scoringInfo = ''
189    else:
190        scoringInfo = 'Max speed = {0:,.0f}; '.format(maxSpeed)+('exit at %d m (%d ft)<br>Validation window starts at %d m (%d ft)<br>End scoring window at %d m (%d ft)<br>' % \
191                        (window.start, M_2_FT*window.start, window.validationStart, M_2_FT*window.validationStart, window.end, M_2_FT*window.end))
192    if (processBadJump and jumpStatus != JumpStatus.OK and jumpStatus != JumpStatus.WARM_UP_FILE) or jumpStatus == JumpStatus.OK:
193        jumpStatusInfo = '<span style="color: %s">%s jump - %s - %.02f km/h</span><br>' % ('green', tag, 'VALID', jumpResult.score)
194        belowMaxAltitude = isValidMaximumAltitude(jumpResult.data.altitudeAGL.max())
195        badJumpLegend = None
196        if not isValidMinimumAltitude(jumpResult.data.altitudeAGL.max()):
197            badJumpLegend = '<span style="color: yellow"><span style="font-weight: bold">Warning:</span> exit altitude AGL was lower than the minimum scoring altitude<br>'
198            jumpStatus = JumpStatus.ALTITUDE_EXCEEDS_MINIMUM
199        if not belowMaxAltitude:
200            jumpStatusInfo = '<span style="color: %s">%s jump - %s - %.02f km/h</span><br>' % ('red', tag, 'INVALID', jumpResult.score)
201            badJumpLegend = '<span style="color: red"><span style="font-weight: bold">RE-JUMP:</span> exit altitude AGL exceeds the maximum altitude<br>'
202            jumpStatus = JumpStatus.ALTITUDE_EXCEEDS_MAXIMUM
203    return jumpStatusInfo, scoringInfo, badJumpLegend, jumpStatus

Interpret the jump results and generate the corresponding labels and warnings if a jump is invalid, or only "somewhat valid" according to ISC rules. The caller turns results display on/off depending on training vs in-competition settins. Because heuristics are a beautiful thing.

Arguments

tag

A string that identifies a specific jump and the FlySight version that generated the corresponding track file. Often in the form: HH-mm-ss:vX where X is the FlySight hardware version.

jumpResult

An instance of ssscoring.datatypes.JumpResults with jump data.

processBadJump

If True, generate end-user warnings as part of its results processing if the jump is invalid because of ISC rules, but set the status to OK so that the jump may be displayed.

Returns

A tuple of these objects:

  • jumpStatusInfo - the jump status, in human-readable form
  • scoringInfo - Max speed, scoring window, etc.
  • badJumpLegend - A warning or error if the jump is invalid according to ISC rules
  • jumpStatus - An instance of JumpStatus that may have been overriden to OK if processBadJump was set to True and the jump was invalid. Used only for display. Use jumpResult.status to determine the actual result of the jump from a strict scoring perspective. jumpStatus is used for display override purposes only.
def plotJumpResult(tag: str, jumpResult: ssscoring.datatypes.JumpResults):
206def plotJumpResult(tag: str,
207                   jumpResult: JumpResults):
208    """
209    Plot the jump results including altitude, horizontal speed, time, etc. for
210    evaluation and interpretation.
211
212    Arguments
213    ---------
214        tag
215    A string that identifies a specific jump and the FlySight version that
216    generated the corresponding track file.  Often in the form: `HH-mm-ss:vX`
217    where `X` is the FlySight hardware version.
218
219        jumpResult
220    An instance of `ssscoring.datatypes.JumpResults` with jump data.
221    """
222    plot = initializePlot(tag)
223    plot = initializeExtraYRanges(plot, startY=min(jumpResult.data.altitudeAGLFt)-500.0, endY=max(jumpResult.data.altitudeAGLFt)+500.0)
224    graphAltitude(plot, jumpResult)
225    graphAngle(plot, jumpResult)
226    hoverValue = bm.HoverTool(tooltips=[('time', '@x{0.0}s'), ('y-val', '@y{0.00}')])
227    plot.add_tools(hoverValue)
228    graphJumpResult(plot, jumpResult, lineColor=SPEED_COLORS[0])
229    st.bokeh_chart(plot, use_container_width=True)

Plot the jump results including altitude, horizontal speed, time, etc. for evaluation and interpretation.

Arguments

tag

A string that identifies a specific jump and the FlySight version that generated the corresponding track file. Often in the form: HH-mm-ss:vX where X is the FlySight hardware version.

jumpResult

An instance of ssscoring.datatypes.JumpResults with jump data.

def initFileUploaderState(filesObject: str, uploaderKey: str = 'uploaderKey'):
232def initFileUploaderState(filesObject:str, uploaderKey:str ='uploaderKey'):
233    """
234    Initialize the session state for the Streamlit app uploader so that
235    selections can be cleared in callbacks later.
236
237    **Important**: `initFileUploaderState()` __must__ be called after setting the
238    page configuration (per Streamlit architecture rules) and before adding any
239    widgets to the sidebars, containers, or main application.
240
241    Argument
242    --------
243        filesObject
244    A `str` name that is either `'trackFile'` for the single track file process
245    or `'trackFiles'` for the app page that handles more than one track file at
246    a time.
247
248        uploaderKey
249    A unique identifier for the uploader key component, usually set to
250    `'uploaderKey'` but can be any arbitrary name.  This value must match the
251    `file_uploader(..., key=uploaderKey,...)` value.
252
253    """
254    if filesObject not in st.session_state:
255        st.session_state[filesObject] = None
256    if uploaderKey not in st.session_state:
257        st.session_state[uploaderKey] = 0

Initialize the session state for the Streamlit app uploader so that selections can be cleared in callbacks later.

Important: initFileUploaderState() __must__ be called after setting the page configuration (per Streamlit architecture rules) and before adding any widgets to the sidebars, containers, or main application.

Argument

filesObject

A str name that is either 'trackFile' for the single track file process or 'trackFiles' for the app page that handles more than one track file at a time.

uploaderKey

A unique identifier for the uploader key component, usually set to 'uploaderKey' but can be any arbitrary name. This value must match the file_uploader(..., key=uploaderKey,...) value.

def displayTrackOnMap(deck: pydeck.bindings.deck.Deck):
260def displayTrackOnMap(deck: pdk.Deck):
261    """
262    Displays a track map drawn using PyDeck.
263
264    Arguments
265    ---------
266        deck
267    A PyDeck initialized with map layers.
268    """
269    st.write('Brightest green point shows the max speed point.  Exit at orange point.  Each track dot is 4 m in diameter.')
270    st.pydeck_chart(deck)

Displays a track map drawn using PyDeck.

Arguments

deck

A PyDeck initialized with map layers.

def setSideBarAndMain(icon: str, singleTrack: bool, selectDZState):
273def setSideBarAndMain(icon: str, singleTrack: bool, selectDZState):
274    """
275    Set all the interactive and navigational components for the app's side bar.
276
277    Arguments
278    ---------
279        icon
280    A meaningful Emoji associated with the the side bar's title.
281
282        singleTrack
283    A flag for allowing selection of a single or multiple track files in the
284    corresponding selector component.  Determines whether the side bar is used
285    for the single- or multiple selection application.
286
287        selectDZState
288    A callback for the drop zone selector selection box, affected by events in
289    the main application.
290
291    Notes
292    -----
293    All the aplication level values associated with the components and
294    selections from the side bar are stored in `st.session_state` and visible
295    across the whole application.
296
297    **Do not** cache calls to `ssscoring.appcommon.setSideBarAndMain()` because
298    this can result in unpredictable behavior since the cache may never be
299    cleared until application reload.
300    """
301    dropZones = initDropZonesFromResource(DZ_DIRECTORY)
302    dropZone = None
303    elevation = None
304    st.sidebar.title('%s SSScore %s' % (icon, __VERSION__))
305    st.session_state.processBadJump = st.sidebar.checkbox('Process bad jumps', value=True, help='Display results from invalid jumps')
306    dropZone = st.sidebar.selectbox('Select the drop zone:', dropZones.dropZone, index=None, on_change=selectDZState, disabled=(elevation != None and elevation != 0.0))
307    elevation = st.sidebar.number_input('...or enter the DZ elevation in meters:', min_value=0.0, max_value=4000.0, value='min', format='%.2f', disabled=(dropZone != None), on_change=selectDZState)
308    if dropZone:
309        st.session_state.elevation = dropZones[dropZones.dropZone == dropZone ].iloc[0].elevation
310    elif elevation != None and elevation != 0.0:
311        st.session_state.elevation= elevation
312    else:
313        st.session_state.elevation = None
314        st.session_state.trackFiles = None
315    st.sidebar.metric('Elevation', value='%.1f m' % (0.0 if st.session_state.elevation == None else st.session_state.elevation))
316    if singleTrack:
317        trackFile = st.sidebar.file_uploader('Track file', [ 'CSV' ], disabled=st.session_state.elevation == None, key = st.session_state.uploaderKey)
318        if trackFile:
319            st.session_state.trackFile = trackFile
320    else:
321        trackFiles = st.sidebar.file_uploader(
322            'Track files',
323            [ 'CSV' ],
324            disabled=st.session_state.elevation == None,
325            accept_multiple_files=True,
326            key = st.session_state.uploaderKey
327        )
328        if trackFiles:
329            st.session_state.trackFiles = trackFiles
330    st.sidebar.button('Clear', on_click=selectDZState)
331    st.sidebar.link_button('Report missing DZ', 'https://github.com/pr3d4t0r/SSScoring/issues/new?template=report-missing-dz.md', icon=':material/breaking_news_alt_1:')
332    st.sidebar.link_button('Feature request or bug report', 'https://github.com/pr3d4t0r/SSScoring/issues/new?template=Blank+issue', icon=':material/breaking_news_alt_1:')

Set all the interactive and navigational components for the app's side bar.

Arguments

icon

A meaningful Emoji associated with the the side bar's title.

singleTrack

A flag for allowing selection of a single or multiple track files in the corresponding selector component. Determines whether the side bar is used for the single- or multiple selection application.

selectDZState

A callback for the drop zone selector selection box, affected by events in the main application.

Notes

All the aplication level values associated with the components and selections from the side bar are stored in st.session_state and visible across the whole application.

Do not cache calls to ssscoring.appcommon.setSideBarAndMain() because this can result in unpredictable behavior since the cache may never be cleared until application reload.