Loaded Walking Project, Shifting Data

Provide easy-to-use, extensible software for modeling, simulating, controlling, and analyzing the neuromusculoskeletal system.
POST REPLY
User avatar
Ali Khalilianmotamed Bonab
Posts: 47
Joined: Mon Aug 13, 2018 6:28 am

Loaded Walking Project, Shifting Data

Post by Ali Khalilianmotamed Bonab » Sat Sep 14, 2019 7:58 am

Dear OpenSim Experts,
I am trying to reproduce the "Loaded Walking Project'" data, but I have serious difficulty in shifting the simulations' raw data to the proper gait cycle duration.
Based on what I noticed from the provided data, the simulation time and gait cycles are different, and they need to be adequately shifted, but I could not use the provided codes properly in Windows environment.
The code provided below is what I found on the supporting information available on this website for shifting the data.

Code: Select all

def shift_data_to_cycle(
        arbitrary_cycle_start_time, arbitrary_cycle_end_time,
        new_cycle_start_time, time, ordinate, cut_off=True):
    """
    Takes data (ordinate) that is (1) a function of time and (2) cyclic, and
    returns data that can be plotted so that the data starts at the desired
    part of the cycle.

    Used to shift data to the desired part of a gait cycle, for plotting purposes.  Data may be recorded from an arbitrary part
    of the gait cycle, but we might desire to plot the data starting at a
    particular part of the gait cycle (e.g., right foot strike).
    Another example use case is that one might have data for both right and
    left limbs, but wish to plot them together, and thus must shift data for
    one of the limbs by 50% of the gait cycle.

    This method also cuts the data so that your data covers at most a full gait
    cycle but not more.

    The first three parameters below not need exactly match times in the `time`
    array.

    This method can also be used just to truncate data, by setting
    `new_cycle_start_time` to be the same as `arbitrary_cycle_start_time`.

    Parameters
    ----------
    arbitrary_cycle_start_time : float
        Choose a complete cycle/period from the original data that you want to
        use in the resulting data. What is the initial time in this period?
    arbitrary_cycle_end_time : float
        See above; what is the final time in this period?
    new_cycle_start_time : float
        The time at which the shifted data should start. Note that the initial
        time in the shifted time array will regardlessly be 0.0, not
        new_cycle_start_time.
    time : np.array
        An array of times that must correspond with ordinate values (see next),
        and must contain arbitrary_cycle_start_time and
        arbitrary_cycle_end_time.
    ordinate : np.array
        The cyclic function of time, values corresponding to the times given.
    cut_off : bool, optional
        Sometimes, there's a discontinuity in the data that prevents obtaining
        a smooth curve if the data wraps around. In order prevent
        misrepresenting the data in plots, etc., an np.nan is placed in the
        appropriate place in the data.

    Returns
    -------
    shifted_time : np.array
        Same size as time parameter above, but its initial value is 0 and its
        final value is the duration of the cycle (arbitrary_cycle_end_time -
        arbitrary_cycle_start_time).
    shifted_ordinate : np.array
        Same ordinate values as before, but they are shifted so that the first
        value is ordinate[{index of arbitrary_cycle_start_time}] and the last
        value is ordinate[{index of arbitrary_cycle_start_time} - 1].

    Examples
    --------
    Observe that we do not require a constant interval for the time:

        >>> ordinate = np.array([2, 1., 2., 3., 4., 5., 6.])
        >>> time = np.array([0.5, 1.0, 1.2, 1.35, 1.4, 1.5, 1.8])
        >>> arbitrary_cycle_start_time = 1.0
        >>> arbitrary_cycle_end_time = 1.5
        >>> new_cycle_start_time = 1.35
        >>> shifted_time, shifted_ordinate = shift_data_to_cycle(
                ...     arbitrary_cycle_start_time, arbitrary_cycle_end_time,
                ...     new_cycle_start_time,
                ...     time, ordinate)
        >>> shifted_time
        array([ 0.  ,  0.05,  0.15,  0.3 ,  0.5 ])
        >>> shifted_ordinate
        array([3., 4., nan, 1., 2.])

    In order to ensure the entire duration of the cycle is kept the same,
    the time interval between the original times "1.5" and "1.0" is 0.1, which
    is the time gap between the original times "1.2" and "1.3"; the time
    between 1.2 and 1.3 is lost, and so we retain it in the place where we
    introduce a new gap (between "1.5" and "1.0"). NOTE that we only ensure the
    entire duration of the cycle is kept the same IF the available data covers
    the entire time interval [arbitrary_cycle_start_time,
    arbitrary_cycle_end_time].

    """
    # TODO gaps in time can only be after or before the time interval of the
    # available data.
	import copy
    if new_cycle_start_time > arbitrary_cycle_end_time:
        raise Exception('(`new_cycle_start_time` = %f) > (`arbitrary_cycle_end'
                '_time` = %f), but we require that `new_cycle_start_time <= '
                '`arbitrary_cycle_end_time`.' % (new_cycle_start_time,
                    arbitrary_cycle_end_time))
    if new_cycle_start_time < arbitrary_cycle_start_time:
        raise Exception('(`new_cycle_start_time` = %f) < (`arbitrary_cycle'
                '_start_time` = %f), but we require that `new_cycle_start_'
                'time >= `arbitrary_cycle_start_time`.' % (new_cycle_start_time,
                    arbitrary_cycle_start_time))


    # We're going to modify the data.
    time = copy.deepcopy(time)
    ordinate = copy.deepcopy(ordinate)

    duration = arbitrary_cycle_end_time - arbitrary_cycle_end_time

    old_start_index = nearest_index(time, arbitrary_cycle_start_time)
    old_end_index = nearest_index(time, arbitrary_cycle_end_time)

    new_start_index = nearest_index(time, new_cycle_start_time)

    # So that the result matches exactly with the user's desired times.
    if new_cycle_start_time > time[0] and new_cycle_start_time < time[-1]:
        time[new_start_index] = new_cycle_start_time
        ordinate[new_start_index] = np.interp(new_cycle_start_time, time,
                ordinate)

    data_exists_before_arbitrary_start = old_start_index != 0
    if data_exists_before_arbitrary_start:
        #or (old_start_index == 0 and
        #    time[old_start_index] > arbitrary_cycle_start_time):
        # There's data before the arbitrary start.
        # Then we can interpolate to get what the ordinate SHOULD be exactly at
        # the arbitrary start.
        time[old_start_index] = arbitrary_cycle_start_time
        ordinate[old_start_index] = np.interp(arbitrary_cycle_start_time, time,
                ordinate)
        gap_before_avail_data = 0.0
    else:
        if not new_cycle_start_time < time[old_start_index]:
            gap_before_avail_data = (time[old_start_index] -
                    arbitrary_cycle_start_time)
        else:
            gap_before_avail_data = 0.0
    data_exists_after_arbitrary_end = time[-1] > arbitrary_cycle_end_time
    # TODO previous: old_end_index != (len(time) - 1)
    if data_exists_after_arbitrary_end:
        #or (old_end_index == (len(time) - 1)
        #and time[old_end_index] < arbitrary_cycle_end_time):
        time[old_end_index] = arbitrary_cycle_end_time
        ordinate[old_end_index] = np.interp(arbitrary_cycle_end_time, time,
                ordinate)
        gap_after_avail_data = 0
    else:
        gap_after_avail_data = arbitrary_cycle_end_time - time[old_end_index]

    # If the new cycle time sits outside of the available data, our job is much
    # easier; just add or subtract a constant from the given time.
    if new_cycle_start_time > time[-1]:
        time_at_end = arbitrary_cycle_end_time - new_cycle_start_time
        missing_time_at_beginning = \
                max(0, time[0] - arbitrary_cycle_start_time)
        move_forward = time_at_end + missing_time_at_beginning
        shift_to_zero = time[old_start_index:] - time[old_start_index]
        shifted_time = shift_to_zero + move_forward
        shifted_ordinate = ordinate[old_start_index:]
    elif new_cycle_start_time < time[0]:
        move_forward = time[0] - new_cycle_start_time
        shift_to_zero = time[:old_end_index + 1] - time[old_start_index]
        shifted_time = shift_to_zero + move_forward
        shifted_ordinate = ordinate[:old_end_index + 1]
    else:
        # We actually must cut up the data and move it around.

        # Interval of time in
        # [arbitrary_cycle_start_time, arbitrary_cycle_end_time] that is 'lost' in
        # doing the shifting.
        if new_cycle_start_time < time[old_start_index]:
            lost_time_gap = 0.0
        else:
            lost_time_gap = time[new_start_index] - time[new_start_index - 1]

        # Starts at 0.0.
        if new_cycle_start_time < time[0]:
            addin = gap_before_avail_data
        else:
            addin = 0
        first_portion_of_new_time = (time[new_start_index:old_end_index+1] -
                new_cycle_start_time + addin)

        # Second portion: (1) shift to 0, then move to the right of first portion.
        second_portion_to_zero = \
                time[old_start_index:new_start_index] - arbitrary_cycle_start_time
        second_portion_of_new_time = (second_portion_to_zero +
                first_portion_of_new_time[-1] + lost_time_gap +
                gap_after_avail_data)

        shifted_time = np.concatenate(
                (first_portion_of_new_time, second_portion_of_new_time))

        # Apply cut-off:
        if cut_off:
            ordinate[old_end_index] = np.nan

        # Shift the ordinate.
        shifted_ordinate = np.concatenate(
                (ordinate[new_start_index:old_end_index+1],
                    ordinate[old_start_index:new_start_index]))

    return shifted_time, shifted_ordinate
I am calling this function in Matlab and providing the "time" and "ordinate" extracted from ".sto" files. for the three other inputs, I am using the information provided in "dodo_subject" files; for example for "dodo_subject05" file, I have:

Code: Select all

simulation(subj_num, 'noload/free', 2,
        GaitLandmarks(
            min_time=0.65,
            max_time=1.6,
            gait_cycle_duration=1.614 - 0.462,
            primary_leg='right',
            left_strike=1.048,
            left_toeoff=1.764,
            right_strike=0.462,
            right_toeoff=1.179),
    speed=1.50,
    ikid_finaltime=2.0,
    order_of_foot_contact='rll',
    )
where I am using "gait_cycle_duration" times for "arbitrary_cycle_start_time" and "arbitrary_cycle_end_time", and "right_strike" for the "new_cycle_start_time" .
The problem is that my output data are not matching with the provided figures as you can see in the attached figures. Additionally, I am getting lots of NaN after shifting the data in the Matlab .mat files especially in the first 20% of a gait cycle. (Provided figure represents the joint moment of subject 05\loaded, Sample data is also provided.)

I would be grateful if you can, first of all, help me with using the provided code and let me know if I am using correct information to shift the data or not. Secondly, it would be great if you can provide more information about how this shift is working and if there is any, provide a similar Matlab code for shifting the data.
Attachments
Sample_Data_Subjects_Joint_Moment.xlsx
Sample Data for Subjects Joint Moment
(470.75 KiB) Downloaded 6 times
subject05.pdf
My Simulations
(26.93 KiB) Downloaded 12 times
joint_moments.pdf
Loaded Walking Supporting Information
(39.81 KiB) Downloaded 17 times

Tags:

User avatar
Ali Khalilianmotamed Bonab
Posts: 47
Joined: Mon Aug 13, 2018 6:28 am

Re: Loaded Walking Project, Shifting Data

Post by Ali Khalilianmotamed Bonab » Mon Sep 16, 2019 12:13 am

I am still struggling with it and would be grateful if anyone can help me.
Sincerely,
Ali.

User avatar
Ayman Habib
Posts: 2248
Joined: Fri Apr 01, 2005 12:24 pm

Re: Loaded Walking Project, Shifting Data

Post by Ayman Habib » Mon Sep 16, 2019 10:27 am

Hi Ali,

Not sure what the authors of the publication you refer to have done, but generally you run simulations and analyses in absolute time (no need to shift, just specify start/end times). You use some other offline script/utility to find events that define the gait cycle then overlay the X-axis as %gait cycle instead of time, you may need some cropping but again these are usually done offline using custom scripts.

Hope this helps,
-Ayman

User avatar
Ali Khalilianmotamed Bonab
Posts: 47
Joined: Mon Aug 13, 2018 6:28 am

Re: Loaded Walking Project, Shifting Data

Post by Ali Khalilianmotamed Bonab » Mon Sep 16, 2019 11:22 am

Dear Professor Ayman,
Thanks for replying, my problem is exactly on those offline interpretations of the data.
I am trying to reproduce the result of the paper titled " Simulated Assistive Devices for Loaded Walking" https://simtk.org/projects/assistloadwalk which the gait events are provided for each subject and trial. But there is a considerable difference between my and their results while I am using their code to process my data.
So, I want to identify what exactly I should use as function inputs or which function I should use to get the same results. My friend in another group is also struggling with reproducing this paper's findings, and he has the same problem, where the resulted joint profiles are not matching with their provided figures.
All the discussed documents are available on my previous post. If it is needed, I can provide my code written in Matlab.
Sincerely,
Ali.

POST REPLY