Представьте, что у вас очень длинная последовательность. Каков наиболее эффективный способ нахождения интервалов, где последовательность - все нули (точнее, последовательность падает до почти нулевых значений abs(X)<eps
):
Для простоты предположим следующую последовательность:
sig = [1 1 0 0 0 0 1 1 1 1 1 0 1 0 0 0 1 1 1 1 1 1 1 1 0 0 1 1 1 0];
Я пытаюсь получить следующую информацию:
startIndex EndIndex Duration
3 6 4
12 12 1
14 16 3
25 26 2
30 30 1
то, используя эту информацию, мы найдем интервалы с длительностью >= до некоторого заданного значения (скажем 3
) и возвращаем индексы значений во всех этих интервалах:
indices = [3 4 5 6 14 15 16];
Эта последняя часть связана с предыдущим вопросом:
MATLAB: создание векторизованного массива из списка начальных/конечных индексов
Это то, что у меня есть до сих пор:
sig = [1 1 0 0 0 0 1 1 1 1 1 0 1 0 0 0 1 1 1 1 1 1 1 1 0 0 1 1 1 0];
len = length(sig);
thresh = 3;
%# align the signal with itself successively shifted by one
%# v will thus contain 1 in the starting locations of the zero interval
v = true(1,len-thresh+1);
for i=1:thresh
v = v & ( sig(i:len-thresh+i) == 0 );
end
%# extend the 1 till the end of the intervals
for i=1:thresh-1
v(find(v)+1) = true;
end
%# get the final indices
v = find(v);
Я ищу для векторизации/оптимизации кода, но я открыт для других решений. Я должен подчеркнуть, что эффективность пространства и времени очень важна, поскольку я обрабатываю большое количество длинных биосигналов.
Это шаги, которые я предприму для решения вашей проблемы в векторизованном виде, начиная с заданного вектора sig
:
Сначала порог вектора, чтобы получить вектор tsig
нулей и единиц (нулей, где абсолютное значение сигнала падает достаточно близко к нулю, в другом месте):
tsig = (abs(sig) >= eps); %# Using eps as the threshold
Затем найдите начальные индексы, конечные индексы и продолжительность каждой строки нулей, используя функции DIFF и FIND:
dsig = diff([1 tsig 1]);
startIndex = find(dsig < 0);
endIndex = find(dsig > 0)-1;
duration = endIndex-startIndex+1;
Затем найдите строки нулей с длительностью, большей или равной некоторому значению (например, 3 из вашего примера):
stringIndex = (duration >= 3);
startIndex = startIndex(stringIndex);
endIndex = endIndex(stringIndex);
Наконец, используйте метод из моего ответа на связанный вопрос, чтобы создать окончательный набор индексов:
indices = zeros(1,max(endIndex)+1);
indices(startIndex) = 1;
indices(endIndex+1) = indices(endIndex+1)-1;
indices = find(cumsum(indices));
Вы можете решить это как задачу поиска строк, найдя строки нулей длины thresh
(функция STRFIND выполняется очень быстро)
startIndex = strfind(sig, zeros(1,thresh));
Обратите внимание, что более длинные подстроки будут отмечены в нескольких местах, но со временем будут объединены, когда мы добавим промежуточные местоположения с интервалов, начинающихся с startIndex
, чтобы заканчиваться на start+thresh-1
.
indices = unique( bsxfun(@plus, startIndex', 0:thresh-1) )';
Обратите внимание, что вы всегда можете поменять этот последний шаг на решение CUMSUM/FIND @gnovice из связанного вопроса.
diff/find
от @gnovice и conv
от @emailhy
Здесь он находится в numpy (также ответил здесь)
def nonzero_intervals(vec):
'''
Find islands of non-zeros in the vector vec
'''
if len(vec)==0:
return []
elif not isinstance(vec, np.ndarray):
vec = np.array(vec)
edges, = np.nonzero(np.diff((vec==0)*1))
edge_vec = [edges+1]
if vec[0] != 0:
edge_vec.insert(0, [0])
if vec[-1] != 0:
edge_vec.append([len(vec)])
edges = np.concatenate(edge_vec)
return zip(edges[::2], edges[1::2])
например:
a=[1, 2, 0, 0, 0, 3, 4, 0]
intervals = nonzero_intervals(a)
assert intervals == [(0, 2), (5, 7)]
приведенный ответ genovice может быть изменен, чтобы найти индексы ненулевых элементов в векторе как:
tsig = (abs(sig) >= eps);
dsig = diff([0 tsig 0]);
startIndex = find(dsig > 0);
endIndex = find(dsig < 0)-1;
duration = endIndex-startIndex+1;
function indice=sigvec(sig,thresh)
%extend sig head and tail to avoid 0 head and 0 tail
exsig=[1,sig,1];
%convolution sig with extend sig
cvexsig=conv(exsig,ones(1,thresh));
tempsig=double(cvexsig==0);
indice=find(conv(tempsig,ones(1,thresh)))-thresh;
Как показал gnovice, мы сделаем пороговый тест, чтобы сделать "почти нулевой" действительно нулевым:
logcl = abs(sig(:)) >= zero_tolerance;
Затем найдите области, где кумулятивная сумма не увеличивается:
cs = cumsum(logcl);
islands = cs(1+thresh:end) == cs(1:end-thresh);
Вспоминая gnovice отличный метод для заполнения диапазонов индексов
v = zeros(1,max(endInd)+1); %# An array of zeroes v(startInd) = 1; %# Place 1 at the starts of the intervals v(endInd+1) = v(endInd+1)-1; %# Add -1 one index after the ends of the intervals indices = find(cumsum(v)); %# Perform a cumulative sum and find the nonzero entries
Заметим, что наш вектор islands
уже имеет единицы в местах startInd
, и для наших целей endInd
всегда появляется thresh
пятна позже (более длинные прогоны имеют прогоны в islands
)
endcap = zeros(thresh,1);
indices = find(cumsum([islands ; endcap] - [endcap ; islands]))
sig = [1 1 0 0 0 0 1 1 1 1 1 0 1 0 0 0 1 1 1 1 1 1 1 1 0 0 1 1 1 0];
logcl = abs(sig(:)) >= .1;
cs = cumsum(logcl);
islands = cs(1+thresh:end) == cs(1:end-thresh);
endcap = zeros(thresh,1);
indices = find(cumsum([islands ; endcap] - [endcap ; islands]))
indices = 2 3 4 5 13 14 15
Я думаю, что самый MATLAB/ "векторизованный" способ сделать это - вычислить свертку вашего сигнала с фильтром, подобным [-1 1]. Вы должны посмотреть документацию функции conv. Затем на выходе conv используйте find для получения соответствующих индексов.