Basic Hypothetical Gait Detection for Mobile Exoskeleton

This notebook illustrates a simple method of detecting what kind of motion a person is performing (walking, running, etc.) based on some basic force data.


Motivation

I used to be on uOttawa Bionics, a student engineering team trying to build a hip-mounted exoskeleton for rehabilitation. A huge problem we faced was how the suit would know how it needs to move to help the person walk. I never got around to tackling this when on the team, so I figured I'd try my hand at it now.

There are a few ways which I know of for going about this. The simplest is to have the suit pre-programmed to move in a certain way; it'll walk at a certain speed when a Walk button is pressed, sit down at a certain rate and into a certain position when the Sit button is pressed, etc. While programming this is certainly more simple than other approaches, it's not great for a wearer who wants control of their actions. Perhap a pre-programmed walking style makes sense in the context of rehabilitation, where it might help re-teach proper gait, but what about in construction, or the military, or just wearing an exoskeleton for fun, where we want wearing an exoskeleton to feel like an extension of ourselves and not something which limits us?

The more complicated approach is to have the suit work with the wearer and augment their desired motion. This means, however, that the suit must know what the wearer is trying to do. This might include:

  1. what kind of gait are they using (walking, running, kneeling, sitting, etc.)
  2. Where are they in that particular gait cycle (beginning their stride, transfering weight to the other leg, etc.), and
  3. Probably many more questions I didn't bother tackling here.

This notebook is my attempt at implemnenting the latter; a super basic way of detecting what the user is trying to do, and guessing what they'll try to do next.

I chose to do this mini-project in Julia since I've heard interesting things about it and wanted to give it a shot.


Generating Data

Before we can go ahead and detect what kind of gait someone is performing and where in the gait cycle they are, we need some data to work with. I don't exactly have access to force sensors and a plethora of willing volunteers anymore, so instead we'll nab some lazy data from a paper; in this case, I chose the normalized hip moment for running and walking from Figure 1 B from Motor Patterns in Human Walking and Running (available here). Since the dataset isn't provided, I used WebPlotDigitizer to turn each subplot into a .csv file.

We have some data, but it's not enough; a quick DDG search shows that force transducers like this one can read at up to $4kHz$ - up to 4000 times per second. I got about 70 readings for the walking data and 40 for the running data. In order to create more data to mimic the reading frequency of a force transducer, we'll curve fit our not-so-great data with a polynomial fit, then use that polynomial fit to populate 4000 data points per second.

Creating a Polynomial Fit

For those that have forgotten (namely, me before doing this), a polynomial function takes the form ($y(x) = a_1 x^1 + a_2 x^2 + \cdots + a_n x^n$). The higher the order of the polynomial ($n$), the closer the function will fit our data. If we choose too high of an order though, the function will fit it too well; if the data used to fit is imperfect (which ours really is since I manually generated the data using WebPlotDigitizer), then the polynomial fit also becomes imperfect. You can see some graphs showing this effect here.

We can create the polynomial fit using the Polynomials.jl package and use Plots.jl to compare the fits to the original data. Messing around with different orders of polynomial, I found that $n = 15$ was suitable for the walking data and $n = 13$ was suitable for the running data; any higher for the latter introduced spikes to the polynomial fit. I assume this is because the running data set had less points to it, resulting in a rockier fit. In any case, this isn't super significant; the data I extracted using WebPlotDigitizer is already inaccurate, so a bit more inaccuracy won't hurt.

Creating More Data

Now that we have polynomial fits which approximate each (way too small) set of data, it's time to create more dense sets of data; to get closer to that 4000-readings-per-second from real force transducers. To do so, we need to know how long a single stride takes; the Figure from the paper is from 0 to 1, where 0 is the beginning of a stride and 1 is the end of that stride (getting back to the starting position), but we don't know if 1 is a second or twenty.

In the spirit of keeping this little project as sketchy as possible, I'll use some very trustworthy and absolutely not random internet sources for my data. According to Science Trends, it takes about 2250 steps to walk a mile and 1550 steps to run a mile. The average walking speed is about 3.5 miles per hour, or an average of:

$$\frac{1\,\textrm{hour}}{3.5\,\textrm{miles}} \times \frac{3600\,\textrm{seconds}}{\textrm{hour}} \approx \frac{1029\,\textrm{seconds}}{\textrm{mile}}$$

To find the number of seconds per step, we can do:

$$\frac{1029\,\textrm{seconds}}{\textrm{mile}} \times \frac{1\,\textrm{mile}}{2250\,\textrm{steps}} \approx \frac{0.457\,\textrm{seconds}}{\textrm{step}}$$

We can repeat this calculation for running, which, with an average pace of $\frac{9\,\textrm{minutes}}{\textrm{mile}}$:

$$\frac{9\,\textrm{minutes}}{\textrm{mile}} \times \frac{60\,\textrm{seconds}}{\textrm{minute}} \times \frac{1\,\textrm{mile}}{1550\,\textrm{steps}} \approx \frac{0.348\,\textrm{seconds}}{\textrm{step}}$$

We can now figure out how many samples should be in each set of data. For walking:

$$\frac{4000\,\textrm{samples}}{\textrm{second}} \times \frac{0.457\,\textrm{seconds}}{\textrm{step}} = \frac{1828\,\textrm{samples}}{\textrm{step}}$$

Likewise for running:

$$\frac{4000\,\textrm{samples}}{\textrm{second}} \times \frac{0.348\,\textrm{seconds}}{\textrm{step}} = \frac{1392\,\textrm{samples}}{\textrm{step}}$$

Now, was any of this strictly necessary? I suppose I could have just used 4000 values for both the running and walking data sets, but I figured I'd at least pretend to science properly. That, and having calculations in my notebook might give the illusion that I have any idea what I'm doing.

Now that we have the number of values we need in the data set for each gait, we can create the sets.

Looks like it worked, the curves have the same shapes and magnitudes.! Now that we have reasonably detailed sets of data to work with, we can try our method for finding the type of gait and where we are in the gait cycle.

Predicting Gait using the Root Mean Square Error

This is the simplest (but hardly most efficient) method I can think of to figure out what the current gait is, and where we are in that gait cycle. Our solution follows these steps:

  1. Grab the last $n$ values of the force transducer
  2. Iterate over our gait data for both walking and running. At every step, calculate the Root Mean Square Error (RMSE) between our data readings and the current gait values
  3. Choose the gait data which gave the lowest error; this is likely where we are in the gait cycle

What Should our Sample Size Be?

Unfortunately, it's tough to know what the sample size (the last $n$ readings) should be in advance. If $n$ is too high, then we're looking too far into the past and might use values which belong to a different type of gait. As an extreme example, if we look at the last minute of gait data, there might be running, walking, sitting, squatting, and a bunch of other things happening in that time span, so whatever we're likely to guess is currently happening is likely to be wrong. If $n$ too low, then we might accidentally guess the wrong gait, since a small sample might match a pattern that is found in multiple types of gaits. An extreme example in this case would be taking a single value, perhaps 0N of force. This might appear when sitting on a chair (since the suit isn't doing anything) or while walking and letting gravity swing the leg; with only a single sample of 0N, we have no way of knowing which it is. I get the impression that choosing a proper $n$ would require some experimentation with actual sensors and patients, so for now, I'll just pick a value more or less at random. Having thoroughly butchered any semblence of the scientific process, we can now proceed to implement the little algorithm above.

What Next?

So now we have a super basic method for detecting current gait. I'm absolutely positive no exoskeletons out there use this, but it was a cute little exercise. That said, what could we do to improve our approach? One idea is to improve the performance of our algorithm. While the method above works, it isn't the fastest algorithm in the world; it must iterate over the full arrays for each gait type to find the point with the lowest RMSE. Once those arrays grow, this method will become less efficient. O(N) scaling is pretty good, but getting to logarithmic complexity (O(log(N))) using something like a hash map would be nice; the hypothetical performance gain illustrated below is pretty massive:

This could be pretty significant when it comes to deploying our algorithm to actual hardware. The longer it takes for our code to predict the user's next move, the more expensive hardware must be used to get the desired performance, or the worse the performance of the suit, such as reducing $n$ or the rate at which our predictions are made. The latter is dangerous, because if it takes too long for our code to figure out the next step, it'll lag behind the wearer, who'll notice that they're fighting to suit instead of it working with them seamlessly.

Fin?

I'm about to start a school semester, so I don't think I'll be able to circle back around to this for a while. If you have any questions though, feel free to send me an email :)