Cadence from Initial Contacts#

The most obvious way to calculate cadence is to use the detected initial contacts. However, initial contact (IC) detection from a single lower back sensor is never perfect. If we naively calculate cadence via a step time derived by “diffing” the initial contacts, missing ICs or even actual breaks in the gait sequence will lead to wrong cadence values (likely underestimating the cadence). We need to be robust to these types of errors.

On the other hand, for the cadence estimation it is less important that the position of the IC within the stride is perfectly correct. This means, different IC detection methods might be optimal for the cadence estimation than for the IC detection itself.

With these two things in mind, we implemented “Proxy” cadence algorithms that work on top of any IC detection method. Two variants exist:

  1. CadFromIc calculates the cadence directly from the provided ICs using step-to-step smoothing to deal with missing ICs and breaks in the gait sequence. This method should be used, if you want to use the same IC detection method for the cadence estimation than for the IC detection itself (i.e. we are not rerunning any calculations, we just use the provided ICs).

  2. CadFromIcDetector is a proxy algorithm that wraps around any IC detection algorithm. Compared to the previous method, this method will ignore the provided ICs and run the IC detection algorithm it wraps (which can be different from the one used for the IC detection itself) to find new ICs that are only used for the Cadence estimation.

Both methods than use step-to-step smoothing to deal with missing ICs and breaks in the gait sequence and then interpolate all step time values to seconds to provide an average cadence for each one second interval of the recording.

Below we will demonstrate the usage of both methods. But first we load some data.

Example Data#

We load example data from the lab dataset together with the INDIP reference system. We will use a single short-trail from the “HA” participant for this example, as it only contains a single gait sequence. All the cadence algorithms are designed to work on a single gait sequence at a time.

from mobgap.data import LabExampleDataset

lab_example_data = LabExampleDataset(reference_system="INDIP")
short_trial: LabExampleDataset = lab_example_data.get_subset(
    cohort="HA", participant_id="001", test="Test5", trial="Trial2"
)

CadFromIc#

To demonstrate the usage of CadFromIc we use the detected initial contacts from the reference system as input.

reference_ic = short_trial.reference_parameters_relative_to_wb_.ic_list
reference_ic
ic lr_label
wb_id step_id
0 0 0 left
1 63 right
2 118 left
3 177 right
4 230 left
5 288 right
6 345 left
7 407 right
8 469 left


reference_gs = short_trial.reference_parameters_relative_to_wb_.wb_list
reference_gs
start end n_strides duration_s length_m avg_walking_speed_mps avg_cadence_spm avg_stride_length_m termination_reason
wb_id
0 392 862 7 4.69 4.76565 1.046655 103.453056 1.211187 Pause


Then we initialize the algorithm and call the calculate method. Note that we use the sampling_rate_hz of the actual data and not the reference system. This is because, the reference parameters are already converted to the data sampling rate.

from mobgap.cadence import CadFromIc

cad_from_ic = CadFromIc()

gs_id = reference_gs.index[0]
data_in_gs = short_trial.data_ss.iloc[
    reference_gs.start.iloc[0] : reference_gs.end.iloc[0]
]
ics_in_gs = reference_ic[["ic"]].loc[gs_id]

cad_from_ic.calculate(
    data_in_gs,
    initial_contacts=ics_in_gs,
    sampling_rate_hz=short_trial.sampling_rate_hz,
)
CadFromIc(max_interpolation_gap_s=3, step_time_smoothing=HampelFilter(half_window_size=2, n_sigmas=3.0))

We get an output that contains the cadence for each second of the gaits sequence. The index represents the sample of the center of the second the cadence value belongs to.

Note, that there might be NaNs in the output, if there are breaks in the gait sequence and further, that the final second might only be partially covered by the gait sequence. We still calculate a cadence value for the entire second, but it is only based on the steps that occur in the part that was actually covered by the gait sequence.

cadence_spm
sec_center_samples
50 101.694915
150 107.142857
250 104.347826
350 96.774194
450 96.774194


To show that the approach results in roughly the “correct” cadence value, we can compare the average cadence to the reference system.

reference_cad = reference_gs["avg_cadence_spm"].loc[gs_id]
cad_from_ic_avg_cad = cad_from_ic.cadence_per_sec_["cadence_spm"].mean()
print(f"Average stride cadence from reference: {reference_cad:.2f} steps/min")
print(
    f"Calculated average per-sec cadence: {cad_from_ic_avg_cad:.2f} steps/min"
)
Average stride cadence from reference: 103.45 steps/min
Calculated average per-sec cadence: 101.35 steps/min

Note that if we would have breaks in the gait sequence, the method would try to interpolate the cadence values for short breaks, but would provide NaNs for longer breaks. This is controlled by the max_interpolation_gap_s parameter.

CadFromIcDetector#

For the CadFromIcDetector we need to supply an IC detection algorithm. In this case we use the IcdShinImproved algorithm. We could also use any other IC detection algorithm or adapt the parameters of the IC detection algorithm.

from mobgap.cadence import CadFromIcDetector
from mobgap.initial_contacts import IcdShinImproved

cad_from_ic_detector = CadFromIcDetector(IcdShinImproved())

Now we can call the calculate method with the same data as before. Note, that we are still passing the initial contacts from the “previous” calculation step to fulfill the API. However, internally the algorithm will ignore the provided ICs and rerun the IC detection using the provided IC detector.

Note, that we need to convert the data to the body frame, as the underlying IC-detector requires it.

from mobgap.utils.conversions import to_body_frame

cad_from_ic_detector.calculate(
    to_body_frame(data_in_gs),
    initial_contacts=ics_in_gs,
    sampling_rate_hz=short_trial.sampling_rate_hz,
)
/home/docs/checkouts/readthedocs.org/user_builds/mobgap/checkouts/v0.9.0/examples/cadence/_01_cad_from_ic.py:126: UserWarning: This method ignores the passed initial contacts and recalculates them from the data. This way you can use a different IC detector for the cadence calculation than for the IC detection. If you don't want this, you should use the `CadFromIc` class instead.

This warning is just a information to make sure you are fully aware of this. If you want to silence this warning, you can pass ``silence_ic_warning=True`` during the initialization of this class.
  cad_from_ic_detector.calculate(

CadFromIcDetector(ic_detector=IcdShinImproved(axis='norm'), max_interpolation_gap_s=3, silence_ic_warning=False, step_time_smoothing=HampelFilter(half_window_size=2, n_sigmas=3.0))

Note

By default the CadFromIcDetector will raise a warning to inform the user that the passed ICs are ignored. This is intentionally, as we assume first time users might be confused by that fact. If you are aware of this and want to get rid of the warning, you can set silence_ic_warning to True on the CadFromIcDetector object.

We get the same output structure as before.

cadence_spm
sec_center_samples
50 101.694915
150 106.194690
250 104.347826
350 97.560976
450 95.238095


But we can also access the detected initial contacts.

cad_from_ic_detector.internal_ic_list_
ic
step_id
0 52
1 111
2 167
3 224
4 279
5 339
6 399
7 462


Or the entire detector object.

IcdShinImproved(axis='norm')

To show that the approach results in roughly the “correct” cadence value, we can compare the average cadence to the reference system.

Note

Compared to the previous method, the cadence value are more different, as we actually run a IC detection algorithm to find the ICs and not just used the values provided by the reference.

cad_from_ic_detector_avg_cad = cad_from_ic_detector.cadence_per_sec_[
    "cadence_spm"
].mean()
print(f"Average stride cadence from reference: {reference_cad:.2f} steps/min")
print(
    f"Calculated average per-sec cadence (CadFromIC): {cad_from_ic_avg_cad:.2f} steps/min"
)
print(
    f"Calculated average per-sec cadence (CadFromIcDetector): {cad_from_ic_detector_avg_cad:.2f} steps/min"
)
Average stride cadence from reference: 103.45 steps/min
Calculated average per-sec cadence (CadFromIC): 101.35 steps/min
Calculated average per-sec cadence (CadFromIcDetector): 101.01 steps/min

Total running time of the script: (0 minutes 2.374 seconds)

Estimated memory usage: 9 MB

Gallery generated by Sphinx-Gallery