分享一个计算计算 SAP (stretch-attend posture) 的 MATLAB 代码

SAP 定义:

Ref.1: Murray, T. A 等人 2016 年发表的文章的解释:

Stretch-attend posture (SAP) occurs during risk assessment and is prevalent in common rodentce behavioral tests.

– Holly, K. S., Orndorff, C. O., & Murray, T. A. (2016). MATSAP: An automated analysis of stretch-attend posture in rodent behavioral experiments. Scientific reports , 6 (1), 31286.

MATSAP: An automated analysis of stretch-attend posture in rodent behavioral experiments

Ref.2: mousebehavior.org 网站的解释:

Stretched attend is an investigatory posture that is typically seen when the mouse is investigating a potentially dangerous stimulus or environment. The posture serves to maintain the body in a safe location, and to allow rapid retreat, while extending the head to investigate.

Investigate - Stretched Attend

根据上述释义,当动物在探索潜在危险的刺激或环境时,通常会看到这种姿势。这个姿势是为了保持身体在一个安全的位置,并允许快速撤退,同时延伸头部进行探索。

上述 Ref.1 提供了一个工具包 MATSAP 用于计算 SAP,不过使用起来相对麻烦。如果基于BehaviorAtlas的 3D 数据如何计算 SAP 呢?下面给出详细教程。

数据准备

  • 首先,如果跑完了 BehaviorAtlas Analyzer,你会在工程里面得到 【results\BeAOutputs】文件夹,里面存放着每个样本的 .h5 格式的结果文件。

  • h5 文件中存放着 3D 的身体坐标数据
    image

代码主脚本

clc; clear; close all;

% Set working path -> [results\BeAOutputs]
current_path = 'Y:\huangkang\data_processing\demo\';

% You can modify this threshold based on experience and actual situation
ecc_thr = 0.89; % default 0.89.
speed_thr = 80; % default 80 mm/s.

fps = 30; % video fps

show_ellipse = false;
show_SAP = true;

% Load body trajectory for all samples
data_3dskls = load_3dskl_h5([current_path, 'results\BeAOutputs\']);
n_sample = size(data_3dskls, 1);

% Process each sample

for ism = 1:n_sample
    body_parts_3d = data_3dskls.data3d{ism};

    SAP_flag = cal_SAP_3d(body_parts_3d, show_ellipse, ecc_thr, speed_thr, fps, show_SAP);

    pause

end

load_3dskl_h5 函数批量加载 3D 身体坐标数据

function data_3dskls = load_3dskl_h5(skl_path)
skl_files = dir([skl_path,'*results.h5']);
data_name = natsort({skl_files.name});
data_name = data_name';

n_data = length(data_name);

data_3dskls = struct();

back_idx = 13;
for id = 1:n_data
    data = h5read([skl_path, cell2mat(data_name(id))], '/3Dskeleton/data3D')';
    body_part_name = deblank(h5read([skl_path, cell2mat(data_name(id))], '/3Dskeleton/Bodyparts'))';

    fps = double(h5read([skl_path, cell2mat(data_name(id))], '/3Dskeleton/FPS'));

    offset_X = (max(data(:,back_idx*3-2)) + min(data(:,back_idx*3-2)))/2;
    offset_Y = (max(data(:,back_idx*3-1)) + min(data(:,back_idx*3-1)))/2;

    sample_name = data_name{id};
    sample_name = split(sample_name, '_');
    sample_name = sample_name{1};
    data_3dskls(id).sample_name = sample_name;
    
    group_label = split(sample_name, '-');
    group_label = group_label{3};
    data_3dskls(id).group_label = group_label;


    data_3dskls(id).data3d = data;

    posX = 500*normalize(data(:,1:3:end), 'range');
    posY = 500*normalize(data(:,2:3:end), 'range');

    data_3dskls(id).x = posX;
    data_3dskls(id).y = posY;
    data_3dskls(id).z = data(:,3:3:end);

    data_3dskls(id).fps = fps;
    data_3dskls(id).body_part_name = {body_part_name};

    disp(['load_3dskl: ', num2str(id)]);
%     hold on
%     scatter(data_3dskls(id).x(:,13),data_3dskls(id).y(:,13))
% 
%     drawnow
end

data_3dskls = struct2table(data_3dskls);

cal_SAP_3d 计算 SAP 函数

function SAP_flag = cal_SAP_3d(body_parts_3d, show_ellipse, ecc_thr, speed_thr, fps, show_SAP)

% body_parts_3d: 3D coordinates of mouse body parts, N*M array, N is the
%                numer of frame, M/3 is the number of body parts.

% SAP_flag: N*1 boolean array

n_frame = size(body_parts_3d, 1);
eccentricity = zeros(n_frame, 1);

back_idx = 13; % back point index

if show_ellipse
    figure
end

disp('Calculating eccentricity, please wait!')
for ifi = 1:n_frame

    % Calculate eccentricity
    bp_x = body_parts_3d(ifi, 1:3:42);
    bp_y = body_parts_3d(ifi, 2:3:42);

    if show_ellipse
        h1 = subplot(121);
        mesh_mouse(body_parts_3d, ifi);
        axis equal; view([0, 90]); zlim([-50, 50]);
        hold on;
        title('Ellipse fitting for 3D mouse (Top view)')

        h2 = subplot(122);
        mesh_mouse(body_parts_3d, ifi);
        axis equal; view([-50, 30]); zlim([-50, 50]);
        hold on;
        title('Ellipse fitting for 3D mouse (3D view)')
    end

    if show_ellipse
        subplot(121)
        fit_ellipse(bp_x, bp_y, h1);
        subplot(122)
        ellipse_t = fit_ellipse(bp_x, bp_y, h2);
        pause(0.01);
        cla(h1); cla(h2);
    else
        ellipse_t = fit_ellipse(bp_x, bp_y);
    end

    if ~isempty(ellipse_t.a)
        a = ellipse_t.long_axis;
        b = ellipse_t.short_axis;
        if a == 0
            eccentricity(ifi) = 0;
        else
            eccentricity(ifi) = sqrt(a^2 - b^2)/a;
        end
    else
        eccentricity(ifi) = eccentricity(ifi - 1);
    end
end

eccentricity_sm =  smooth(eccentricity, fps*1/2);
% Calculate speed
body_3d = fillmissing(body_parts_3d, 'linear');  % mat 格式

X = body_3d(:, back_idx*3-2);
Y = body_3d(:, back_idx*3-1);

mean_pos = [X, Y];
speed_raw = diff(mean_pos);

% 2D speed
speed2 = sqrt(speed_raw(:, 1).^2 + speed_raw(:, 2).^2);
speed2 = speed2/(1/fps);
speed2 = [speed2(1); speed2];
speed_sm2 = smooth(speed2, fps*1/2);

SAP_flag = speed_sm2 < speed_thr & eccentricity_sm > ecc_thr;

if show_SAP
    ts = linspace(1/fps, n_frame/fps, n_frame);
    figure
    subplot(311)
    plot(ts, speed_sm2)
    xlabel('Time (s)'); ylabel('Speed (mm/s)')

    subplot(312)
    plot(ts, eccentricity_sm)
    xlabel('Time (s)'); ylabel('Eccentricity')

    subplot(313)
    plot(ts, SAP_flag)
    xlabel('Time (s)'); ylabel('SAP')

end

这个函数几个关键步骤,一是根据小鼠 X-Y 平面做椭圆拟合,计算 eccentricity 偏心率;然后计算小鼠的 X-Y 平面的位移速度。当某一帧 speed_sm2 < speed_thr & eccentricity_sm > ecc_thr 同时满足时,即可判定为 SAP。

3D 小鼠椭圆拟合的效果:

SAP 的计算结果:

1 个赞

有没有办法验证这个系统的信度呢?

一种方法是对照视频,另一种是人工标一些数据,进行验证误差。