8

Previously, I calculated the axis of orientation based on anatomical structures, such as the toes in a paw.

enter image description here

But I found that this doesn't work when I can't distinguish between the toes very well or if the 'heel' (blue square) is way off. So I decided to look for better alternatives and I decided to try and calculate the inertial axis.

This page gives a great explanation of how to calculate it, but I have trouble understanding the steps of getting from the Center of Mass (or pressure in my case) to an angle.

enter image description here

The explanation boils it down to: enter image description here which uses the Center of Pressure and a value p, of which I don't know what it is.

I had access to the Matlab code that calculated this axis for human feet and did my best to translate it to Python:

x = 0.508 # sensor size in the x-direction
y = 0.762 # sensor size in the y-direction
Ptot = 0 # total pressure 
Px   = 0 # first order moment(x)
Py   = 0 # first order moment(y)
Pxx  = 0 # second order moment (y)
Pyy  = 0 # second order moment (x)
Pxy  = 0 # second order moment (xy)

for row in range(rows): # y-direction
    for col in range(cols): # x-direction
        if data[row,col] > 0.0: # If not zero
            temp = 1
        else:
            temp = 0
        Ptot = Ptot + temp # Add 1 for every sensor that is nonzero
        Px = Px   + (x * col + x / 2) * temp
        Py = Py   + (y * row + y / 2) * temp
        Pxx = Pxx + (x * y * y * y / 12 + x * y * (row * y + y / 2) * (row * y + y / 2) ) * temp
        Pyy = Pyy + (y * x * x * x / 12 + x * y * (col * x + x / 2) * (col * x + x / 2) ) * temp        
        Pxy = Pxy + (x * y * (row * y + y / 2) * (row * x + x / 2)) * temp

CoPY = Py / Ptot
CoPX = Px / Ptot
CoP = [CoPX, CoPY]

Ixx = Pxx - Ptot * self.x * self.y * CoPY * CoPY
Iyy = Pyy - Ptot * self.x * self.y * CoPX * CoPX
Ixy = Pxy - Ptot * self.x * self.y * CoPY * CoPX
angle = (math.atan(2 * Ixy / (Iyy - Ixx))) / 2

Ixp = Ixx * math.cos(angle) * math.cos(angle) + Iyy * math.sin(angle) * math.sin(angle) - 2 * Ixy * math.sin(angle) * math.cos(angle)
Iyp = Iyy * math.cos(angle) * math.cos(angle) + Ixx * math.sin(angle) * math.sin(angle) + 2 * Ixy * math.sin(angle) * math.cos(angle)
RotationMatrix = [[math.cos(angle), math.sin(angle)], [-math.sin(angle), math.cos(angle)]]

So as far as I understood it, sin(angle) and cos(angle) from RotationMatrix are used to determine the axis. But I don't really understand how to use these values to draw an axis through the paw and rotate it around it.

Any idea what I'm doing wrong and/or what I should do to solve it?

If someone feels the need to experiment, here's a file with all the sliced arrays that contain the pressure data of each paw. To clarfiy: walk_sliced_data is a dictionary that contains ['ser_3', 'ser_2', 'sel_1', 'sel_2', 'ser_1', 'sel_3'], which are the names of the measurements. Each measurement contains another dictionary, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] (example from 'sel_1') which represent the impacts that were extracted.

Community
  • 1
  • 1
Ivo Flipse
  • 10,222
  • 18
  • 50
  • 63
  • @Ivo I was writing an answer explaining how to calculate moments of inertia, and then came through your sentence _"But I don't really understand how to use these values to draw an axis through the paw and rotate it around it."_. I guess something is wrong there. You can't rotate around an axis in 2D. You rotate around a POINT. Axis rotations make sense in 3D (and up). Can you clarify please? – Dr. belisarius May 03 '11 at 14:00
  • Probably I'm mixing up terms @belisarius, in my case the 'axis' would be a line around which both halves of the foot balance. In dog paws this is perhaps less evident than with humans, but we can rotate our feet around a longitudinal axis. I reckon I'm mistaking that hourglass shaped figure for a flat plane. Basically I want a line that runs through the Center of Pressure around which the contact surface would 'balance'. – Ivo Flipse May 03 '11 at 14:11
  • @Ivo Any line passing through the center of pressure will have the property "both halves of the foot balance" – Dr. belisarius May 03 '11 at 14:14
  • @Ivo Perhaps you may show a pair of images showing your desired result – Dr. belisarius May 03 '11 at 14:18
  • @belisarius, the outer pixels have a larger moment arm and given that the shape isn't uniform, I can't imagine 'any' line would balance both halves. [Here's an example of four paws I divided](http://i.imgur.com/HPJat.jpg) and [one of a human foot](http://i.imgur.com/SK0CJ.jpg). If you have any ideas on how else to calculate this, I'm all ears! – Ivo Flipse May 03 '11 at 14:53
  • @belisarius is right. Any axis throught the CoP will balance. What @Ivo seems to be looking for is the principal axis. If the pressures are replaced by masses in a plane (within 3d space) then the eigenvectors of the inertia tensor will be the principal axis. This will work best for longer feet like humans. – phkahler May 03 '11 at 15:09
  • Well the raw data does have the pressure values, though I did notice this calculation didn't use those @phkahler. Care to explain in some more detail how I would be able to calculate it? And should I edit my question to reflect what I want more clearly? – Ivo Flipse May 03 '11 at 16:09

1 Answers1

8

Well, here's an implementation doing the same thing as your code above (and rotating the image by the relevant angle).

However, in the case of your paws, I'm not sure it's going to work as well as it does for a human foot.

First off, for a dog's paw, the "long" axis defined this way is along the breadth of the paw instead of the length of the paw. This doesn't matter much as long as it's consistent, as we can simply rotate by the angle calculated instead of 90 - the angle calculated.

However, the fact that a dog's paw is close to circular gives us more problems.

Basically, this probably isn't going to be as useful for dogs as it would be for humans. The rotation of the "long" axis deduced by the covariance matrix of the image formed from the second central moments of the image (which is what (I think) your code above does) is less likely to be an accurate measurement of the orientation of the paw.

In other words, a dog's paw is close to round, and they appear put most of their weight on their toes, so the "back" toe is weighted less heavily than the font in this calculation. Because of that, the axis that we get isn't going to consistently have a relationship to the position of the "back" toe vs. the front toes. (Hopefully that made some sense... I'm a horrible writer... Which is why I'm answering this question rather than working on the paper I should be working on...)

At any rate, enough rambling... Here's an example:

import cPickle
import numpy as np
import matplotlib.pyplot as plt

from scipy import ndimage

def main():
    measurements = cPickle.load(open('walk_sliced_data', 'r'))
    plot(measurements['ser_1'].values())
    plt.show()

def raw_moment(data, iord, jord):
    nrows, ncols = data.shape
    y, x = np.mgrid[:nrows, :ncols]
    data = data * x**iord * y**jord
    return data.sum()

def intertial_axis(data):
    data_sum = data.sum()
    m10 = raw_moment(data, 1, 0)
    m01 = raw_moment(data, 0, 1)
    x_bar = m10 / data_sum
    y_bar = m01 / data_sum
    u11 = (raw_moment(data, 1, 1) - x_bar * m01) / data_sum
    u20 = (raw_moment(data, 2, 0) - x_bar * m10) / data_sum
    u02 = (raw_moment(data, 0, 2) - y_bar * m01) / data_sum
    angle = 0.5 * np.arctan(2 * u11 / (u20 - u02))
    return x_bar, y_bar, angle


def plot(impacts):
    def plot_subplot(pawprint, ax):
        x_bar, y_bar, angle = intertial_axis(pawprint)
        ax.imshow(pawprint)
        plot_bars(x_bar, y_bar, angle, ax)
        return angle

    fig1 = plt.figure()
    fig2 = plt.figure()
    for i, impact in enumerate(impacts[:9]):
        ax1 = fig1.add_subplot(3,3,i+1)
        ax2 = fig2.add_subplot(3,3,i+1)

        pawprint = impact.sum(axis=2)
        angle = plot_subplot(pawprint, ax1)

        pawprint = ndimage.rotate(pawprint, np.degrees(angle))
        plot_subplot(pawprint, ax2)

    fig1.suptitle('Original')
    fig2.suptitle('Rotated')

def plot_bars(x_bar, y_bar, angle, ax):
    def plot_bar(r, x_bar, y_bar, angle, ax, pattern):
        dx = r * np.cos(angle)
        dy = r * np.sin(angle)
        ax.plot([x_bar - dx, x_bar, x_bar + dx], 
                [y_bar - dy, y_bar, y_bar + dy], pattern)
    plot_bar(1, x_bar, y_bar, angle + np.radians(90), ax, 'wo-')
    plot_bar(3, x_bar, y_bar, angle, ax, 'ro-')
    ax.axis('image')


if __name__ == '__main__':
    main()

In these plots, the center dot is the centroid of the image, and the red line defines the "long" axis, while the white line defines the "short" axis.

Original (Unrotated) Paws: enter image description here

Rotated Paws: enter image description here

One thing to note here... I'm just rotating the image around its center. (Also, scipy.ndimage.rotate works as well for N-D arrays as it does for 2D. You could just as easily rotate the original 3D "pawprint-over-time" array.)

If you do want to rotate it about a point (say, the centroid), and move that point to a new position on the new image, you can do it fairly easily in scipy's ndimage module through a couple of tricks. I can give an example if you'd like. It's a bit long for this example, though...

Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Ha! You beat me by a few seconds. I'll not post my answer as the results are pretty much the same and anyway I am also unsure if this will help the OP to find some kind of useful _direction_, as it does in a human foot. +1 – Dr. belisarius May 03 '11 at 17:32
  • @belisarius - Sorry to beat you to the punch! I know the feeling... That dratted "1 new answer has been posted" bar gets me more often than not! Feel free to post yours as well, though... Never hurts to see a different way of doing things! I agree that this particular method probably isn't going to do what @Ivo wants, though. I've played around a bit with a few other things (e.g. getting centroid+moments of a thresholded image instead of the actual pressure values), but it's a rather tricky problem... – Joe Kington May 03 '11 at 17:54
  • @Joe My pictures are so similar to yours that I am afraid it will look like a plagiarism :). Anyway, I think the OP needs to consider some morphological _and_ geometrical info to get this done. I mean, not all toes are equivalent, and the fact that they may carry the same pressure does not alter their "un-equivalent" property. So, perhaps the geometry of the paw AND the CoM position _may_ be combined to get the desired direction. Just ranting. – Dr. belisarius May 03 '11 at 18:07
  • Technically the sensors are rectangular (w=0.5, h=0.7), so while the arrays are near square, in reality they might be 'somewhat' rectangular. However, I agree that it looks quite problematic. Furthermore, I was hoping this method would actually work better when I had less pronounced toes, but I fear I was wrong. – Ivo Flipse May 03 '11 at 19:48
  • I like @belisarius suggesting of mixing methods. I already did something of the sort, by drawing the axis through the two central toes and the back toe. But I'm not sure how I could use this information to 'fix' the axis, since the shape stays the same. Either way, I'm open for any other suggestions, even if it would require some 'manual' curating for each dog to feed some training set. [I would probably mix it in with marking my contacts, as you can see here](http://i.imgur.com/w615E.png) – Ivo Flipse May 03 '11 at 19:57
  • @Joe [the regular software uses 'lasts'](http://i.imgur.com/L5xoo.png) and then subdivides the area within it based on certain ratios. If I were to make an ellipsoid based on a guesstimate and make that best fit the paw. Then I'd only need to position it correctly for several paws to 'teach' the program how to position it for similar paws of the same dog right? – Ivo Flipse May 05 '11 at 14:55