MATLAB: binary array count of groups longer than N - arrays

I have a binary array that represents object detection for individual frames of a video. I'm trying to determine from this vector how many separate events there are. I need to figure out a way to count the number of clusters of 1's in the binary array.
What's the easiest way using Matlab functions to determine how many individual groups of consecutive 1's there are that are larger than say, N=5?
For example for the array:
1 1 0 0 0 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 0 0 1 0 0 1 I would like the output to be 2, because there are 2 groups of 1's longer than N.
I need an efficient way to do this rather than just looping through because I have to run this on about 20 thousand short videos. Kind of hoping there's a built in function for this purpose that I've missed, but any solution is welcome.
The ugly version of this code that I'm trying to speed up looks like this:
% Count Events
EventCount = 0;
subcount = 0;
N = 5;
for e=1:length(events) % events is a binary array
if (events(e) == 1) && (subcount == 0)
subcount = 1;
elseif events(e) == 1
subcount = subcount + 1;
elseif (events(e) == 0) && (subcount > N)
EventCount = EventCount + 1;
subcount = 0;
elseif (events(e) == 0) && (subcount <= N)
subcount = 0;
else
disp('Oops, should not get here!');
end
end
disp(EventCount);

A one linear solution:
sum(accumarray(1+cumsum([0 diff( events)==1].'),events.')>N)
Compute starting index of blocks of 1s:
idx = diff(events.')==1;
Assign a category number to each group:
catnum=1+cumsum([0 idx].');
Count number of 1s in each category
count = accumarray(catnum,events);
count how many groups of 1's are longer than N
sum(count>N)

Related

MATLAB : How can I generate a random binary matrix with a specific number of 1s in each row and in each column?

I would like to generate a binary matrix A of dimension m * n with a unique number J of 1s on each column of the matrix and a unique number K of 1s on each row of the matrix.
For example, for a binary matrix A of dimension m * n = 5 * 10 with J = 3 and K = 6 we may obtain the following matrix:
1 0 1 1 1 0 1 1 0 0
0 1 1 0 0 1 1 0 1 1
1 1 0 1 1 1 0 1 0 0
0 1 1 0 1 0 1 0 1 1
1 0 0 1 0 1 0 1 1 1
This answer by Luis Mendo, gives just the specific number of 1s on each column. In my case I'm trying to add the option of the specific factor of 1s on each column and on each row, which can be a different number.
How can I construct this matrix?
I made this just for fun. It can get stuck, so it doesn't work all the time. For the values in your example, the function found a matrix in about 40 % of the runs. You might want to tinker a bit with max_iterations. If it's too small, the probability of finding a matrix goes down, but if it's too large it's really time consuming.
Note: I wrote this in python and haven't run it in matlab, so there might be typos.
function A = generate_matrix(m,n,J,K, max_iterations)
if nargin < 5
max_iterations = m*K*4;
end
% Check if it's possible to create matrix:
if (m*K ~= n*J)
error('Matrix not possible')
end
A = falses(m,n);
allowed_rows = 1:m;
allowed_cols = 1:n;
too_many_iterations = false;
counter = 0;
while ~isempty(allowed_rows) && ~too_many_iterations
% pick random row and column from the ones that don't have enough ones
row_index = allowed_rows(randi(length(allowed_rows)));
col_index = allowed_cols(randi(length(allowed_cols)));
A(row_index, col_index) = true;
% Update allowed_rows and allowed_cols
allowed_rows = find(sum(A,2) < K);
allowed_cols = find(sum(A,1) < J);
% Keep track of number of iterations
counter = counter + 1;
too_many_iterations = counter > max_iterations;
end
if ~isempty(allowed_rows)
warning('Matrix not found!')
A = [];
end
end
The function will start with a matrix of zeros and randomly pick a row and column that doesn't have enough ones and place a one at that location, then pick a new row and column and so on until there are no rows left. The reason it doesn't work all the time, is that it can reach a dead end. For example, with m=3, n=6, J=2 and K=4, the following matrix is a dead end, since the only row without four ones is row 1, the only column without two ones is column 3, but A[1,3] already is one.
[1, 0, 1, 1, 0, 0;
1, 1, 0, 0, 1, 1;
0, 1, 0, 1, 1, 1]

Add values in array and compare with threshold within loop in Matlab

I am stuck trying to figure this out. I have an array:
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1]
I want to add the values in the array so that it equal to 10. Once the added value reaches 10, I want the array to start adding the value again until it reaches 10. There is two problem that I face here,
1) How can I add the array so that the sum = 10 everytime. Notice that in the array, there is 3. If I add all the value before 3, I get 8 and I only need 2 from 3. I need to make sure that the remainder, which is 1 is added to the next array to get the sum 10.
2) How do I break the loop once it reaches 10 and ask it continue the summation to next value to get another 10?
I created a loop but it only works for the first part of the array. I have no idea how to make it continue. The code is as follow:
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1];
c = 0;
for i = 1:length(a)
while c < 10
c = c + a(i);
break
end
end
Please help. Thank you
This can be done using cumsum, mod, diff and find as follows:
temp = cumsum(a);
required = find([0 diff(mod(temp,10))] <0)
cumsum returns the cumulative sum which then is rescaled using mod. diff determines where the sum gets greater than or equal to 10 and finally find determines those indexes.
Edit: Above solution works if a doesn't have negative elements. If a can have negative elements then:
temp1=cumsum(a); %Commulative Sum
temp2=[0 diff(mod(temp1,10))];%Indexes where sum >=10 (indicated by negative values)
temp2(temp1<0)=0; %Removing false indexes which may come if `a` has -ve values
required = find(temp2 <0) %Required indexes
This should do what you are trying. It displays the index at which each time the sum equals 10. Check this with your testcases. rem stores the residual sum in each iteration which is carried forward in the next iteration. The rest of the code is similar to what you were doing.
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1];
c = 0;
rem = 0;
i = 1;
length(a);
while(i <= length(a))
c = rem;
while (c < 10 && i <= length(a))
c = c + a(i);
i = i + 1;
if(c >= 10)
rem = c - 10;
break
end
end
if(c >= 10)
disp(i-1)
end
use cumsum instead of your while loop:
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1];
a_ = a;
endidxlist = false(size(a));
startidxlist = false(size(a));
startidxlist(1) = true;
while any(a_) && (sum(a_) >= 10)
b = cumsum(a_);
idx = find(b >= 10,1);
endidxlist(idx) = true;
% move residual to the next sequence
a_(idx) = b(idx) - 10;
if a_(idx) > 0
startidxlist(idx) = idx;
elseif (idx+1) <= numel(a)
startidxlist(idx+1) = true;
end
a_(1:idx-1) = 0;
end
if (idx+1) <= numel(a)
startidxlist(idx+1) = false;
end
endidxlist gives you the end-indexes of each sequence and startidxlist the start-indexes

Find where condition is true n times consecutively

I have an array (say of 1s and 0s) and I want to find the index, i, for the first location where 1 appears n times in a row.
For example,
x = [0 0 1 0 1 1 1 0 0 0] ;
i = 5, for n = 3, as this is the first time '1' appears three times in a row.
Note: I want to find where 1 appears n times in a row so
i = find(x,n,'first');
is incorrect as this would give me the index of the first n 1s.
It is essentially a string search? eg findstr but with a vector.
You can do it with convolution as follows:
x = [0 0 1 0 1 1 1 0 0 0];
N = 3;
result = find(conv(x, ones(1,N), 'valid')==N, 1)
How it works
Convolve x with a vector of N ones and find the first time the result equals N. Convolution is computed with the 'valid' flag to avoid edge effects and thus obtain the correct value for the index.
Another answer that I have is to generate a buffer matrix where each row of this matrix is a neighbourhood of overlapping n elements of the array. Once you create this, index into your array and find the first row that has all 1s:
x = [0 0 1 0 1 1 1 0 0 0]; %// Example data
n = 3; %// How many times we look for duplication
%// Solution
ind = bsxfun(#plus, (1:numel(x)-n+1).', 0:n-1); %'
out = find(all(x(ind),2), 1);
The first line is a bit tricky. We use bsxfun to generate a matrix of size m x n where m is the total number of overlapping neighbourhoods while n is the size of the window you are searching for. This generates a matrix where the first row is enumerated from 1 to n, the second row is enumerated from 2 to n+1, up until the very end which is from numel(x)-n+1 to numel(x). Given n = 3, we have:
>> ind
ind =
1 2 3
2 3 4
3 4 5
4 5 6
5 6 7
6 7 8
7 8 9
8 9 10
These are indices which we will use to index into our array x, and for your example it generates the following buffer matrix when we directly index into x:
>> x = [0 0 1 0 1 1 1 0 0 0];
>> x(ind)
ans =
0 0 1
0 1 0
1 0 1
0 1 1
1 1 1
1 1 0
1 0 0
0 0 0
Each row is an overlapping neighbourhood of n elements. We finally end by searching for the first row that gives us all 1s. This is done by using all and searching over every row independently with the 2 as the second parameter. all produces true if every element in a row is non-zero, or 1 in our case. We then combine with find to determine the first non-zero location that satisfies this constraint... and so:
>> out = find(all(x(ind), 2), 1)
out =
5
This tells us that the fifth location of x is where the beginning of this duplication occurs n times.
Based on Rayryeng's approach you can loop this as well. This will definitely be slower for short array sizes, but for very large array sizes this doesn't calculate every possibility, but stops as soon as the first match is found and thus will be faster. You could even use an if statement based on the initial array length to choose whether to use the bsxfun or the for loop. Note also that for loops are rather fast since the latest MATLAB engine update.
x = [0 0 1 0 1 1 1 0 0 0]; %// Example data
n = 3; %// How many times we look for duplication
for idx = 1:numel(x)-n
if all(x(idx:idx+n-1))
break
end
end
Additionally, this can be used to find the a first occurrences:
x = [0 0 1 0 1 1 1 0 0 0 0 0 1 0 1 1 1 0 0 0 0 0 1 0 1 1 1 0 0 0]; %// Example data
n = 3; %// How many times we look for duplication
a = 2; %// number of desired matches
collect(1,a)=0; %// initialise output
kk = 1; %// initialise counter
for idx = 1:numel(x)-n
if all(x(idx:idx+n-1))
collect(kk) = idx;
if kk == a
break
end
kk = kk+1;
end
end
Which does the same but shuts down after a matches have been found. Again, this approach is only useful if your array is large.
Seeing you commented whether you can find the last occurrence: yes. Same trick as before, just run the loop backwards:
for idx = numel(x)-n:-1:1
if all(x(idx:idx+n-1))
break
end
end
One possibility with looping:
i = 0;
n = 3;
for idx = n : length(x)
idx_true = 1;
for sub_idx = (idx - n + 1) : idx
idx_true = idx_true & (x(sub_idx));
end
if(idx_true)
i = idx - n + 1;
break
end
end
if (i == 0)
disp('No index found.')
else
disp(i)
end

How to find the longest interval of 1's in a list [matlab]

I need to find the longest interval of 1's in a matrix, and the position of the first "1" in that interval.
For example if i have a matrix: [1 0 0 1 1 1 0 0 0 0 1 1 1 1 1 1 1 ]
I need to have both the length of 7 and that the first 1's position is 11.
Any suggestions on how to proceed would be appreciated.
Using this anwser as a basis, you can do as follows:
a = [1 0 0 1 1 1 0 0 0 0 1 1 1 1 1 1 1 ]
dsig = diff([0 a 0]);
startIndex = find(dsig > 0);
endIndex = find(dsig < 0) - 1;
duration = endIndex-startIndex+1;
duration
startIdx = startIndex(duration == max(duration))
endIdx = endIndex(duration == max(duration))
This outputs:
duration =
1 3 7
startIdx =
11
endIdx =
17
Please note, this probably needs double checking if it works for other cases than your example. Nevertheless, I think this is the way in the right directions. If not, in the linked anwser you can find more info and possibilities.
If there are multiple intervals of one of the same length, it will only give the position of the first interval.
A=round(rand(1,20)) %// test vector
[~,p2]=find(diff([0 A])==1); %// finds where a string of 1's starts
[~,p3]=find(diff([A 0])==-1); %// finds where a string of 1's ends
le=p3-p2+1; %// length of each interval of 1's
ML=max(le); %// length of longest interval
ML %// display ML
p2(le==ML) %// find where strings of maximum length begin (per Marcin's answer)
I have thought of a brute force approach;
clc; clear all; close all;
A= [1 0 0 1 1 1 0 0 0 0 1 1 1 1 1 1 1 ];
index = 1;
globalCount = 0;
count = 0;
flag = 0; %// A flag to keep if the previous encounter was 0 or 1
for i = 1 : length(A)
if A(i) == 1
count = count + 1;
if flag == 0
index = i
flag = 1;
end
end
if A(i) == 0 || i == length(A)
if count > globalCount
globalCount = count;
end
flag = 0;
count = 0;
end
end

Effective picking of surrounded element

If I have sequence 1 0 0 0 1 0 1 0 1 1 1
how to effectively locate zero which has from both sides 1.
In this sequence it means zero on position 6 and 8. The ones in bold.
1 0 0 0 1 0 1 0 1 1 1
I can imagine algorithm that would loop through the array and look one in back and one in front I guess that means O(n) so probably there is not any more smooth one.
If you can find another way, I am interested.
Use strfind:
pos = strfind(X(:)', [1 0 1]) + 1
Note that this will work only when X is a vector.
Example
X = [1 0 0 0 1 0 1 0 1 1 1 ];
pos = strfind(X(:)', [1 0 1]) + 1
The result:
pos =
6 8
The strfind method that #EitanT suggested is quite nice. Another way to do this is to use find and element-wise bit operations:
% let A be a logical ROW array
B = ~A & [A(2:end),false] & [false,A(1:end-1)];
elements = find(B);
This assumes, based on your example, that you want to exclude boundary elements. The concatenations [A(2:end),false] and [false,A(1:end-1)] are required to keep the array length the same. If memory is a concern, these can be eliminated:
% NB: this will work for both ROW and COLUMN vectors
B = ~A(2:end-1) & A(3:end) & A(1:end-2);
elements = 1 + find(B); % need the 1+ because we cut off the first element above
...and to elaborate on #Eitan T 's answer, you can use strfind for an array if you loop by row
% let x = some matrix of 1's and 0's (any size)
[m n] = size(x);
for r = 1:m;
pos(r,:) = strfind(x(r,:)',[1 0 1]) + 1;
end
pos would be a m x ? matrix with m rows and any returned positions. If there were no zeros in the proper positions though, you might get a NaN ... or an error. Didn't get a chance to test.

Resources