Question

I have a series of CSV files of timestamped coordinates (X, Y, and Z in mm). What would be the simplest way to extract motion data from them?

Measurables

The information I'd like to extract includes the following:

  1. Number of direction changes
  2. Initial acceleration of the first and last movements
  3. ...and the bearing (angle) of these movements
  4. Average speed whilst non-stationary

Ideally, I'd eventually like to be able to categorise patterns of motion, so bonus points for anyone who can suggest a way of doing this. It strikes me that one way I could do this would be to generate pictures/videos of the motion from the coordinates and ask humans to categorise them - suggestions as to how I'd do this are very welcome.

Noise

A complication is the fact that the readings are polluted with noise. In order to overcome this, each recording is prefaced with at least 20 seconds of stillness which can serve as a sort of "noise profile". I'm not sure how to implement this though.

Specifics

If it helps, the motion being recorded is that of a persons hand during a simple grabbing task. The data is generated using a magnetic motion tracker attached to the wrist. Also, I'm using C#, but I guess the maths is language agnostic.

Edits

Bounty

For the bounty, I'd really like to see some (pseudo-)code examples.

Was it helpful?

Solution

Let's see what can be done with your example data.

Disclaimer: I didn't read your hardware specs (tl;dr :))

I'll work this out in Mathematica for convenience. The relevant algorithms (not many) will be provided as links.

The first observation is that all your measurements are equally spaced in time, which is most convenient for simplifying the approach and algorithms. We will represent "time" or "ticks" (measurements) on our convenience, as their are equivalent.

Let's first plot your position by axis, to see what the problem is about:

(* This is Mathematica code, don't mind, I am posting this only for 
   future reference *)
ListPlot[Transpose@(Take[p1[[All, 2 ;; 4]]][[1 ;;]]), 
 PlotRange -> All,
 AxesLabel -> {Style["Ticks", Medium, Bold], 
               Style["Position (X,Y,Z)", Medium, Bold]}]

enter image description here

Now, two observations:

  • Your movement starts around tick 1000
  • Your movement does not start at {0,0,0}

So, we will slightly transform your data subtracting a zero position and starting at tick 950.

ListLinePlot[
 Drop[Transpose@(x - Array[Mean@(x[[1 ;; 1000]]) &, Length@x]), {}, 950], 
 PlotRange -> All,
 AxesLabel -> {Style["Ticks", Medium, Bold], 
               Style["Position (X,Y,Z)", Medium, Bold]}]

enter image description here

As the curves have enough noise to spoil the calculations, we will convolve it with a Gaussian Kernel to denoise it:

kern = Table[Exp[-n^2/100]/Sqrt[2. Pi], {n, -10, 10}];
t = Take[p1[[All, 1]]];
x = Take[p1[[All, 2 ;; 4]]];

x1 = ListConvolve[kern, #] & /@ 
   Drop[Transpose@(x - Array[Mean@(x[[1 ;; 1000]]) &, Length@x]), {}, 
    950];

enter image description here

So you can see below the original and smoothed trajectories:

enter image description here

enter image description here

Now we are ready to take Derivatives for the Velocity and Acceleration. We will use fourth order approximants for the first and second derivative. We also will smooth them using a Gaussian kernel, as before:

Vel = ListConvolve[kern, #] & /@ 
   Transpose@
    Table[Table[(-x1[[axis, i + 2]] + x1[[axis, i - 2]] - 
         8 x1[[axis, i - 1]] + 
         8 x1[[axis, i + 1]])/(12 (t[[i + 1]] - t[[i]])), {axis, 1, 3}], 
    {i, 3, Length[x1[[1]]] - 2}];

Acc = ListConvolve[kern, #] & /@ 
   Transpose@
    Table[Table[(-x1[[axis, i + 2]] - x1[[axis, i - 2]] + 
         16 x1[[axis, i - 1]] + 16 x1[[axis, i + 1]] - 
         30 x1[[axis, i]])/(12 (t[[i + 1]] - t[[i]])^2), {axis, 1,  3}], 
   {i, 3, Length[x1[[1]]] - 2}];

And the we plot them:

Show[ListLinePlot[Vel,PlotRange->All,
     AxesLabel->{Style["Ticks",Medium,Bold],
                 Style["Velocity (X,Y,Z)",Medium,Bold]}],
    ListPlot[Vel,PlotRange->All]]

Show[ListLinePlot[Acc,PlotRange->All,
     AxesLabel->{Style["Ticks",Medium,Bold],
                 Style["Acceleation (X,Y,Z)",Medium,Bold]}],
     ListPlot[Acc,PlotRange->All]]

enter image description here enter image description here

Now, we also have the speed and acceleration modulus:

ListLinePlot[Norm /@ (Transpose@Vel), 
 AxesLabel -> {Style["Ticks", Medium, Bold], 
               Style["Speed Module", Medium, Bold]}, 
 Filling -> Axis]
ListLinePlot[Norm /@ (Transpose@Acc), 
 AxesLabel -> {Style["Ticks", Medium, Bold], 
               Style["Acceleration Module", Medium, Bold]},
 Filling -> Axis]       

enter image description here enter image description here

And the Heading, as the direction of the Velocity:

Show[Graphics3D[
 {Line@(Normalize/@(Transpose@Vel)),
 Opacity[.7],Sphere[{0,0,0},.7]},
 Epilog->Inset[Framed[Style["Heading",20],
        Background->LightYellow],{Right,Bottom},{Right,Bottom}]]]

enter image description here

I think this is enough to get you started. let me know if you need help in calculating a particular parameter.

HTH!

Edit

Just as an example, suppose you want to calculate the mean speed when the hand is not at rest. so, we select all points whose speed is more than a cutoff, for example 5, and calculate the mean:

Mean@Select[Norm /@ (Transpose@Vel), # > 5 &]
-> 148.085

The units for that magnitude depend on your time units, but I don't see them specified anywhere.

Please note that the cutoff speed is not "intuitive". You can search an appropriate value by plotting the mean speed vs the cutoff speed:

ListLinePlot[
 Table[Mean@Select[Norm /@ (Transpose@Vel), # > h &], {h, 1, 30}], 
 AxesLabel -> {Style["Cutoff Speed", Medium, Bold], 
               Style["Mean Speed", Medium, Bold]}]

enter image description here

So you see that 5 is an appropriate value.

OTHER TIPS

e solution could be as simple as a state machine, where each state represents a direction. Sequences of movements are represented by sequences of directions. This approach would only work if the orientation of the sensor doesn't change with respect to the movements, otherwise you'll need a method of translating the movements into the correct orientation, before calculating sequences of directions.

On the other end, you could use various AI techniques, although exactly what you'd use is beyond me.

To get the speed between any two coordinates:

               _________________________________
Avg Speed =   /(x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2
            --------------------------------------
                 (t2-t1)

To get the average speed for the whole motion, say you have 100 timestamped coordinates, use the above equation to calculate 99 speed values. Then sum all the speeds, and divide by the number of speeds (99)

To get the acceleration, the location at three moments is required, or the velocity at two moments.

Accel X = (x3 - 2*x + x1) / (t3 - t2)
Accel Y = (y3 - 2*y + y1) / (t3 - t2)
Accel Z = (z3 - 2*z + z1) / (t3 - t2)

Note: This all assumes per axis calculations: I have no experience with two-axis particle motion.

You will have a much easier time with this if you first convert your position measurements to velocity measurements.

First step: Remove the noise. As you said, each recording is prefaced with 20 seconds of stillness. So, to find the actual measurements, search for 20 second intervals where the position doesn't change. Then, take the measurement directly after.

Second step: Calculate velocities using: (x2 - x1)/(t2 - t1); the slope formula. The interval should match the interval of the recordings.

Calculations:

Direction change:

A direction change occurs where the acceleration is zero. Use numeric integration to find these times. Integrate from 0 until a time when the result of the integration is zero. Record this time. Then, integrate from the previous time until you get zero again. Repeat until you hit the end of the data.

Initial accelerations:

These are found using the slope formula again, substituting v for x.

Average speed:

The average speed formula is the slope formula. x1 and t1 should correspond to the first reading, and x2 and t2 should correspond to the final reading.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top