How to sort my paws?

In my previous question I got an excellent answer that helped me detect where a paw hit a pressure plate, but now I'm struggling to link these results to their corresponding paws:

alt text

I manually annotated the paws (RF=right front, RH= right hind, LF=left front, LH=left hind).

As you can see there's clearly a repeating pattern and it comes back in almost every measurement. Here's a link to a presentation of 6 trials that were manually annotated.

My initial thought was to use heuristics to do the sorting, like:

  • There's a ~60-40% ratio in weight bearing between the front and hind paws;
  • The hind paws are generally smaller in surface;
  • The paws are (often) spatially divided in left and right.

However, I’m a bit skeptical about my heuristics, as they would fail on me as soon as I encounter a variation I hadn’t thought off. They also won’t be able to cope with measurements from lame dogs, whom probably have rules of their own.

Furthermore, the annotation suggested by Joe sometimes get's messed up and doesn't take into account what the paw actually looks like.

Based on the answers I received on my question about peak detection within the paw, I’m hoping there are more advanced solutions to sort the paws. Especially because the pressure distribution and the progression thereof are different for each separate paw, almost like a fingerprint. I hope there's a method that can use this to cluster my paws, rather than just sorting them in order of occurrence.

alt text

So I'm looking for a better way to sort the results with their corresponding paw.

For anyone up to the challenge, I have pickled a dictionary with all the sliced arrays that contain the pressure data of each paw (bundled by measurement) and the slice that describes their location (location on the plate and in time).

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.

Also note that 'false' impacts, such as where the paw is partially measured (in space or time) can be ignored. They are only useful because they can help recognizing a pattern, but won't be analyzed.

And for anyone interested, I’m keeping a blog with all the updates regarding the project!

11566 次浏览

使用纯粹基于持续时间的信息,我认为您可以应用来自运动学建模的技术,即 逆运动学。结合方向,长度,持续时间和总重量,它提供了一定程度的周期性,我希望可以成为第一步,试图解决您的“排序爪子”的问题。

All that data could be used to create a list of bounded polygons (or tuples), which you could use to sort by step size then by paw-ness [index].

你能让技术人员手动输入第一个爪子(或前两个爪子)进行测试吗? 过程可能是:

  • 显示技术的步骤顺序图像,并要求他们注释第一爪。
  • 根据第一只爪子标记其他爪子,让技术人员进行修正或重新运行测试。这允许瘸腿或三条腿的狗。

好吧!我终于设法让某些东西持续工作了!这个问题困扰了我好几天... 有趣的东西!对不起,这个答案的长度,但我需要详细说明一些事情... (虽然我可能会创造一个最长的非垃圾邮件堆栈溢出答案!)

作为一个侧面说明,我正在使用的完整数据集,在他的 原始问题艾沃 提供了一个链接到。它是一系列 rar 文件(每只狗一个) ,每个文件包含几个不同的实验运行,存储为 ascii 数组。与其尝试将独立代码示例复制粘贴到这个问题中,不如使用带有完整的独立代码的 位桶式汞储存库。你可以克隆它

hg clone https://joferkington@bitbucket.org/joferkington/paw-analysis


Overview

正如您在问题中指出的,本质上有两种方法来处理这个问题。我会用不同的方式来使用它们。

  1. 使用(时间和空间)顺序的爪子的影响,以确定哪一个爪子是哪一个。
  2. Try to identify the "pawprint" based purely on its shape.

基本上,第一种方法是按照上面 Ivo 问题中的梯形图案来操作狗的爪子,但是当狗的爪子没有按照梯形图案来操作时,这种方法就失败了。当它不工作时,通过编程很容易检测到。

因此,我们可以利用这些测量数据来建立一个训练数据集(来自30只不同的狗的2000个爪子碰撞的数据)来识别哪个爪子是哪个,这个问题可以归结为一个监督分类(有一些额外的皱纹... ... 图像识别比“正常”的监督分类问题要难一些)。


模式分析

详细说明第一种方法,当狗在散步时(不是在跑!)正常情况下(其中一些狗可能不是) ,我们希望爪子的影响顺序是: 前左,后右,前右,后左,前左等。图案可以从左前爪或右前爪开始。

如果情况总是如此,我们可以简单地按照初始接触时间对影响进行排序,并使用模块4按照爪子将它们分组。

Normal Impact Sequence

然而,即使一切都“正常”,这种方法也不起作用。这是由于梯形形状的模式。后爪在空间上落在前爪的后面。

因此,后爪碰撞后的初始前爪碰撞往往脱落的传感器板,并没有记录。同样,最后一次爪击通常不是顺序中的下一次爪击,因为爪击之前发生在传感器板上,没有记录。

Missed Hind Paw

尽管如此,我们可以利用爪子撞击的形状来判断这是什么时候发生的,以及我们是从左前爪还是右前爪开始的。(实际上我忽略了最后一次撞击的问题。不过,添加它并不太难。)

def group_paws(data_slices, time):
# Sort slices by initial contact time
data_slices.sort(key=lambda s: s[-1].start)


# Get the centroid for each paw impact...
paw_coords = []
for x,y,z in data_slices:
paw_coords.append([(item.stop + item.start) / 2.0 for item in (x,y)])
paw_coords = np.array(paw_coords)


# Make a vector between each sucessive impact...
dx, dy = np.diff(paw_coords, axis=0).T


#-- Group paws -------------------------------------------
paw_code = {0:'LF', 1:'RH', 2:'RF', 3:'LH'}
paw_number = np.arange(len(paw_coords))


# Did we miss the hind paw impact after the first
# front paw impact? If so, first dx will be positive...
if dx[0] > 0:
paw_number[1:] += 1


# Are we starting with the left or right front paw...
# We assume we're starting with the left, and check dy[0].
# If dy[0] > 0 (i.e. the next paw impacts to the left), then
# it's actually the right front paw, instead of the left.
if dy[0] > 0: # Right front paw impact...
paw_number += 2


# Now we can determine the paw with a simple modulo 4..
paw_codes = paw_number % 4
paw_labels = [paw_code[code] for code in paw_codes]


return paw_labels

尽管如此,它还是经常不能正常工作。在完整的数据集中,许多狗狗似乎都在跑步,而且爪子的碰撞并不遵循与狗狗散步时相同的时间顺序。(或者狗只是有严重的髋关节问题... ...)

Abnormal Impact Sequence

幸运的是,我们仍然可以通过编程检测爪子的撞击是否遵循我们预期的空间模式:

def paw_pattern_problems(paw_labels, dx, dy):
"""Check whether or not the label sequence "paw_labels" conforms to our
expected spatial pattern of paw impacts. "paw_labels" should be a sequence
of the strings: "LH", "RH", "LF", "RF" corresponding to the different paws"""
# Check for problems... (This could be written a _lot_ more cleanly...)
problems = False
last = paw_labels[0]
for paw, dy, dx in zip(paw_labels[1:], dy, dx):
# Going from a left paw to a right, dy should be negative
if last.startswith('L') and paw.startswith('R') and (dy > 0):
problems = True
break
# Going from a right paw to a left, dy should be positive
if last.startswith('R') and paw.startswith('L') and (dy < 0):
problems = True
break
# Going from a front paw to a hind paw, dx should be negative
if last.endswith('F') and paw.endswith('H') and (dx > 0):
problems = True
break
# Going from a hind paw to a front paw, dx should be positive
if last.endswith('H') and paw.endswith('F') and (dx < 0):
problems = True
break
last = paw
return problems

因此,即使简单的空间分类并不总是有效,我们也可以合理地确定它何时有效。

培训数据集

通过正确使用基于模式的分类,我们可以建立一个非常大的正确分类爪子的训练数据集(来自32只不同的狗的约2400个爪子碰撞!).

我们现在可以开始看看“平均”前左等爪子的样子。

To do this, we need some sort of "paw metric" that is the same dimensionality for any dog. (In the full dataset, there are both very large and very small dogs!) A paw print from an Irish elkhound will be both much wider and much "heavier" than a paw print from a toy poodle. We need to rescale each paw print so that a) they have the same number of pixels, and b) the pressure values are standardized. To do this, I resampled each paw print onto a 20x20 grid and rescaled the pressure values based on the maximum, mininum, and mean pressure value for the paw impact.

def paw_image(paw):
from scipy.ndimage import map_coordinates
ny, nx = paw.shape


# Trim off any "blank" edges around the paw...
mask = paw > 0.01 * paw.max()
y, x = np.mgrid[:ny, :nx]
ymin, ymax = y[mask].min(), y[mask].max()
xmin, xmax = x[mask].min(), x[mask].max()


# Make a 20x20 grid to resample the paw pressure values onto
numx, numy = 20, 20
xi = np.linspace(xmin, xmax, numx)
yi = np.linspace(ymin, ymax, numy)
xi, yi = np.meshgrid(xi, yi)


# Resample the values onto the 20x20 grid
coords = np.vstack([yi.flatten(), xi.flatten()])
zi = map_coordinates(paw, coords)
zi = zi.reshape((numy, numx))


# Rescale the pressure values
zi -= zi.min()
zi /= zi.max()
zi -= zi.mean() #<- Helps distinguish front from hind paws...
return zi

After all of this, we can finally take a look at what an average left front, hind right, etc paw looks like. Note that this is averaged across >30 dogs of greatly different sizes, and we seem to be getting consistent results!

Average Paws

然而,在我们做任何分析之前,我们需要减去平均值(所有狗腿的平均爪子)。

Mean Paw

现在我们可以分析平均值的差异,这个更容易识别:

Differential Paws

基于图像的足爪识别

好了,我们终于找到了一组图案可以开始比对爪子了。每个爪子可以被视为一个400维向量(由 paw_image函数返回) ,可以与这四个400维向量进行比较。

不幸的是,如果我们只是使用一个“正常”的监督分类算法(即使用一个简单的距离来找出4种模式中哪一种最接近特定的爪印) ,它就不能始终如一地工作。事实上,它并不比训练数据集上的随机机会好多少。

这是图像识别中的一个常见问题。由于输入数据的高维度,以及图像的某种“模糊”特性(即相邻像素具有高协方差) ,简单地看图像与模板图像的差异并不能很好地衡量它们形状的相似性。

特制爪子

为了解决这个问题,我们需要建立一组“特征爪”(就像面部识别中的“特征脸”) ,并将每个爪印描述为这些特征爪的组合。这与主成分分析相同,基本上提供了一种降低数据维数的方法,因此距离是一个很好的形状度量。

Because we have more training images than dimensions (2400 vs 400), there's no need to do "fancy" linear algebra for speed. We can work directly with the covariance matrix of the training data set:

def make_eigenpaws(paw_data):
"""Creates a set of eigenpaws based on paw_data.
paw_data is a numdata by numdimensions matrix of all of the observations."""
average_paw = paw_data.mean(axis=0)
paw_data -= average_paw


# Determine the eigenvectors of the covariance matrix of the data
cov = np.cov(paw_data.T)
eigvals, eigvecs = np.linalg.eig(cov)


# Sort the eigenvectors by ascending eigenvalue (largest is last)
eig_idx = np.argsort(eigvals)
sorted_eigvecs = eigvecs[:,eig_idx]
sorted_eigvals = eigvals[:,eig_idx]


# Now choose a cutoff number of eigenvectors to use
# (50 seems to work well, but it's arbirtrary...
num_basis_vecs = 50
basis_vecs = sorted_eigvecs[:,-num_basis_vecs:]


return basis_vecs

这些 basis_vecs是“特征爪子”。

Eigenpaws

为了使用这些,我们简单地用基本向量点(即矩阵乘法)每个爪子图像(作为一个400维的向量,而不是一个20x20的图像)。这给了我们一个50维向量(每个基向量一个元素) ,我们可以用它来对图像进行分类。我们不将20x20的图像与每个“模板”爪子的20x20的图像进行比较,而是将50维转换后的图像与每个50维转换后的模板爪子进行比较。这是非常不敏感的微小变化,每个脚趾是如何定位等,基本上减少了问题的维度,只有相关的维度。

Eigenpaw-based Paw Classification

现在我们可以简单地使用50维向量和每条腿的“模板”向量之间的距离来分类哪个爪子是哪个:

codebook = np.load('codebook.npy') # Template vectors for each paw
average_paw = np.load('average_paw.npy')
basis_stds = np.load('basis_stds.npy') # Needed to "whiten" the dataset...
basis_vecs = np.load('basis_vecs.npy')
paw_code = {0:'LF', 1:'RH', 2:'RF', 3:'LH'}
def classify(paw):
paw = paw.flatten()
paw -= average_paw
scores = paw.dot(basis_vecs) / basis_stds
diff = codebook - scores
diff *= diff
diff = np.sqrt(diff.sum(axis=1))
return paw_code[diff.argmin()]

以下是一些结果: alt text alt text alt text

Remaining Problems

仍然存在一些问题,特别是狗太小以至于无法清晰的掌印... ... (对于大型狗来说效果最好,因为在传感器的分辨率下,脚趾分离得更清楚。)此外,该系统不能识别部分爪印,而梯形模式系统可以识别部分爪印。

然而,由于特征爪分析本身使用距离度量,我们可以分类爪子两种方式,并退回到梯形模式为基础的系统时,特征爪分析的最小距离从“码书”超过一些阈值。不过我还没有实现这个。

好长啊! 艾沃问了这么有趣的问题,我要向他脱帽致敬!