ネコLGNのX細胞のスパイクトリガー平均(Spike triggered average; STA)による受容野解析をしてみます。データと設定はTheoretical Neuroscienceの演習問題(Chapter 2-3)より (神経活動の元データはP. Kara, P.Reinagel, & R.C. Reid. (2000)より)。

データとコードは https://github.com/takyamamoto/STA-for-catLGNcell で公開しています。動かしたい場合はリポジトリをCloneした後に sta_catLGN.pyを実行してください。

スパイクトリガー平均の計算

今回のスパイクトリガー平均はざっくり次のように求めます。

  1. ネコにホワイトノイズ(2D: 16 x 16)を見せ、その時のLGNのX細胞の神経活動を記録。
  2. X細胞のスパイクが生じるまでの200ms程度前までの視覚刺激を、時間ごとに加算。

今回のデータではビンが15.6 msで12ステップ前(= 187.2 ms)までの刺激に対して計算をします。ただし、スパイクのデータ(count)の1つのビンには1つ以上のスパイクが記録されており、スパイクの数に応じて刺激を重みづけ加算します。

ライブラリのimport

import numpy as np
import matplotlib.pyplot as plt
import scipy.io
import scipy.ndimage
from matplotlib import gridspec
from tqdm import tqdm

DataのloadとSTAの計算

以下はスパイクトリガー平均の計算部分のコードです。staが解析結果(12 x 16 x 16の配列)。

# Load data
data = scipy.io.loadmat('./data/lgn_sta_data/c2p3.mat')
 
# stim : 16 x 16 images that were presented at the corresponding times.
stim = data['stim'] # (16, 16, 32767)

# counts : vector containing the number of spikes in each 15.6 ms bin.
counts = data['counts']
counts = np.reshape(counts, (-1)) # (32767)

"""
Calculate the spike-triggered average images 
for each of the 12 time steps(= 187.2 ms) before each spike
"""
spike_timing = np.where(counts > 0)[0]
num_spikes = np.sum(counts > 0) # number of spikes

num_timesteps = 12
H, W = 16, 16 # height and width of stimulus image 
sta = np.zeros((H, W, num_timesteps)) #spike-triggered average

for t in spike_timing:
    if t > num_timesteps:
        sta += stim[:, :, t-num_timesteps:t]*counts[t]
   
sta /= num_spikes

受容野の可視化

空間的受容野

計算したSTAを用いて受容野の可視化を行います。全て表示すると長くなるので初めと最初だけ表示します。

#collapse-hide
for T in tqdm(range(num_timesteps)):
    if T in [0, num_timesteps-2, num_timesteps-1]: 
        fig = plt.figure(figsize=(10,3))
        fig.suptitle("The receptive field of a cat LGN X cell. (timesteps : "+str(T)+")", fontsize=14)
        gs = gridspec.GridSpec(1, 3, width_ratios=[1, 1, 2], height_ratios=[1]) 

        x = np.arange(-W//2, W//2+2, 2)
        y = np.arange(-H//2, H//2+2, 2)

        ax1 = plt.subplot(gs[0,0])
        plt.imshow(sta[:,:,T])
        plt.gca().set_aspect('equal')
        plt.gca().invert_yaxis()
        plt.xlabel('x')
        plt.ylabel('y')
        ax1.title.set_text('heatmap')
        plt.xticks(list(np.arange(0, W+1, 2)-0.5), list(x))
        plt.yticks(list(np.arange(0, H+1, 2)-0.5), list(y))

        zoom = 3
        smoothed_sta = scipy.ndimage.zoom(sta[:,:,T], zoom=zoom) # smoothed

        x = np.arange(-W//2*zoom, W//2*zoom)
        y = np.arange(-H//2*zoom, H//2*zoom)
        X, Y = np.meshgrid(x, y)

        ax2 = plt.subplot(gs[0,1])
        plt.contour(X, Y, smoothed_sta, 25)
        plt.xlabel('x')
        ax2.title.set_text('contour')
        plt.gca().set_aspect('equal')

        ax3 = plt.subplot(gs[0,2], projection='3d')
        ax3.plot_surface(X, Y, smoothed_sta, shade=True,
                         color='grey')
        ax3.set_xlabel('x')
        ax3.set_ylabel('y')
        plt.show()
        plt.close()
  0%|                                                                                           | 0/12 [00:00<?, ?it/s]
  8%|██████▉                                                                            | 1/12 [00:01<00:13,  1.20s/it]
 92%|███████████████████████████████████████████████████████████████████████████▏      | 11/12 [00:01<00:00,  1.16it/s]
100%|██████████████████████████████████████████████████████████████████████████████████| 12/12 [00:02<00:00,  4.34it/s]

いずれもcenter-surroundingな受容野を持っているのですが、最後は符号が反転しています。

問題文に

In the averaged images, you should see a central receptive field that reverses sign over time.

とあるので解析を間違えたわけではなさそうですが、ちゃんと理解できていません。

時空間的受容野

計算されたSTAについてy方向の加算を行い、x-tの軸の受容野を可視化します。

#collapse-hide

# Calculate spatio-temporal receptive field
sum_sta = np.sum(sta, axis=1).T

fig = plt.figure(figsize=(12, 3))
fig.suptitle("The spatio-temporal receptive field of a cat LGN X cell.", fontsize=14)
gs = gridspec.GridSpec(1, 3, width_ratios=[1, 1, 1.5],
                       height_ratios=[1]) 

x = np.arange(-H//2, H//2+2, 2)

ax1 = plt.subplot(gs[0,0])
plt.imshow(sum_sta)
plt.gca().set_aspect('equal')
plt.gca().invert_yaxis()
plt.xlabel('x')
plt.ylabel('time steps')
ax1.title.set_text('heatmap')
plt.xticks(list(np.arange(0, W+1, 2)-0.5), list(x))

zoom = 3
smoothed_sum_sta = scipy.ndimage.zoom(sum_sta, zoom=zoom) # smoothed

x = np.arange(-H//2*zoom, H//2*zoom)
t = np.arange(12*zoom)
X, T = np.meshgrid(x, t)

ax2 = plt.subplot(gs[0,1])
plt.contour(X, T, smoothed_sum_sta, 25)
plt.xlabel('x')

ax2.title.set_text('contour')
plt.gca().set_aspect('equal')

ax3 = plt.subplot(gs[0,2], projection='3d')
ax3.plot_surface(X, T, smoothed_sum_sta, shade=True,
                 color='grey')
ax3.set_xlabel('x')
ax3.set_ylabel('time steps')
plt.show()
plt.close()

Theoretical Neuroscienceの図2.25Cのような図が再現できました。