Posts under category Psychology | 心理学

Matlab与BrainVision脑电信号录制软件的并口通信

Matlab中可以使用io64包向并口发送数据。
io64是一个可以用作并口通信的包,其使用方法在官网有清楚的介绍。此文写作时io64包的官网挂了,可以使用互联网档案馆-io64官网20180309002101归档

下文中BrainVision脑电信号记录软件简称为Recorder。

在Matlab通过io64与Recorder的通讯过程中,信号使用并口发送,程序向Recorder发送一次Marker的通信过程如下(由测试经验得出,无官方文档):

  1. 拉高并口电平,即发送数据信号,如代码io64(ioObj, address, singal);
  2. 等待一段时间,这段时间可能与Recorder刷新率有关;
  3. 拉低并口电平,即结束此次数据发送,代码io64(ioObj, address, 0);

Recorder记录到Marker的时间是接收到第一步程序发出的电信号的时间。发送的信号最大为8个比特位,即十进制数字区间[0, 255]。

第二步等待一段时间仅仅是为了确保Recorder成功接收到了信号,建议最短不要低于0.001(测试经验所得,无文档,可能与并口线长度等有关)。

第三步拉低电平是为了结束一次数据发送,理论上第一步与第三步之间的时间(即第二部的等待时间)可以很长,测试过程中等待2秒无异常。因此第三步拉低电平的信号可以在结束一个trial之后发送,以免影响到实验过程中trial的onset或者response时间。

如果不进行第三步拉低电平的操作,Recorder虽然能够正常显示下一次发送的Marker,但是在Marker日志中会出现伪信号导致的异常Marker。

综上,给出两个demo。由于第二种没有额外等待时间的影响,因此记录各时间节点时不需要花太多精力考虑时间误差的问题。

1. 发送信号结束后开始trial

address = hex2dec('D010');
ioObj = io64();
status = io64(ioObj);
    
for i = 0:1e10
    marker =  mod(i, 9) + 1;
    
    io64(ioObj, address, marker);
    WaitSecs(0.004);
    io64(ioObj, address, 0);

    % Trial start
    WaitSecs(2);
       
end

2. 拉高电平后开始trial,结束trial后拉低电平

address = hex2dec('D010');
ioObj = io64();
status = io64(ioObj);
    
for i = 0:1e10
    marker =  mod(i, 9) + 1;
    
    io64(ioObj, address, marker);

    % Trial start
    WaitSecs(2);

    io64(ioObj, address, 0);   
end

一些使用Psychtoolbox-3的注意事项

注意本文中使用的是Psychtoolbox-3

1. 要求安装GStreamer

注意:

  1. GStreamer版本要求为1.18
  2. 通过installer安装的Psytoolbox有能力自动搜索GStreamer并将其添加到Matlab的path中,若使用免安装版,需要手动添加GStreamer的目录到path
Requires 64-Bit GStreamer 1.18 MSVC on Microsoft Windows for both Matlab and Octave. GStreamer 1.18 recommended on macOS.
Point out need for GStreamer 1.18 instead of GStreamer 1.20 more clearly.

GStreamer安装异常的结果包括但不限于:

  1. 文字字号错误
  2. 文字显示不全
  3. 衬线字体基线(baseline)对齐异常

2. add path有关

./Psychtoolbox/PsychBasic/目录中包含了不同编译环境生成的.mex文件,手动添加Psychtoolbox目录到Matlab的path后,./Psychtoolbox被添加到了最高层级,实际需要手动将./Psychtoolbox/PsychBasic/目录下与当前操作系统匹配的目录移到最顶层。

Python:样本和总体方差的比较示意图

未标题-1.jpg

绘制所用的Python代码如下:

import matplotlib.pyplot as plt
from scipy import linspace, sqrt
from scipy.special import erf
import numpy as np

color = '#339999'
color2 = '#660033'


def pdf(x, mu=0, sigma=1):
    """
    Calculates the normal distribution's probability density
    function (PDF).

    """
    term1 = 1.0 / (sqrt(2 * np.pi) * sigma)
    term2 = np.exp(-0.5 * ((x - mu) / sigma) ** 2)
    return term1 * term2


def cdf(_x):
    return (1 + erf(_x / sqrt(2))) / 2


def skew(_x, e, _w, a, z=100):
    t = (_x - e) / _w
    return z / _w * pdf(t) * cdf(a * t)


plt.figure(figsize=(20, 10))
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

n = 129
w = 2.5
y_text_offset = -0.5
y_title_offset = 8.1

fontsize = 24

middle_x = 0

x = linspace(middle_x - w * 3.3, middle_x + w * 3.3, n)
p = skew(x, middle_x, 3, 0)
plt.plot(x, p, 'r-', color=color, linewidth=3.0)
middle_index = list(p).index(max(p))
plt.text(x[middle_index + 12] - 0.4, max(p)-0.4, '总体', fontsize=fontsize, color=color)
plt.plot([x[middle_index], x[middle_index]], [0, p[middle_index]], '--', color=color,
         linewidth=3.0)
plt.text(x[middle_index] - 0.1, y_text_offset, '$\mu$', fontsize=fontsize, color=color)

plt.ylim([0, max(p) + 0.2])

middle_x = middle_x + 0.4
x = linspace(middle_x - w * 1.5, middle_x + w * 1.5, n)
p = skew(x, middle_x, 1.4, 0, 24)
plt.plot(x, p, 'r-', color=color2, linewidth=3.0)
plt.text(x[middle_index + 12] - 0.4, max(p), '样本', fontsize=fontsize, color=color2)
plt.plot([x[middle_index], x[middle_index]], [0, p[middle_index]], '--', color=color2,
         linewidth=3.0)
plt.text(x[middle_index] - 0.1, y_text_offset, r'$\barX$', fontsize=fontsize, color=color2)
print([min(x), max(x)])
plt.plot([min(x), max(x)], [y_text_offset, y_text_offset], 'r-', color=color2,
         linewidth=3.0)

plt.title('样本和总体的方差比较', fontsize=fontsize)

plt.yticks([])
plt.xticks([])

plt.show()

t检验与点二列相关的异同

t检验、点二列相关
两者侧重点不同,前者重在检验两列数据的均值差异,即宏观上是否来自同一总体,后者关注两列数据的相关程度。
相同点在都是一个二分数据将一列等距或等比数据分类,且两者数值可以通过公式相互转换。

转换的Python代码如下:

import math
import numpy as np


# s1 = [10, 9, 8, 8, 8, 7]
# s2 = [5, 5, 4, 4]

# s1 = [67, 73, 74, 70, 70, 75, 73, 68, 69]
# s2 = [69, 63, 67, 64, 61, 66, 60, 63, 63]

s1 = [84, 84, 88, 90, 78, 92, 94, 96, 88, 90]
s2 = [82, 76, 60, 72, 74, 76, 80, 78, 76, 74]

xbar1 = np.mean(s1)
xbar2 = np.mean(s2)


# Calculate sum of squares(和方)
def calc_ss(s):
  return sum(map(lambda x: x * x, s)) - sum(s) ** 2 / len(s)


ss1 = calc_ss(s1)
ss2 = calc_ss(s2)

xsquare1 = ss1 / (len(s1) - 1)
xsquare2 = ss2 / (len(s2) - 1)

print('Xbar1:', xbar1)
print('Xbar2:', xbar2)

print('SS1:', ss1)
print('SS2:', ss2)

print('Xsquare1:', xsquare1)
print('Xsquare2:', xsquare2)

spsquare = (ss1 + ss2) / (len(s1) + len(s2) - 2)
# 总体方差
print('SPsquare', spsquare)

se = np.sqrt( spsquare / len(s1) + spsquare / len(s2) )

print('SE:', se)


tobs = (xbar1 - xbar2) / se

print('t_obs:', tobs)

print('---------------------')

s3 = s1 + s2

sx = np.sqrt( calc_ss(s3) / len(s3) )

print('sx:', sx)

rpb = ((xbar1 - xbar2) / sx) * np.sqrt( (len(s1) / len(s3)) * (len(s2) / len(s3)) )

print('r_pb:', rpb)
print('t_obs:', tobs)
print('Fmax:', max(xsquare1, xsquare2) / min(xsquare1, xsquare2))

print(rpb ** 2, 'is r_pb^2')
print(tobs ** 2 / (tobs ** 2 + len(s1) + len(s2) - 2), 'is t^2 / (t^2 + df)')

Output:

Xbar1: 88.4
Xbar2: 74.8
SS1: 254.39999999999418
SS2: 321.59999999999854
Xsquare1: 28.26666666666602
Xsquare2: 35.73333333333317
SPsquare 31.999999999999595
SE: 2.5298221281346875
t_obs: 5.375872022286282
---------------------
sx: 8.662563131083052
r_pb: 0.7849870641173408
t_obs: 5.375872022286282
Fmax: 1.2641509433962497
0.616204690831562 is r_pb^2
0.6162046908315598 is t^2 / (t^2 + df)