コード例 #1
0
def endurance_summary(FitFilePath, ConfigFile=None, OutStream=sys.stdout):

    (FilePath, FitFileName) = os.path.split(FitFilePath)

    if ConfigFile is None:
        ConfigFile = FindConfigFile('', FilePath)
    if (ConfigFile is None) or (not os.path.exists(ConfigFile)):
        raise IOError('Configuration file not specified or found')

    #
    #   Parse the configuration file
    #
    from ConfigParser import ConfigParser
    config      = ConfigParser()
    config.read(ConfigFile)
    print >> OutStream, 'reading config file ' + ConfigFile
    ThresholdPower  = config.getfloat( 'power', 'ThresholdPower' )
    ThresholdHR     = config.getfloat( 'power', 'ThresholdHR'    )
    print >> OutStream, 'ThresholdPower: ', ThresholdPower
    print >> OutStream, 'ThresholdHR   : ', ThresholdHR

    # power zones from "Cyclist's Training Bible", 5th ed., by Joe Friel, p51
    FTP = ThresholdPower
    pZones  = { 1   : [    0    ,   0.55*FTP ],
                2   : [ 0.55*FTP,   0.75*FTP ],
                3   : [ 0.75*FTP,   0.90*FTP ],
                4   : [ 0.90*FTP,   1.05*FTP ],
                5   : [ 1.05*FTP,   1.20*FTP ],
                6   : [ 1.20*FTP,   1.50*FTP ],
                7   : [ 1.50*FTP,   2.50*FTP ]}

    # heart-rate zones from "Cyclist's Training Bible" 5th ed. by Joe Friel, p50
    FTHR = ThresholdHR
    hZones  = { 1   : [     0    ,   0.82*FTHR ],  # 1
                2   : [ 0.82*FTHR,   0.89*FTHR ],  # 2
                3   : [ 0.89*FTHR,   0.94*FTHR ],  # 3
                4   : [ 0.94*FTHR,   1.00*FTHR ],  # 4
                5   : [ 1.00*FTHR,   1.03*FTHR ],  # 5a
                6   : [ 1.03*FTHR,   1.06*FTHR ],  # 5b
                7   : [ 1.07*FTHR,   1.15*FTHR ]}  # 5c

    # get zone bounds for plotting
    p_zone_bounds   = [ pZones[1][0],
                        pZones[2][0],
                        pZones[3][0],
                        pZones[4][0],
                        pZones[5][0],
                        pZones[6][0],
                        pZones[7][0],
                        pZones[7][1] ]

    h_zone_bounds   = [     0.4*FTHR,   # better plotting
                        hZones[2][0],
                        hZones[3][0],
                        hZones[4][0],
                        hZones[5][0],
                        hZones[6][0],
                        hZones[7][0],
                        hZones[7][1] ]


    from datetime import datetime
    from fitparse import Activity
    from activity_tools import extract_activity_signals

    required_signals    = [ 'power',
                            'heart_rate' ]

    # get the signals
    activity = Activity(FitFilePath)
    signals     = extract_activity_signals(activity, resample='existing')

    if not all( s in signals.keys() for s in required_signals ):
        msg = 'required signals not in file'
        print >> OutStream, msg
        print >> OutStream, 'Signals required:'
        for s in required_signals:
            print >> OutStream, '   ' + s
        print >> OutStream, 'Signals contained:'
        for s in signals.keys():
            print >> OutStream, '   ' + s
        raise IOError(msg)

    '''
    ####################
    # Get Records of type 'lap'
    # types: [ 'record', 'lap', 'event', 'session', 'activity', ... ]
    records = activity.get_records_by_type('lap')
    current_record_number = 0

    elapsed_time    = []
    timer_time      = []
    avg_heart_rate  = []
    avg_power       = []
    avg_cadence     = []
    max_heart_rate  = []
    balance         = []
    lap_timestamp   = []
    lap_start_time  = []

    FirstIter   = True

    for record in records:

        # Print record number
        current_record_number += 1
        #print (" Record #%d " % current_record_number).center(40, '-')

        # Get the list of valid fields on this record
        valid_field_names = record.get_valid_field_names()

        for field_name in valid_field_names:
            # Get the data and units for the field
            field_data = record.get_data(field_name)
            field_units = record.get_units(field_name)

            ## Print what we've got!
            #if field_units:
            #    print >> OutStream, " * %s: %s %s" % (field_name, field_data, field_units)
            #else:
            #    print >> OutStream, " * %s: %s" % (field_name, field_data)

            if 'timestamp' in field_name:
                lap_timestamp.append( field_data )

            if 'start_time' in field_name:
                lap_start_time.append( field_data )

            if 'total_elapsed_time' in field_name:
                elapsed_time.append( field_data )

            if 'total_timer_time' in field_name:
                timer_time.append( field_data )

            if 'avg_power' in field_name:
                avg_power.append( field_data )

            # avg_heart_rate is in a lap record
            if 'avg_heart_rate' in field_name:
                avg_heart_rate.append(field_data)

            if 'max_heart_rate' in field_name:
                max_heart_rate.append(field_data)

            if 'avg_cadence' in field_name:
                avg_cadence.append(field_data)

            if 'left_right_balance' in field_name:
                balance.append(field_data)

        #print
    ####################
    '''

    #
    #   extract lap results
    #
    from fitparse import Activity
    from activity_tools import extract_activity_laps
    import numpy as np
    activity    = Activity(FitFilePath)
    laps        = extract_activity_laps(activity)
    avg_power       = laps['power']
    time            = laps['time']
    cadence         = laps['cadence']
    avg_heart_rate  = laps['avg_hr']
    max_heart_rate  = laps['max_hr']
    balance         = laps['balance']
    lap_start_time  = laps['start_time']
    lap_timestamp   = laps['timestamp' ]
    timer_time      = laps['total_timer_time']
    elapsed_time    = laps['total_elapsed_time']


    IntervalThreshold = 0.0     # get all laps (0.72*FTP)
    from numpy import nonzero, array, arange, zeros, average, logical_and

    # resample power to constant-increment (1 Hz) with zeros at missing samples
    time_idx                = signals['time'].astype('int')
    power_vi                = signals['power']
    heart_rate_vi           = signals['heart_rate']
    nScans                  = time_idx[-1]+1
    time_ci                 = arange(nScans)
    power                   = zeros(nScans)
    power[time_idx]         = power_vi
    heart_rate_ci           = zeros(nScans)
    heart_rate_ci[time_idx] = heart_rate_vi

    t0 = signals['metadata']['timestamp']
    print >> OutStream, 'signal timestamp: ', t0.time()

    # plot lap results as continuous time signals
    lap_avg_hr_c        = zeros(nScans)
    lap_avg_power_c     = zeros(nScans)
    lap_norm_power_c    = zeros(nScans)

    # compute the 30-second, moving-average power signal.
    p30 = BackwardMovingAverage( power )

    #
    # compute lap metrics
    #
    print >> OutStream, 'lap results:'
    nLaps   = len(elapsed_time)
    vi_time_vector  = signals['time']

    lap_avg_power   = zeros(nLaps)
    lap_norm_power  = zeros(nLaps)
    lap_avg_hr      = zeros(nLaps)
    lap_if          = zeros(nLaps)      # intensity factor
    lap_start_sec   = zeros(nLaps)      # lap start times in seconds

    #time    = array(elapsed_time)
    #cadence = array(avg_cadence)
    #avg_hr  = array(avg_heart_rate)
    #max_hr  = array(max_heart_rate)
    #balance = array(balance)
    names1  = [    '', '  lap', '  avg', ' norm', 'avg',  'max',    '' ]
    names2  = [ 'lap', ' time', 'power', 'power', ' HR',  ' HR', ' IF' ]
    fmt     = "%8s"+"%10s"+"%8s"*5
    print >> OutStream, fmt % tuple(names1)
    print >> OutStream, fmt % tuple(names2)

    for i in range(nLaps):
        # count samples in this lap
        tBeg = (lap_start_time[i] - t0).total_seconds()
        tEnd = (lap_timestamp[i]  - t0).total_seconds()
        ii = nonzero( logical_and( time_idx >= tBeg,  \
                                   time_idx <  tEnd)  )[0]
        nPts = ii.size
        lap_start_sec[i]    = tBeg
        lap_avg_hr[i]       = average(heart_rate_vi[ii])
        lap_avg_power[i]    = average(power[time_idx[ii]])
        lap_norm_power[i]   = average( p30[time_idx[ii]]**4 )**(0.25)
        lap_if[i]           = lap_norm_power[i] / FTP

        # duration from lap metrics
        dur = (lap_timestamp[i] - lap_start_time[i]).total_seconds()
        mm = timer_time[i] // 60
        ss = timer_time[i]  % 60
        fmt = '%8d'+'%7i:%02i'+'%8i'*4 + '%8.2f'
        print >> OutStream, fmt \
                % ( i,
                    mm, ss,
                    avg_power[i],
                    lap_norm_power[i],
                    avg_heart_rate[i],
                    max_heart_rate[i],
                    lap_if[i]           )

        # plot lap results as continuous time signals
        lap_avg_hr_c    [time_idx[ii]]  = lap_avg_hr[i]
        lap_avg_power_c [time_idx[ii]]  = lap_avg_power[i]
        lap_norm_power_c[time_idx[ii]]  = lap_norm_power[i]

    #
    # ride-level results
    #
    print >> OutStream, 'ride-level results:'
    names1  = [    '', 'moving', '  avg', ' norm', 'avg',    '',  'Pw:' ]
    names2  = [ 'seg', '  time', 'power', 'power', ' HR', ' IF',  ' HR' ]
    fmt     = "%8s"+"%10s"+"%8s"*5
    print >> OutStream, fmt % tuple(names1)
    print >> OutStream, fmt % tuple(names2)

    # whole ride
    tBeg = (lap_start_time[0] - t0).total_seconds()
    tEnd = (lap_timestamp[-1] - t0).total_seconds()
    ii = nonzero( logical_and( time_idx >= tBeg,  \
                               time_idx <  tEnd)  )[0]
    nPts = ii.size
    dur = nPts  # sample rate 1 Hz
    hh  = dur // 3600
    mm  = (dur % 3600) // 60
    ss  = (dur % 3600) % 60
    all_avg_hr      = average(heart_rate_vi[ii])
    all_avg_power   = average(power_vi[ii])
    all_norm_power  = average( p30[time_idx[ii]]**4 )**(0.25)
    all_max_hr      = max(heart_rate_vi[ii])
    all_if          = all_norm_power / FTP
    # aerobic decoupling
    iiH1            = ii[0:nPts/2]
    h1_norm_power   = average( p30[time_idx[iiH1]]**4 )**(0.25)
    h1_avg_hr       = average(heart_rate_vi[iiH1])
    h1ef            = h1_norm_power / h1_avg_hr
    iiH2            = ii[nPts/2:]
    h2_norm_power   = average( p30[time_idx[iiH2]]**4 )**(0.25)
    h2_avg_hr       = average(heart_rate_vi[iiH2])
    h2ef            = h2_norm_power / h2_avg_hr
    all_pw_hr       = (h1ef-h2ef)/(h1ef)*100.0
    fmt = '%8s'+'%4i:%02i:%02i'+'%8i'*3 + '%8.2f' + '%8.1f'
    print >> OutStream, fmt \
            % ( 'all',
                hh, mm, ss,
                all_avg_power,
                all_norm_power,
                all_avg_hr,
                all_if,
                all_pw_hr          )

    # without end laps
    tBeg = (lap_start_time[1] - t0).total_seconds()
    tEnd = (lap_timestamp[-2] - t0).total_seconds()
    ii = nonzero( logical_and( time_idx >= tBeg,  \
                               time_idx <  tEnd)  )[0]
    nPts = ii.size
    dur = nPts  # sample rate 1 Hz
    hh  = dur // 3600
    mm  = (dur % 3600) // 60
    ss  = (dur % 3600) % 60
    mid_avg_hr      = average(heart_rate_vi[ii])
    mid_avg_power   = average(power_vi[ii])
    mid_norm_power  = average( p30[time_idx[ii]]**4 )**(0.25)
    mid_max_hr      = max(heart_rate_vi[ii])
    mid_if          = mid_norm_power / FTP
    # aerobic decoupling
    iiH1            = ii[0:nPts/2]
    h1_norm_power   = average( p30[time_idx[iiH1]]**4 )**(0.25)
    h1_avg_hr       = average(heart_rate_vi[iiH1])
    h1ef            = h1_norm_power / h1_avg_hr
    iiH2            = ii[nPts/2:]
    h2_norm_power   = average( p30[time_idx[iiH2]]**4 )**(0.25)
    h2_avg_hr       = average(heart_rate_vi[iiH2])
    h2ef            = h2_norm_power / h2_avg_hr
    mid_pw_hr       = (h1ef-h2ef)/(h1ef)*100.0
    fmt = '%5i-%02i'+'%4i:%02i:%02i'+'%8i'*3 + '%8.2f' + '%8.1f'
    print >> OutStream, fmt \
            % ( 1, nLaps-2,
                hh, mm, ss,
                mid_avg_power,
                mid_norm_power,
                mid_avg_hr,
                mid_if,
                mid_pw_hr          )

    print
    print

    #
    # time plot
    #

    import matplotlib.pyplot as plt
    import matplotlib.dates as md
    from matplotlib.dates import date2num, DateFormatter
    import datetime as dt
    base = dt.datetime(2014, 1, 1, 0, 0, 0)
    x = [base + dt.timedelta(seconds=t) for t in time_ci.astype('float')]
    x = date2num(x) # Convert to matplotlib format
    fig1, (ax0, ax1) = plt.subplots(nrows=2, sharex=True)
    ax0.plot_date( x, heart_rate_ci, 'r-', linewidth=1 );
    ax0.plot_date( x, lap_avg_hr_c,  'r-', linewidth=3 );
    ax0.set_yticks( h_zone_bounds, minor=False)
    x_laps  = [ base + dt.timedelta(seconds=t)   \
                for t in lap_start_sec.astype('float') ]
    x_laps  = date2num(x_laps)
    ax0.grid(True)
    ax0.set_ylabel('heart rate, BPM')
    ax1.plot_date( x, power,            'k-', linewidth=1 );
    ax1.plot_date( x, p30,              'm-', linewidth=1);
    ax1.plot_date( x, lap_avg_power_c,  'b-', linewidth=3);
    ax1.plot_date( x, lap_norm_power_c, 'g-', linewidth=3);
    ax1.xaxis.set_major_formatter(DateFormatter('%H:%M:%S'))
    ax1.set_yticks( p_zone_bounds, minor=False)
    ax1.grid(True)
    ax1.set_ylabel('power, watts')
    ax1.legend(['power', 'p30', 'lap_avg_power', 'lap_norm_power'],
                loc='upper left');
    for i in range(nLaps):
        ax0.axvline( x_laps[i], label=str(i+1) )
        ax1.axvline( x_laps[i], label=str(i+1) )
    fig1.autofmt_xdate()
    fig1.suptitle('Endurance Power Results', fontsize=20)
    fig1.tight_layout()
    fig1.subplots_adjust(hspace=0)   # Remove horizontal space between axes
    fig1.canvas.set_window_title(FitFilePath)
    plt.show()

    def ClosePlots():
        plt.close('all')

    return ClosePlots
コード例 #2
0
ファイル: zone_detect.py プロジェクト: j33433/FitFiles
def zone_detect(FitFilePath, ConfigFile=None, OutStream=sys.stdout):

    (FilePath, FitFileName) = os.path.split(FitFilePath)

    if ConfigFile is None:
        ConfigFile = FindConfigFile('', FilePath)
    if (ConfigFile is None) or (not os.path.exists(ConfigFile)):
        raise IOError('Configuration file not specified or found')

    #
    #   Parse the configuration file
    #
    from ConfigParser import ConfigParser
    config = ConfigParser()
    config.read(ConfigFile)
    print >> OutStream, 'reading config file ' + ConfigFile
    ThresholdPower = config.getfloat('power', 'ThresholdPower')
    ThresholdHR = config.getfloat('power', 'ThresholdHR')
    print >> OutStream, 'ThresholdPower: ', ThresholdPower
    print >> OutStream, 'ThresholdHR   : ', ThresholdHR

    from datetime import datetime
    from fitparse import Activity
    from activity_tools import extract_activity_signals

    required_signals = ['power']  # 'heart_rate' optional

    # get the signals
    activity = Activity(FitFilePath)
    signals = extract_activity_signals(activity)

    if not all(s in signals.keys() for s in required_signals):
        msg = 'required signals not in file'
        print >> OutStream, msg
        print >> OutStream, 'Signals required:'
        for s in required_signals:
            print >> OutStream, '   ' + s
        print >> OutStream, 'Signals contained:'
        for s in signals.keys():
            print >> OutStream, '   ' + s
        raise IOError(msg)

    hasHR = True if 'heart_rate' in signals.keys() else False

    # up-sample by 5x so that zone-skipping is not needed
    SampleRate = 5.0
    from numpy import arange, interp
    n = len(signals['power'])
    old_time = arange(n)
    nPts = int(n * SampleRate)  # 32-bit integer
    new_time = arange(nPts) / SampleRate
    power = interp(new_time, old_time, signals['power'])
    if hasHR:
        heart_rate = interp(new_time, old_time, signals['heart_rate'])

    # power zones from "Cyclist's Training Bible", 5th ed., by Joe Friel, p51
    FTP = ThresholdPower
    pZones = {
        1: [0, 0.55 * FTP],
        2: [0.55 * FTP, 0.75 * FTP],
        3: [0.75 * FTP, 0.90 * FTP],
        4: [0.90 * FTP, 1.05 * FTP],
        5: [1.05 * FTP, 1.20 * FTP],
        6: [1.20 * FTP, 1.50 * FTP],
        7: [1.50 * FTP, 2.50 * FTP]
    }

    # heart-rate zones from "Cyclist's Training Bible" 5th ed. by Joe Friel, p50
    FTHR = ThresholdHR
    hZones = {
        1: [0, 0.82 * FTHR],  # 1
        2: [0.82 * FTHR, 0.89 * FTHR],  # 2
        3: [0.89 * FTHR, 0.94 * FTHR],  # 3
        4: [0.94 * FTHR, 1.00 * FTHR],  # 4
        5: [1.00 * FTHR, 1.03 * FTHR],  # 5a
        6: [1.03 * FTHR, 1.06 * FTHR],  # 5b
        7: [1.07 * FTHR, 1.15 * FTHR]
    }  # 5c

    def LocateZone(x, zones):
        Z = 1
        if x >= zones[2][0]: Z = 2
        if x >= zones[3][0]: Z = 3
        if x >= zones[4][0]: Z = 4
        if x >= zones[5][0]: Z = 5
        if x >= zones[6][0]: Z = 6
        if x >= zones[7][0]: Z = 7
        return Z

    # define boxcar averages used to test for upward transition out of
    # indicated zone.
    fpZ1 = ForwardBoxcarAverage(power, window=90, SampleRate=SampleRate)
    fpZ2 = ForwardBoxcarAverage(power, window=60, SampleRate=SampleRate)
    fpZ3 = ForwardBoxcarAverage(power, window=45, SampleRate=SampleRate)
    fpZ4 = ForwardBoxcarAverage(power, window=30, SampleRate=SampleRate)
    fpZ5 = ForwardBoxcarAverage(power, window=15, SampleRate=SampleRate)
    fpZ6 = ForwardBoxcarAverage(power, window=5, SampleRate=SampleRate)
    # fpZ7 not needed

    # assemble these into a dictionary:
    # so that I could test
    #     if LocateZone( FBoxCars[CurrentZone][i], pZones )
    #         > CurrentZone:
    FBoxCars = {
        1: fpZ1,
        2: fpZ2,
        3: fpZ3,
        4: fpZ4,
        5: fpZ5,
        6: fpZ6
    }  # Z7 not needed

    # define boxcar averages used to test for downward transition into
    # indicated zone.
    cpZ1 = CenteredBoxcarAverage(power, window=60, SampleRate=SampleRate)
    cpZ2 = CenteredBoxcarAverage(power, window=45, SampleRate=SampleRate)
    cpZ3 = CenteredBoxcarAverage(power, window=30, SampleRate=SampleRate)
    cpZ4 = CenteredBoxcarAverage(power, window=15, SampleRate=SampleRate)
    cpZ5 = CenteredBoxcarAverage(power, window=7, SampleRate=SampleRate)
    cpZ6 = CenteredBoxcarAverage(power, window=3, SampleRate=SampleRate)
    # fpZ7 not needed

    # assemble these into a dictionary:
    # so that I could test
    #     if LocateZone( CBoxCars[CurrentZone][i], pZones )
    #         > CurrentZone:
    CBoxCars = {
        1: cpZ1,
        2: cpZ2,
        3: cpZ3,
        4: cpZ4,
        5: cpZ5,
        6: cpZ6
    }  # Z7 not needed

    from numpy import array, arange, append, zeros, cumsum, average

    cp2 = zeros(nPts)
    fboxpower = zeros(nPts)
    cboxpower = zeros(nPts)
    zone = zeros(nPts)
    zone_mid = zeros(nPts)
    CurrentZone = 1

    # create a phaseless, lowpass-filtered signal for downward transitions
    # see
    #   https://docs.scipy.org/doc/scipy/reference/signal.html
    from scipy import signal
    poles = 4
    cutoff = 0.1  # Hz
    Wn = cutoff / (SampleRate / 2)
    PadLen = int(SampleRate / cutoff)
    b, a = signal.butter(poles, Wn, btype='lowpass')
    # lpfpower    = signal.filtfilt(b, a, power, padlen=PadLen)

    #   calculate zone midpoints for plotting
    ZoneMidPoint = {}  # empty dictionary
    ZoneMidPoint[1] = (pZones[1][0] + pZones[1][1]) / 2
    ZoneMidPoint[2] = (pZones[2][0] + pZones[2][1]) / 2
    ZoneMidPoint[3] = (pZones[3][0] + pZones[3][1]) / 2
    ZoneMidPoint[4] = (pZones[4][0] + pZones[4][1]) / 2
    ZoneMidPoint[5] = (pZones[5][0] + pZones[5][1]) / 2
    ZoneMidPoint[6] = (pZones[6][0] + pZones[6][1]) / 2
    ZoneMidPoint[7] = (pZones[7][0] + pZones[7][1]) / 2

    for i, p in zip(range(nPts), power):

        #   compute the centered 3-second power
        #raise RuntimeError("need to account for sample rate in cp2")
        sr = int(SampleRate)
        if i == 0:
            cp2[i] = power[i]
        elif i < 2 * sr:
            cp2[i] = average(power[0:i])
        elif i > nPts - 2 * sr:
            cp2[i] = average(power[i - sr:])
        else:
            cp2[i] = average(power[i - sr:i + sr + 1])

        #   upward transition
        cz = CurrentZone  # short name
        if cz < 7:
            tz = 7  # Test Zone
            while tz > cz:
                if  (LocateZone(           cp2[i], pZones ) >= tz) \
                  & (LocateZone( FBoxCars[tz-1][i], pZones ) >= tz):
                    CurrentZone = tz
                    zone[i] = CurrentZone
                    break
                tz -= 1
        #   downward transition. Avoid 2nd test if in Z1.
        #   use centered-boxcar average to avoid getting "trapped"
        #   in low zones.
        if cz > 1:
            tz = cz - 1
            while tz >= 1:
                if  (LocateZone(          cp2[i], pZones ) <= tz) \
                  & (LocateZone( CBoxCars[tz][i], pZones ) <= tz) \
                  & (LocateZone( FBoxCars[tz][i], pZones ) <= tz):
                    CurrentZone = tz
                    zone[i] = CurrentZone
                    break
                tz -= 1

        # the filtered power comes from CurrentZone after any transition
        fboxpower[i] = FBoxCars[min(CurrentZone, 6)][i]
        cboxpower[i] = CBoxCars[max(CurrentZone - 1, 1)][i]

        #   calculate zone midpoints for plotting
        zone_mid[i] = ZoneMidPoint[CurrentZone]

    # get zone bounds for plotting
    p_zone_bounds = [
        pZones[1][0], pZones[2][0], pZones[3][0], pZones[4][0], pZones[5][0],
        pZones[6][0], pZones[7][0], pZones[7][1]
    ]

    h_zone_bounds = [
        0.4 * FTHR,  # better plotting
        hZones[2][0],
        hZones[3][0],
        hZones[4][0],
        hZones[5][0],
        hZones[6][0],
        hZones[7][0],
        hZones[7][1]
    ]

    ############################################################
    #                  plotting                                #
    ############################################################

    #
    #   extract lap times
    #
    from activity_tools import extract_activity_laps
    activity = Activity(FitFilePath)
    laps = extract_activity_laps(activity)
    lap_start_time = laps['start_time']  # datetime object
    lap_timestamp = laps['timestamp']
    nLaps = len(lap_start_time)
    t0 = signals['metadata']['timestamp']
    lap_start_sec = zeros(nLaps)  # lap start times in seconds
    for i in range(nLaps):
        tBeg = (lap_start_time[i] - t0).total_seconds()
        tEnd = (lap_timestamp[i] - t0).total_seconds()
        lap_start_sec[i] = tBeg

    # time plot
    import matplotlib.pyplot as plt
    import matplotlib.dates as md
    from matplotlib.dates import date2num, DateFormatter
    import datetime as dt
    base = dt.datetime(2014, 1, 27, 0, 0, 0)
    x = [base + dt.timedelta(seconds=t) for t in new_time]
    x = date2num(x)  # Convert to matplotlib format
    x_laps  = [ base + dt.timedelta(seconds=t)   \
                for t in lap_start_sec.astype('float') ]
    x_laps = date2num(x_laps)
    if hasHR:
        fig1, (ax0, ax1) = plt.subplots(nrows=2, sharex=True)
        ax0.plot_date(x, heart_rate, 'r-', linewidth=3)
        ax0.set_yticks(h_zone_bounds, minor=False)
        ax0.grid(True)
        ax0.set_title('heart rate, BPM')
        for i in range(nLaps):
            ax0.axvline(x_laps[i], label=str(i + 1))
    else:
        fig1, ax1 = plt.subplots(nrows=1, sharex=True)
    ax1.plot_date(x, power, 'k-', linewidth=1)
    ax1.plot_date(x, fboxpower, 'm-', linewidth=1)
    ax1.plot_date(x, cp2, 'r.', markersize=4)
    ax1.plot_date(x, cboxpower, 'b-', linewidth=1)
    ax1.plot_date(x, zone_mid, 'g-', linewidth=3)
    ax1.xaxis.set_major_formatter(DateFormatter('%H:%M:%S'))
    ax1.set_yticks(p_zone_bounds, minor=False)
    ax1.grid(True)
    ax1.set_title('power, watts')
    ax1.legend(['power', 'FBoxCar', 'cp2', 'CBoxCar', 'zone mid'],
               loc='upper left')
    for i in range(nLaps):
        ax1.axvline(x_laps[i], label=str(i + 1))
    fig1.autofmt_xdate()
    fig1.suptitle('Power Zone Detection', fontsize=20)
    fig1.tight_layout()
    fig1.canvas.set_window_title(FitFilePath)
    plt.show()

    # better histogram plot with control of counts
    from numpy import histogram
    PowerCounts, PowerBins = histogram(power, bins=p_zone_bounds)
    ZoneCounts, ZoneBins = histogram(zone_mid, bins=p_zone_bounds)
    fig2, ax = plt.subplots()
    bar_width = 0.35
    opacity = 0.4
    #error_config = {'ecolor': '0.3'}
    zone_ints = arange(7) + 1
    LogY = True
    rects1 = ax.bar(zone_ints,
                    PowerCounts / SampleRate / 60,
                    bar_width,
                    alpha=opacity,
                    color='b',
                    log=LogY,
                    label='raw power')
    rects2 = ax.bar(zone_ints + bar_width,
                    ZoneCounts / SampleRate / 60,
                    bar_width,
                    alpha=opacity,
                    color='r',
                    log=LogY,
                    label='detected zone')
    ax.set_xlabel('Zone')
    ax.set_ylabel('minutes')
    ax.set_title('Zone Detection Histogram')
    ax.set_xticks(zone_ints + bar_width / 2)
    ax.set_xticklabels(('Rec', 'End', 'Tmp', 'Thr', 'VO2', 'An', 'NM'))
    ax.legend()
    fig2.tight_layout()
    fig2.canvas.set_window_title(FitFilePath)
    plt.show()

    # formatted print of histogram
    print >> OutStream, 'Power Zone Histogram:'
    for i in range(7):
        dur = ZoneCounts[i] / SampleRate
        pct = dur / sum(ZoneCounts / SampleRate) * 100
        hh = dur // 3600
        mm = (dur % 3600) // 60
        ss = (dur % 3600) % 60
        print >> OutStream, '    Zone %i: %2i:%02i:%02i (%2i%%)' \
                            % (i+1, hh, mm, ss, pct)
    dur = sum(ZoneCounts) / SampleRate
    hh = dur // 3600
    mm = (dur % 3600) // 60
    ss = (dur % 3600) % 60
    print >> OutStream, '     total: %2i:%02i:%02i' % (hh, mm, ss)

    def ClosePlots():
        plt.close('all')

    return ClosePlots
コード例 #3
0
def pwhr_transfer_function(FitFilePath, ConfigFile=None, OutStream=sys.stdout):

    # this needs to stay INSIDE the function or bad things happen
    import matplotlib.pyplot as plt

    (FilePath, FitFileName) = os.path.split(FitFilePath)

    if ConfigFile is None:
        ConfigFile = FindConfigFile('', FilePath)
    if (ConfigFile is None) or (not os.path.exists(ConfigFile)):
        raise IOError('Configuration file not specified or found')

    #
    #   Parse the configuration file
    #
    if type(ConfigFile) != type(ConfigParser()):
        if (ConfigFile is None) or (not os.path.exists(ConfigFile)):
            raise IOError('Configuration file not specified or found')
        config = ConfigParser()
        config.read(ConfigFile)
        print >> OutStream, 'reading config file ' + ConfigFile
    else:
        config = ConfigFile
    WeightEntry = config.getfloat('user', 'weight')
    WeightToKg = config.getfloat('user', 'WeightToKg')
    weight = WeightEntry * WeightToKg
    age = config.getfloat('user', 'age')
    EndurancePower = config.getfloat('power', 'EndurancePower')
    ThresholdPower = config.getfloat('power', 'ThresholdPower')
    EnduranceHR = config.getfloat('power', 'EnduranceHR')
    ThresholdHR = config.getfloat('power', 'ThresholdHR')
    HRTimeConstant = config.getfloat('power', 'HRTimeConstant')
    HRDriftRate = config.getfloat('power', 'HRDriftRate')
    print >> OutStream, 'WeightEntry    : ', WeightEntry
    print >> OutStream, 'WeightToKg     : ', WeightToKg
    print >> OutStream, 'weight         : ', weight
    print >> OutStream, 'age            : ', age
    print >> OutStream, 'EndurancePower : ', EndurancePower
    print >> OutStream, 'ThresholdPower : ', ThresholdPower
    print >> OutStream, 'EnduranceHR    : ', EnduranceHR
    print >> OutStream, 'ThresholdHR    : ', ThresholdHR
    print >> OutStream, 'HRTimeConstant : ', HRTimeConstant
    print >> OutStream, 'HRDriftRate    : ', HRDriftRate

    # power zones from "Cyclist's Training Bible", 5th ed., by Joe Friel, p51
    FTP = ThresholdPower
    pZones = {
        1: [0, 0.55 * FTP],
        2: [0.55 * FTP, 0.75 * FTP],
        3: [0.75 * FTP, 0.90 * FTP],
        4: [0.90 * FTP, 1.05 * FTP],
        5: [1.05 * FTP, 1.20 * FTP],
        6: [1.20 * FTP, 1.50 * FTP],
        7: [1.50 * FTP, 2.50 * FTP]
    }

    # heart-rate zones from "Cyclist's Training Bible" 5th ed. by Joe Friel, p50
    FTHR = ThresholdHR
    hZones = {
        1: [0, 0.82 * FTHR],  # 1
        2: [0.82 * FTHR, 0.89 * FTHR],  # 2
        3: [0.89 * FTHR, 0.94 * FTHR],  # 3
        4: [0.94 * FTHR, 1.00 * FTHR],  # 4
        5: [1.00 * FTHR, 1.03 * FTHR],  # 5a
        6: [1.03 * FTHR, 1.06 * FTHR],  # 5b
        7: [1.07 * FTHR, 1.15 * FTHR]
    }  # 5c

    # get zone bounds for plotting
    p_zone_bounds = [
        pZones[1][0], pZones[2][0], pZones[3][0], pZones[4][0], pZones[5][0],
        pZones[6][0], pZones[7][0], pZones[7][1]
    ]

    h_zone_bounds = [
        0.4 * FTHR,  # better plotting
        hZones[2][0],
        hZones[3][0],
        hZones[4][0],
        hZones[5][0],
        hZones[6][0],
        hZones[7][0],
        hZones[7][1]
    ]

    from fitparse import Activity
    from activity_tools import extract_activity_signals

    required_signals = ['power', 'heart_rate']

    # get the signals
    activity = Activity(FitFilePath)
    signals = extract_activity_signals(activity, resample='existing')

    if not all(s in signals.keys() for s in required_signals):
        msg = 'required signals not in file'
        print >> OutStream, msg
        print >> OutStream, 'Signals required:'
        for s in required_signals:
            print >> OutStream, '   ' + s
        print >> OutStream, 'Signals contained:'
        for s in signals.keys():
            print >> OutStream, '   ' + s
        raise IOError(msg)

    # resample to constant-increment (1 Hz) with zeros at missing samples
    time_idx = signals['time'].astype('int')
    power_vi = signals['power']
    heart_rate_vi = signals['heart_rate']
    nScans = time_idx[-1] + 1
    time_ci = np.arange(nScans)
    power = np.zeros(nScans)
    power[time_idx] = power_vi
    heart_rate_ci = np.zeros(nScans)
    heart_rate_ci[time_idx] = heart_rate_vi

    # compute the 30-second, moving-average power signal.
    p30 = BackwardMovingAverage(power)
    '''
    # compute moving time
        moving_time would contain data in the gaps--repeats of the "stopped"
        time until it resumes:
            >>> time_idx
            array([ 0,  1,  2,          5,  6,  7,         10, 11, 12])
            >>> time_ci
            array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12])
            >>> moving_time
            array([ 0,  1,  2,  2,  2,  3,  4,  5,  5,  5,  6,  7,  8])
    '''
    moving_time = np.zeros(nScans).astype('int')
    idx = 0
    for i in time_ci[0:-1]:
        moving_time[i] = idx
        if time_idx[idx + 1] == i + 1:
            idx += 1
    moving_time[nScans - 1] = idx

    # Calculate running normalized power and TSS.
    norm_power = np.zeros(nScans)
    TSS = np.zeros(nScans)
    for i in range(1, nScans):
        ii = np.nonzero(time_idx <= i)[0]
        norm_power[i] = np.average(p30[time_idx[ii]]**4)**(0.25)
        TSS[i] = moving_time[i] / 36 * (norm_power[i] / FTP)**2

    #
    # simulate the heart rate
    #
    SampleRate = 1.0
    tau = HRTimeConstant  # 63.0 seconds
    PwHRTable = np.array([
        [0, 0.50 * FTHR],  # Active resting HR
        [0.55 * FTP, 0.70 * FTHR],  # Recovery
        [0.70 * FTP, 0.82 * FTHR],  # Aerobic threshold
        [1.00 * FTP, FTHR],  # Functional threshold
        [1.20 * FTP, 1.03 * FTHR],  # Aerobic capacity
        [1.50 * FTP, 1.06 * FTHR]
    ])  # Max HR

    def heartrate_dot(HR, t):
        i = min(int(t * SampleRate), nScans - 1)
        HRp = np.interp(power[i], PwHRTable[:, 0], PwHRTable[:, 1])
        HRt = HRp + HRDriftRate * TSS[i]
        return (HRt - HR) / tau

    heart_rate_sim = odeint(heartrate_dot, heart_rate_ci[0], time_ci)
    err     = np.squeeze( heart_rate_sim ) \
            - np.squeeze( heart_rate_ci  )
    RMSError = np.sqrt(np.average(err[time_idx]**2))
    print >> OutStream, 'Average  measured HR  : %7i BPM' \
                        % np.average(heart_rate_ci[time_idx])
    print >> OutStream, 'Average simulated HR  : %7i BPM' \
                        % np.average(heart_rate_sim[time_idx])
    print >> OutStream, 'RMS error             : %7i BPM' % RMSError

    #
    # Estimate better values for FTHR and HRDriftRate
    #
    coef = np.polyfit(TSS[time_idx],
                      -err[time_idx],
                      deg=1,
                      w=heart_rate_ci[time_idx] - 0.50 * FTHR)
    slope = coef[0]
    offset = coef[1]
    NewThresholdHR = offset + ThresholdHR
    NewHRDriftRate = slope + HRDriftRate
    print >> OutStream, 'Estimated ThresholdHR : %7.1f BPM' \
                        % NewThresholdHR
    print >> OutStream, 'Estimated HRDriftRate : %7.4f BPM/TSS' \
                        % NewHRDriftRate

    # -------------- debug ---------------
    print 'coef = ', coef
    print 'TSS = ', TSS[-1]
    hh = moving_time[-1] // 3600
    mm = (moving_time[-1] % 3600) // 60
    ss = (moving_time[-1] % 3600) % 60
    print 'moving time: %4i:%02i:%02i' % (hh, mm, ss)
    print 'normalized power:', norm_power[-1], norm_power.max()
    CrossPlotFig = plt.figure()
    sc = plt.scatter(TSS[time_idx], -err[time_idx], s=5)
    plt.title('Simulation Error Vs TSS')
    plt.xlabel('TSS')
    plt.ylabel('BPM')
    plt.grid(b=True, which='major', axis='both')
    a = plt.axis()
    #plt.axis([ 0, a[1], 0, a[3] ])
    y_fit = slope * TSS[time_idx] + offset
    plt.plot(TSS[time_idx], y_fit, 'k-')
    plt.show()

    #
    #   extract lap results
    #
    from activity_tools import extract_activity_laps
    activity = Activity(FitFilePath)
    laps = extract_activity_laps(activity)
    lap_start_time = laps['start_time']  # datetime object
    lap_timestamp = laps['timestamp']
    nLaps = len(lap_start_time)
    t0 = signals['metadata']['timestamp']
    lap_start_sec = np.zeros(nLaps)  # lap start times in seconds
    for i in range(nLaps):
        tBeg = (lap_start_time[i] - t0).total_seconds()
        tEnd = (lap_timestamp[i] - t0).total_seconds()
        lap_start_sec[i] = tBeg

    #
    # time plot
    #

    import matplotlib.dates as md
    from matplotlib.dates import date2num, DateFormatter
    import datetime as dt
    base = dt.datetime(2014, 1, 1, 0, 0, 0)
    x = [base + dt.timedelta(seconds=t) for t in time_ci.astype('float')]
    x = date2num(x)  # Convert to matplotlib format
    x_laps  = [ base + dt.timedelta(seconds=t)   \
                for t in lap_start_sec.astype('float') ]
    x_laps = date2num(x_laps)
    fig1, (ax0, ax1) = plt.subplots(nrows=2, sharex=True)
    ax0.plot_date(x, heart_rate_ci, 'r-', linewidth=1)
    ax0.plot_date(x, heart_rate_sim, 'm-', linewidth=3)
    ax0.set_yticks(h_zone_bounds, minor=False)
    ax0.grid(True)
    ax0.legend(['measured', 'simulated'], loc='upper left')
    ax0.set_title('heart rate, BPM')
    ax1.plot_date(x, power, 'k-', linewidth=1)
    ax1.plot_date(x, p30, 'b-', linewidth=3)
    ax1.xaxis.set_major_formatter(DateFormatter('%H:%M:%S'))
    ax1.set_yticks(p_zone_bounds, minor=False)
    ax1.grid(True)
    ax1.set_title('power, watts')
    for i in range(nLaps):
        ax0.axvline(x_laps[i], label=str(i + 1))
        ax1.axvline(x_laps[i], label=str(i + 1))
    fig1.autofmt_xdate()
    ax1.legend(['power', 'p30'], loc='upper left')
    fig1.suptitle('Pw:HR Transfer Function', fontsize=20)
    fig1.tight_layout()
    fig1.canvas.set_window_title(FitFilePath)
    fig1.subplots_adjust(hspace=0)  # Remove horizontal space between axes
    plt.show()

    def ClosePlots():
        plt.close('all')

    return ClosePlots
コード例 #4
0
def saddle_endurance_anls(FitFilePath, ConfigFile=None, OutStream=sys.stdout):

    (FilePath, FitFileName) = os.path.split(FitFilePath)

    # no config file needed

    from datetime import datetime
    from fitparse import Activity
    from activity_tools import extract_activity_signals
    import numpy as np

    required_signals = ['power', 'cadence']

    # get the signals
    activity = Activity(FitFilePath)
    signals = extract_activity_signals(activity)

    if not all(s in signals.keys() for s in required_signals):
        msg = 'required signals not in file'
        print >> OutStream, msg
        print >> OutStream, 'Signals required:'
        for s in required_signals:
            print >> OutStream, '   ' + s
        print >> OutStream, 'Signals contained:'
        for s in signals.keys():
            print >> OutStream, '   ' + s
        raise IOError(msg)

    SampleRate = 1.0
    cadence = signals['cadence']
    elapsed_time = signals['time']
    nPts = len(cadence)

    cad_fwd_min_3_8 = FwdRunningMinimum(cadence, wBeg=3, wEnd=8)
    cad_fwd_min_1_8 = FwdRunningMinimum(cadence, wBeg=1, wEnd=8)

    STANDING = 0
    SEATED = 1
    state = STANDING

    CThr = 40.0
    seated_state = np.zeros(nPts)

    for i in range(nPts):
        if state == STANDING:
            if cadence[i] >= CThr and cad_fwd_min_1_8[i] >= CThr:
                state = SEATED
        else:  # state==SEATED:
            if cadence[i] < CThr and cad_fwd_min_3_8[i] < CThr:
                state = STANDING
        seated_state[i] = state

    #
    #   Determine seated segment durations
    #
    iiUp = np.nonzero(seated_state[1:] - seated_state[0:-1] == 1)[0]
    iiDn = np.nonzero(seated_state[1:] - seated_state[0:-1] == -1)[0]
    if iiUp[-1] < iiDn[-1]:
        seg_durtns = np.zeros(len(iiDn))
        seg_starts = np.zeros(len(iiDn)).astype('int')
        seg_stops = np.zeros(len(iiDn)).astype('int')
    else:
        seg_durtns = np.zeros(len(iiDn) + 1)
        seg_starts = np.zeros(len(iiDn) + 1).astype('int')
        seg_stops = np.zeros(len(iiDn) + 1).astype('int')
    if iiDn[0] < iiUp[0]:  # begins seated
        seg_durtns[0] = iiDn[0]
        seg_starts[0] = 0
        seg_stops[0] = iiDn[0]
        if iiUp[-1] > iiDn[-1]:  #   Ends seated
            seg_durtns[1:-1] = iiDn[1:] - iiUp[0:-1]
            seg_starts[1:-1] = iiUp[0:-1]
            seg_stops[1:-1] = iiDn[1:]
            seg_durtns[-1] = nPts - iiUp[-1]
            seg_starts[-1] = iiUp[-1]
            seg_stops[-1] = nPts
        else:  #   ends standing
            seg_durtns[1:] = iiDn[1:] - iiUp
            seg_starts[1:] = iiUp
            seg_stops[1:] = iiDn[1:]
    elif iiUp[0] < iiDn[0]:  # begins standing
        if iiUp[-1] > iiDn[-1]:  #   ends seated
            seg_durtns[0:-1] = iiDn - iiUp[0:-1]
            seg_starts[0:-1] = iiUp[0:-1]
            seg_stops[0:-1] = iiDn
            seg_durtns[-1] = nPts - iiUp[-1]
            seg_starts[-1] = iiUp[-1]
            seg_stops[-1] = nPts
        else:  #   ends standing
            seg_durtns = iiDn - iiUp
            seg_starts = iiUp
            seg_stops = iiDn
    else:
        raise RuntimeError("shouldn't be able to reach this code.")

    #
    #   Formatted print of results for all segments
    #

    # overall results
    dur = nPts / SampleRate
    hh = dur // 3600
    mm = (dur % 3600) // 60
    ss = (dur % 3600) % 60
    print >> OutStream, 'total time     : %2i:%02i:%02i' % (hh, mm, ss)
    iSeat = np.nonzero(seated_state == 1)[0]
    pct = len(iSeat) / float(nPts) * 100.0
    dur = len(iSeat) / SampleRate
    hh = dur // 3600
    mm = (dur % 3600) // 60
    ss = (dur % 3600) % 60
    print >> OutStream, 'seated time    : %2i:%02i:%02i (%2i%%))' % (hh, mm,
                                                                     ss, pct)
    iStnd = np.nonzero(seated_state == 0)[0]
    pct = len(iStnd) / float(nPts) * 100.0
    dur = len(iStnd) / SampleRate
    hh = dur // 3600
    mm = (dur % 3600) // 60
    ss = (dur % 3600) % 60
    print >> OutStream, 'standing time  : %2i:%02i:%02i (%2i%%))' % (hh, mm,
                                                                     ss, pct)

    # segment results
    nSeg = len(seg_durtns)
    print >> OutStream, 'standing segments:'
    names = ['segment', 'start', 'stop', 'duration']
    fmt = "%12s" + "%10s" * 3
    print >> OutStream, fmt % tuple(names)
    for i in range(nSeg):
        Beg = elapsed_time[seg_starts[i]]
        hhBeg = Beg // 3600
        mmBeg = (Beg % 3600) // 60
        ssBeg = (Beg % 3600) % 60
        End = elapsed_time[seg_stops[i]]
        hhEnd = End // 3600
        mmEnd = (End % 3600) // 60
        ssEnd = (End % 3600) % 60
        dur = seg_durtns[i] / SampleRate
        hhDur = dur // 3600
        mmDur = (dur % 3600) // 60
        ssDur = (dur % 3600) % 60
        DurPlus = '' if hhDur == 0 else '+%ih' % (hhDur)
        fmt = '%12d' + '%4i:%02i:%02i' + '%4i:%02i:%02i' + '%7i:%02i' + '%s'
        print >> OutStream, fmt \
                % (i, hhBeg, mmBeg, ssBeg,
                      hhEnd, mmEnd, ssEnd,
                      mmDur, ssDur, DurPlus)

    #
    # best hour saddle endurance
    #
    '''
    Find the longest segments that together total one hour,
    and compute the average duration to serve as a metric for
    the ride.
    '''

    # time limit: 15 minutes less than end for short rides
    TimeLimit = min(3600.0, (nPts / SampleRate - 15 * 60))

    # list of indices for longest durations
    def GetSegment(i):
        return seg_durtns[i]

    indx = range(nSeg)
    indx.sort(reverse=True, key=GetSegment)

    # compute average and print
    print >> OutStream, 'best-hour segments:'
    names = ['segment', 'start', 'stop', 'duration']
    fmt = "%12s" + "%10s" * 3
    print >> OutStream, fmt % tuple(names)
    TotalTime = 0.0
    i = 0
    while TotalTime < TimeLimit and i < nSeg:
        Beg = elapsed_time[seg_starts[indx[i]]]
        hhBeg = Beg // 3600
        mmBeg = (Beg % 3600) // 60
        ssBeg = (Beg % 3600) % 60
        End = elapsed_time[seg_stops[indx[i]]]
        hhEnd = End // 3600
        mmEnd = (End % 3600) // 60
        ssEnd = (End % 3600) % 60
        dur = seg_durtns[indx[i]] / SampleRate
        hhDur = dur // 3600
        mmDur = (dur % 3600) // 60
        ssDur = (dur % 3600) % 60
        DurPlus = '' if hhDur == 0 else '+%ih' % (hhDur)
        fmt = '%12d' + '%4i:%02i:%02i' + '%4i:%02i:%02i' + '%7i:%02i' + '%s'
        print >> OutStream, fmt \
                % (indx[i], hhBeg, mmBeg, ssBeg,
                            hhEnd, mmEnd, ssEnd,
                            mmDur, ssDur, DurPlus)
        TotalTime += dur
        i += 1
    BestAve = TotalTime / float(i)
    hh = BestAve // 3600
    mm = (BestAve % 3600) // 60
    ss = (BestAve % 3600) % 60
    DurPlus = '' if hh == 0 else '+%ih' % (hh)
    print >> OutStream, '        BEST ONE-HOUR AVERAGE:  %7i:%02i%s' \
            % (mm, ss, DurPlus)

    ############################################################
    #                  plotting                                #
    ############################################################

    #
    #   extract lap times
    #
    from activity_tools import extract_activity_laps
    activity = Activity(FitFilePath)
    laps = extract_activity_laps(activity)
    lap_start_time = laps['start_time']  # datetime object
    lap_timestamp = laps['timestamp']
    nLaps = len(lap_start_time)
    t0 = signals['metadata']['timestamp']
    lap_start_sec = np.zeros(nLaps)  # lap start times in seconds
    for i in range(nLaps):
        tBeg = (lap_start_time[i] - t0).total_seconds()
        tEnd = (lap_timestamp[i] - t0).total_seconds()
        lap_start_sec[i] = tBeg

    # time plot
    import matplotlib.pyplot as plt
    import matplotlib.dates as md
    from matplotlib.dates import date2num, DateFormatter
    import datetime as dt
    base = dt.datetime(2014, 1, 27, 0, 0, 0)
    x = [base + dt.timedelta(seconds=t) for t in elapsed_time]
    x = date2num(x)  # Convert to matplotlib format
    x_laps  = [ base + dt.timedelta(seconds=t)   \
                for t in lap_start_sec.astype('float') ]
    x_laps = date2num(x_laps)
    fig, (ax0, ax1, ax2) = plt.subplots(nrows=3, sharex=True)
    ax0.plot_date(x, signals['power'], 'b-', linewidth=1)
    ax0.grid(True)
    ax0.set_ylabel('power, W')
    ax0.set_title('Saddle Endurance')
    ax1.plot_date(x, signals['cadence'], 'g-', linewidth=1)
    ax1.plot_date(x, cad_fwd_min_3_8, 'm-', linewidth=1)
    ax1.plot_date(x, cad_fwd_min_1_8, 'brown', linestyle='-', linewidth=1)
    ax1.grid(True)
    ax1.set_ylabel('cadence, RPM')
    ax1.legend(['cadence', 'cad_fwd_min_3_8', 'cad_fwd_min_1_8'],
               loc='upper left')
    ax2.plot_date(x, seated_state, 'r-', linewidth=3)
    ax2.xaxis.set_major_formatter(DateFormatter('%H:%M:%S'))
    ax2.grid(True)
    ax2.set_ylabel('seated')
    ax2.set_yticks([0, 1])
    ax2.set_yticklabels(('standing', 'seated'))
    for i in range(nLaps):
        ax0.axvline(x_laps[i], label=str(i + 1))
        ax1.axvline(x_laps[i], label=str(i + 1))
        ax2.axvline(x_laps[i], label=str(i + 1))
    fig.canvas.set_window_title(FitFilePath)
    fig.tight_layout()
    fig.subplots_adjust(hspace=0)  # Remove horizontal space between axes
    plt.show()

    def ClosePlots():
        plt.close('all')

    return ClosePlots
コード例 #5
0
ファイル: interval_laps.py プロジェクト: j33433/FitFiles
def interval_laps(FitFilePath, ConfigFile=None, OutStream=sys.stdout):

    (FilePath, FitFileName) = os.path.split(FitFilePath)

    if ConfigFile is None:
        ConfigFile = FindConfigFile('', FilePath)
    if (ConfigFile is None) or (not os.path.exists(ConfigFile)):
        raise IOError('Configuration file not specified or found')

    #
    #   Parse the configuration file
    #
    from ConfigParser import ConfigParser
    config = ConfigParser()
    config.read(ConfigFile)
    print >> OutStream, 'reading config file ' + ConfigFile
    ThresholdPower = config.getfloat('power', 'ThresholdPower')
    ThresholdHR = config.getfloat('power', 'ThresholdHR')
    print >> OutStream, 'ThresholdPower: ', ThresholdPower
    print >> OutStream, 'ThresholdHR   : ', ThresholdHR
    FTP = ThresholdPower
    '''
    # Get Records of type 'lap'
    # types: [ 'record', 'lap', 'event', 'session', 'activity', ... ]
    records = activity.get_records_by_type('lap')
    current_record_number = 0

    elapsed_time    = []
    avg_heart_rate  = []
    avg_power       = []
    avg_cadence     = []
    max_heart_rate  = []
    balance         = []

    FirstIter   = True

    for record in records:

        # Print record number
        current_record_number += 1
        #print >> OutStream, (" Record #%d " % current_record_number).center(40, '-')

        # Get the list of valid fields on this record
        valid_field_names = record.get_valid_field_names()

        for field_name in valid_field_names:
            # Get the data and units for the field
            field_data = record.get_data(field_name)
            field_units = record.get_units(field_name)

            ## Print what we've got!
            #if field_units:
            #    print >> OutStream, " * %s: %s %s" % (field_name, field_data, field_units)
            #else:
            #    print >> OutStream, " * %s: %s" % (field_name, field_data)

            if 'timestamp' in field_name:
                if FirstIter:
                    t0  = field_data    # datetime
                    t   = t0
                    FirstIter   = False
                else:
                    t   = field_data    # datetime

            if 'total_timer_time' in field_name:
                elapsed_time.append( field_data )

            if 'avg_power' in field_name:
                avg_power.append( field_data )

            # avg_heart_rate is in a lap record
            if 'avg_heart_rate' in field_name:
                avg_heart_rate.append(field_data)

            if 'max_heart_rate' in field_name:
                max_heart_rate.append(field_data)

            if 'avg_cadence' in field_name:
                avg_cadence.append(field_data)

            if 'left_right_balance' in field_name:
                balance.append(field_data)

    from numpy import nonzero, array, arange, zeros, average, logical_and

    power   = array(avg_power)
    time    = array(elapsed_time)
    cadence = array(avg_cadence)
    avg_hr  = array(avg_heart_rate)
    max_hr  = array(max_heart_rate)
    if len(balance) == 0:
        balance = zeros(len(power))
    else:
        balance = array(balance)
    '''

    #
    #   extract lap results
    #
    from fitparse import Activity
    from activity_tools import extract_activity_laps
    import numpy as np
    activity = Activity(FitFilePath)
    laps = extract_activity_laps(activity)
    power = laps['power']
    time = laps['total_timer_time']
    cadence = laps['cadence']
    avg_hr = laps['avg_hr']
    max_hr = laps['max_hr']
    balance = laps['balance']

    #   All high-intensity intervals:    >80 %FTP
    ii = np.nonzero(power >= 0.80 * FTP)[0]  # index array
    if len(ii) > 0:
        print >> OutStream, 'processing %d all laps above %d watts...' \
                            % (len(ii), 0.80*FTP)
        names1 = ['', '', 'avg', 'avg', 'avg', 'max', 'avg']
        names2 = ['lap', 'time', 'power', 'cad', 'HR', 'HR', 'bal']
        print >> OutStream, "%8s" * 7 % tuple(names1)
        print >> OutStream, "%8s" * 7 % tuple(names2)
        for i in range(len(ii)):
            mm = time[ii[i]] // 60
            ss = time[ii[i]] % 60
            print >> OutStream, '%8d%5i:%02i%8d%8d%8d%8d%8.1f' \
                    % (ii[i], mm, ss, power[ii[i]],
                        cadence[ii[i]],
                        avg_hr[ii[i]],
                        max_hr[ii[i]],
                        balance[ii[i]] )
        mm = sum(time[ii]) // 60
        ss = sum(time[ii]) % 60
        print >> OutStream, '%8s%5i:%02i%8d%8d%8d%8d%8.1f' \
                % ("AVERAGE", mm, ss,
                    sum(  power[ii]*time[ii]) / sum(time[ii]),
                    sum(cadence[ii]*time[ii]) / sum(time[ii]),
                    sum( avg_hr[ii]*time[ii]) / sum(time[ii]),
                    max(max_hr[ii]),
                    sum(balance[ii]*time[ii]) / sum(time[ii]) )
    else:
        print >> OutStream, 'No high-intensity laps found.' \

    #   Tempo intervals:    75-88  %FTP
    ii = np.nonzero(np.logical_and(power >= 0.75 * FTP,
                                   power < 0.88 * FTP))[0]  # index array
    if len(ii) > 0:
        print >> OutStream, 'processing %d tempo laps between %d and %d watts...' \
                            % (len(ii), 0.75*FTP, 0.88*FTP)
        names1 = ['', '', 'avg', 'avg', 'avg', 'max', 'avg']
        names2 = ['lap', 'time', 'power', 'cad', 'HR', 'HR', 'bal']
        print >> OutStream, "%8s" * 7 % tuple(names1)
        print >> OutStream, "%8s" * 7 % tuple(names2)
        for i in range(len(ii)):
            mm = time[ii[i]] // 60
            ss = time[ii[i]] % 60
            print >> OutStream, '%8d%5i:%02i%8d%8d%8d%8d%8.1f' \
                    % (ii[i], mm, ss, power[ii[i]],
                        cadence[ii[i]],
                        avg_hr[ii[i]],
                        max_hr[ii[i]],
                        balance[ii[i]] )
        mm = sum(time[ii]) // 60
        ss = sum(time[ii]) % 60
        print >> OutStream, '%8s%5i:%02i%8d%8d%8d%8d%8.1f' \
                % ("AVERAGE", mm, ss,
                    sum(  power[ii]*time[ii]) / sum(time[ii]),
                    sum(cadence[ii]*time[ii]) / sum(time[ii]),
                    sum( avg_hr[ii]*time[ii]) / sum(time[ii]),
                    max(max_hr[ii]),
                    sum(balance[ii]*time[ii]) / sum(time[ii]) )
    else:
        print >> OutStream, 'No tempo laps found.' \

    #   Cruise intervals:   88-105 %FTP.
    ii = np.nonzero(np.logical_and(power >= 0.88 * FTP,
                                   power < 1.05 * FTP))[0]  # index array
    if len(ii) > 0:
        print >> OutStream, 'processing %d threshold laps between %d and %d watts...' \
                            % (len(ii), 0.88*FTP, 1.05*FTP)
        names1 = ['', '', 'avg', 'avg', 'avg', 'max', 'avg']
        names2 = ['lap', 'time', 'power', 'cad', 'HR', 'HR', 'bal']
        print >> OutStream, "%8s" * 7 % tuple(names1)
        print >> OutStream, "%8s" * 7 % tuple(names2)
        for i in range(len(ii)):
            mm = time[ii[i]] // 60
            ss = time[ii[i]] % 60
            print >> OutStream, '%8d%5i:%02i%8d%8d%8d%8d%8.1f' \
                    % (ii[i], mm, ss, power[ii[i]],
                        cadence[ii[i]],
                        avg_hr[ii[i]],
                        max_hr[ii[i]],
                        balance[ii[i]] )
        mm = sum(time[ii]) // 60
        ss = sum(time[ii]) % 60
        print >> OutStream, '%8s%5i:%02i%8d%8d%8d%8d%8.1f' \
                % ("AVERAGE", mm, ss,
                    sum(  power[ii]*time[ii]) / sum(time[ii]),
                    sum(cadence[ii]*time[ii]) / sum(time[ii]),
                    sum( avg_hr[ii]*time[ii]) / sum(time[ii]),
                    max(max_hr[ii]),
                    sum(balance[ii]*time[ii]) / sum(time[ii]) )
    else:
        print >> OutStream, 'No threshold laps found.' \

    #   VO2max intervals:  105-200 %FTP.
    ii = np.nonzero(np.logical_and(power >= 1.05 * FTP,
                                   power < 2.00 * FTP))[0]  # index array
    if len(ii) > 0:
        print >> OutStream, 'processing %d VO2max laps between %d and %d watts...' \
                            % (len(ii), 1.05*FTP, 2.00*FTP)
        names1 = ['', '', 'avg', 'avg', 'avg', 'max', 'avg']
        names2 = ['lap', 'time', 'power', 'cad', 'HR', 'HR', 'bal']
        print >> OutStream, "%8s" * 7 % tuple(names1)
        print >> OutStream, "%8s" * 7 % tuple(names2)
        for i in range(len(ii)):
            mm = time[ii[i]] // 60
            ss = time[ii[i]] % 60
            print >> OutStream, '%8d%5i:%02i%8d%8d%8d%8d%8.1f' \
                    % (ii[i], mm, ss, power[ii[i]],
                        cadence[ii[i]],
                        avg_hr[ii[i]],
                        max_hr[ii[i]],
                        balance[ii[i]] )
        mm = sum(time[ii]) // 60
        ss = sum(time[ii]) % 60
        print >> OutStream, '%8s%5i:%02i%8d%8d%8d%8d%8.1f' \
                % ("AVERAGE", mm, ss,
                    sum(  power[ii]*time[ii]) / sum(time[ii]),
                    sum(cadence[ii]*time[ii]) / sum(time[ii]),
                    sum( avg_hr[ii]*time[ii]) / sum(time[ii]),
                    max(max_hr[ii]),
                    sum(balance[ii]*time[ii]) / sum(time[ii]) )
    else:
        print >> OutStream, 'No VO2max laps found.' \
コード例 #6
0
def channel_inspect_anls(FitFilePath, ConfigFile=None, OutStream=sys.stdout,
                         ParentWin=None):

    (FilePath, FitFileName) = os.path.split(FitFilePath)

    if ConfigFile is None:
        ConfigFile = FindConfigFile('', FilePath)
    if (ConfigFile is None) or (not os.path.exists(ConfigFile)):
        raise IOError('Configuration file not specified or found')
    #
    #   Parse the configuration file
    #
    from ConfigParser import ConfigParser
    config      = ConfigParser()
    config.read(ConfigFile)
    print >> OutStream, 'reading config file ' + ConfigFile
    if config.has_section('units'):
        # convert list of tuples to dictionary
        UserUnits = dict( config.items('units') )
    else:
        UserUnits = None

    # get the signals
    from datetime import datetime
    from fitparse import Activity
    from activity_tools import extract_activity_signals, UnitHandler
    activity    = Activity(FitFilePath)
    signals     = extract_activity_signals(activity, resample='existing')

    # convert units
    if UserUnits is not None:
        unithandler = UnitHandler(UserUnits)
        signals = unithandler.ConvertSignalUnits(signals)

    ChannelList = signals.keys()
    ChannelList.remove('time')
    ChannelList.remove('metadata')

    print >> OutStream, 'Signals contained:'
    for s in ChannelList:
        print >> OutStream, '   ' + s

    if ParentWin is None:
        app = wx.App()

    dlg = wx.MultiChoiceDialog( ParentWin,
                               "Pick channels\nto plot",
                               "channel inspector: " + FitFileName,
                               ChannelList)

    if (dlg.ShowModal() == wx.ID_OK):
        selections = dlg.GetSelections()
        ChannelNames = [ ChannelList[x] for x in selections ]
        print >> OutStream, "Plotting: %s" % (ChannelNames)

    dlg.Destroy()

    #
    #   extract lap results
    #
    from activity_tools import extract_activity_laps
    activity    = Activity(FitFilePath)
    laps        = extract_activity_laps(activity)
    lap_start_time  = laps['start_time']    # datetime object
    lap_timestamp   = laps['timestamp' ]
    nLaps           = len(lap_start_time)
    t0 = signals['metadata']['timestamp']
    lap_start_sec   = np.zeros(nLaps)          # lap start times in seconds
    for i in range(nLaps):
        tBeg = (lap_start_time[i] - t0).total_seconds()
        tEnd = (lap_timestamp[i]  - t0).total_seconds()
        lap_start_sec[i]    = tBeg

    #
    # time plot
    #
    nPlots  = len(ChannelNames)

    PlotColors  = { 'power'         : 'm'       ,
                    'heart_rate'    : 'r'       ,
                    'cadence'       : 'g'       ,
                    'speed'         : 'b'       ,
                    'temperature'   : 'brown'   }


    import matplotlib.pyplot as plt
    import matplotlib.dates as md
    from matplotlib.dates import date2num, DateFormatter
    import datetime as dt
    base = dt.datetime(2014, 1, 1, 0, 0, 0)
    x = [base + dt.timedelta(seconds=t) for t in signals['time'].astype('float')]
    x = date2num(x) # Convert to matplotlib format
    #fig1, (ax0, ax1) = plt.subplots(nrows=2, sharex=True)
    x_laps  = [ base + dt.timedelta(seconds=t)   \
                for t in lap_start_sec.astype('float') ]
    x_laps  = date2num(x_laps)
    axislist    = []
    fig = plt.figure()
    for i, channel in zip( range(nPlots), ChannelNames ):
        if PlotColors.has_key(channel):
            pcolor  = PlotColors[channel]
        else:
            pcolor  = 'k-'
        if i > 0:
            ax  = plt.subplot( nPlots, 1, i+1, sharex=axislist[0] )
        else:
            ax  = plt.subplot( nPlots, 1, i+1 )
        ax.plot_date( x, signals[channel], pcolor, linewidth=1, linestyle='-' );
        for j in range(nLaps):
            ax.axvline( x_laps[j], label=str(j+1) )
        ax.grid(True)
        if signals['metadata']['units'].has_key(channel):
            YLabel  = channel + '\n' \
                    + signals['metadata']['units'][channel]
        else:
            YLabel  = channel + '\n' + 'none'
        ax.set_ylabel(YLabel)
        ax.xaxis.set_major_formatter(DateFormatter('%H:%M:%S'))
        ax.grid(True)
        axislist.append(ax)
    fig.autofmt_xdate()
    fig.suptitle(FitFileName, fontsize=20)
    fig.tight_layout()
    fig.canvas.set_window_title(FitFilePath)
    fig.subplots_adjust(hspace=0)   # Remove horizontal space between axes
    plt.show()


    def ClosePlots():
        plt.close('all')

    return ClosePlots