This repository has been archived by the owner on May 27, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathswlda.py
106 lines (89 loc) · 4.4 KB
/
swlda.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/usr/bin/python
import numpy as np
from stepwise import stepwisefit
def swlda(responses, type, sampling_rate, response_window, decimation_frequency,
max_model_features = 60, penter = 0.1, premove = 0.15):
"""
Stepwise Linear Discriminant Analysis
``responses'' must be a (trials x samples x channels) array containing
responses to a stimulus.
``type'' must be a one-dimensional array of bools of length trials.
``sampling_rate'' is the sampling rate of the data.
``response_window'' is of the form [begin, end] in milliseconds.
``decimation_frequency'' is the frequency at which to resample.
``max_model_features'' is the maximum allowed number of features to be
chosen by stepwisefit.
``penter'' and ``premove'' are the thresholds for adding and removing
features from the model.
"""
# Housekeeping
responses = np.asarray(responses, dtype = float)
type = np.asarray(type, dtype = bool)
response_window = np.asarray(response_window)
if np.size(response_window) == 1:
# Make response_window into an array of length 2.
response_window = np.asarray([0, np.ravel(response_window)[0]])
assert np.shape(response_window) == (2,)
# End housekeeping
dec_factor = int(np.round(float(sampling_rate) / decimation_frequency))
response_window = np.asarray(np.round(
response_window * sampling_rate / 1000.), dtype = int)
trials, samples, channels = responses.shape
# The following pieces of information are now known:
# response_window ([begin, end] in samples)
# sampling_rate (in Hz)
# decimation_frequency (in Hz)
# max_model_features (total number of features allowed in final model)
# random_sampling (% of responses to be randomly selected for creating
# model)
# dec_factor (number of samples that should be decimated into one)
# trials, samples, channels
indices = np.arange(response_window[0], response_window[1] - dec_factor + 1,
dec_factor, dtype = int)
downsampled = np.zeros((trials, indices.size, channels))
for i in xrange(indices.size):
index = indices[i]
downsampled[:, i, :] = \
responses[:, index:index + dec_factor, :].mean(axis = 1)
# ``downsampled'' is now (trials x indices.size x channels).
target = type.nonzero()[0]
nontarget = (~type).nonzero()[0]
target_then_nontarget = np.concatenate((target, nontarget))
unraveled_sorted = np.swapaxes(downsampled, 1, 2).reshape(
(trials, indices.size * channels)
)[target_then_nontarget]
labels = type[target_then_nontarget] * 2 - 1
# ``unraveled_sorted'' is now (trials x (indices.size * channels)) and is
# sorted into target and non-target stimuli.
# ``labels'' contains 1s and -1s in the order of ``unraveled_sorted''.
b, se, pval, inmodel, stats, nextstep, history = stepwisefit(
unraveled_sorted, labels, maxiter = max_model_features,
penter = penter, premove = premove
)
if not inmodel.any():
return 'Could not find an appropriate model.'
b = b * 10 / abs(b).max()
b = b.reshape((channels, -1))
inmodel = inmodel.reshape((channels, -1)).nonzero()
whichchannels = np.unique(inmodel[0])
inv_channel_map = np.zeros(whichchannels.max() + 1)
inv_channel_map[whichchannels] = np.arange(1, whichchannels.size + 1)
# ``inv_channel_map'' contains the 1-based index of each channel at the
# index described by that channel (and zeros everyhere else).
weights = np.zeros((inmodel[0].size, 4))
# ``weights'' will contain three columns: channel number, sample number,
# and the weight as assigned by stepwisefit (after being adjusted).
weights[:, 0] = inv_channel_map[inmodel[0]] # already 1-based
weights[:, 1] = inmodel[1]
weights[:, 2] = 1 # channel out (for P300, this is always 1)
weights[:, 3] = b[inmodel]
restored_weights = np.tile(weights, (1, dec_factor)).reshape((-1, 4))
for i in xrange(0, restored_weights.shape[0], dec_factor):
start_val = restored_weights[i, 1] * dec_factor + 1 # 1-based
restored_weights[i:i + dec_factor, 1] = \
np.arange(start_val, start_val + dec_factor)
restored_weights = restored_weights[
restored_weights[:, 1] <= response_window[1]
] # remove anything past where we actually recorded data
# make whichchannels 1-based
return whichchannels + 1, restored_weights