Counting islands in a matrix (preferably in IDL) - matlab

How can I count the number of values that are contiguous in an matrix? For example, if
A= [ 1 0 1 0 0 0 0 \
1 1 1 0 0 0 1\
1 1 0 1 1 1 1]
is a 7 by 3 matrix then the result should indicate that there are 12 contiguous values that are "1" (highlighted in bold), that there are 8 contiguous 0 values (highlighted in italics) and one lone 0 value. Code in IDL is preferred but MATLAB would also be helpful.

This is what you can do in MATLAB using the bwconncomp function. This is a function in the Image Processing Toolbox. I don't know a whole lot about IDL, it might have a similar function.
bwconncomp returns a struct with some information, one of the fields is PixelIdxList, which is a cell array with one element per connected component. Each of these elements is a vector with the indices to one of the array elements in the connected component. For the case of the 1 elements in your example, this cell array will have one vector with 12 values. For the case of the 0 elements, it will have two vectors, one with 1 value and one with 8:
>> A = [ 1 0 1 0 0 0 0 ; 1 1 1 0 0 0 1 ; 1 1 0 1 1 1 1 ];
>> CC = bwconncomp(A==1, 8);
>> cellfun(#numel, CC.PixelIdxList)
ans =
12
>> CC = bwconncomp(A==0, 8);
>> cellfun(#numel, CC.PixelIdxList)
ans =
1 8
The bwconncomp takes 4 or 8 as the second argument. This specifies what are considered connected elements (contiguous values, neighbors). 4 means only the 4 elements N,S,E,W are connected; 8 means also diagonal connections exist (8 neighbors).

Related

How do I randomly shuffle a subset of the elements of an existing matrix in Matlab? [duplicate]

This question already has answers here:
shuffle matrix element in matlab
(2 answers)
Closed 5 years ago.
I have a matrix of values ranging from 0 to 3 in Matlab. I want to randomly shuffle the elements of the matrix, but only within the cells that have values in the range 1 -3 (so only within a subset of the whole matrix). Is there a way to do that? Thanks.
You can do this by getting an index of all the values of interest (such as a logical index), randomly permuting their order using randperm, then assigning them back into the matrix using the same index:
% Sample matrix with values from 0 to 3:
M = randi([0 3], 5)
M =
3 1 0 3 0
0 3 3 2 0
1 0 2 1 0
1 1 2 2 0
3 0 0 1 0
index = (M > 0); % Index of values from 1 to 3
values = M(index); % Vector of indexed values
M(index) = values(randperm(numel(values))) % Matrix with shuffled values
M =
2 3 0 2 0
0 3 1 1 0
2 0 3 3 0
1 1 2 1 0
3 0 0 1 0
Note that the zeroes are all still in the same place in the shuffled matrix. Note also that you still have the same number of ones, twos, and threes, since they are just shuffled to different spots.

Inserting Ones at pseudo-fixed Intervals

I have the following row vector:
A = zeros(1,200);
I'd like to insert a '1' every 2-3 columns until I have exactly 80 ones in total that are approximately evenly spaced - as opposed to having fixed spacing - with the first 2 columns being zeros.
e.g.
0 0 1 0 1 0 0 1 0 0 1 0 1 ...
It would be nice if the combination could be permuted as well so that more than one row vector satisfies the criteria.
Thanks!
You could use repelem (run-length encoding) to do this. The way that repelem works is that we have two inputs: the values and the number of times each value is repeated.
For example
values = [0 1];
repeats = [1 2];
repelem(values, repeats)
% 0 1 1
We can also have duplicate values in the values array
values = [0 1 0 1];
repeats = [2 1 1 1];
repelem(values, repeats)
% 0 0 1 0 1
We can utilize this to solve your problem.
First we construct the values matrices to simply alternate between 0 and 1 meaning that we want the expanded matrix to contain some 0's followed by a 1, some 0's followed by a 1, etc.
values = ~mod(1:80, 2);
% 0 1 0 1 0 1 0 1 ....
In your case, the number of times each 0 is going to be repeated is going to be either 1 or 2. Each 1 however, is only going to be repeated once. To make this happen we use rand to pick randomly between 1 and 2 repeats. Then we assign all the repeats for 1 values to be a single repeat.
repeats = randi([1 2], size(values));
% Make sure that the 1's are always only repeated once
repeats(values) = 1;
We use 80 entries in the repeats and values arrays just to make sure that we end up with at least 80 values in the final (expanded) array.
Now apply the repelem and keep only the first 80 values
result = repelem(values, repeats);
result = result(1:80);
% 0 1 0 0 1 0 0 1 0 1 0 0 1
You can do this with a few standard functions and array indexing. Something like this ...
A = zeros(1,200);
ixs = round(cumsum(2 + rand(200,1)));
A(ixs(ixs<200))=1;
%Sample result here, first 20 entries: 0 0 1 0 1 0 0 1 0 0 1 0 1 0 0 1 0 1 0 1
What we're doing here is:
Setting up the A array (this is clear)
Defining an oversized array of index values to set to one (more on that below)
Then using that index to set values to one, trimming the oversize.
In terms of creating the index ixs, in innermost portion (2 + rand(200,1)) creates a 200x1 array of values between 2 and 3. Using cumsum generates the cumulative sum of this array, and then round rounds the values to an integer, which can be used for indexing. For example, the first 10 values is ixs look like this, for a particular run:
>> ixs(1:10)'
ans =
3 5 8 11 13 16 18 20 22 24
Since the number of 1 values will vary each time, I set this up to be oversized. That is, the last few values are [487 489 491 497 500], larger than the actual size required. This is why the values need to be trimmed with applying the index.
A = zeros(1,200);
idx = cumsum(1 + randi(2,80,1)); % This is the main trick
A(idx) = 1;
cumsum(1 + randi(2,80,1)) gets you the indexes for exactly 80 elements in A which need to be switched to 1 spaced by 2 or 3 (randomly)

Transform a matrix to a stacked vector where all zeroes after the last non-zero value per row are removed

I have a matrix with some zero values I want to erase.
a=[ 1 2 3 0 0; 1 0 1 3 2; 0 1 2 5 0]
>>a =
1 2 3 0 0
1 0 1 3 2
0 1 2 5 0
However, I want to erase only the ones after the last non-zero value of each line.
This means that I want to retain 1 2 3 from the first line, 1 0 1 3 2 from the second and 0 1 2 5 from the third.
I want to then store the remaining values in a vector. In the case of the example this would result in the vector
b=[1 2 3 1 0 1 3 2 0 1 2 5]
The only way I figured out involves a for loop that I would like to avoid:
b=[];
for ii=1:size(a,1)
l=max(find(a(ii,:)));
b=[b a(ii,1:l)];
end
Is there a way to vectorize this code?
There are many possible ways to do this, here is my approach:
arotate = a' %//rotate the matrix a by 90 degrees
b=flipud(arotate) %//flips the matrix up and down
c= flipud(cumsum(b,1)) %//cumulative sum the matrix rows -and then flip it back.
arotate(c==0)=[]
arotate =
1 2 3 1 0 1 3 2 0 1 2 5
=========================EDIT=====================
just realized cumsum can have direction parameter so this should do:
arotate = a'
b = cumsum(arotate,1,'reverse')
arotate(b==0)=[]
This direction parameter was not available on my 2010b version, but should be there for you if you are using 2013a or above.
Here's an approach using bsxfun's masking capability -
M = size(a,2); %// Save size parameter
at = a.'; %// Transpose input array, to be used for masked extraction
%// Index IDs of last non-zero for each row when looking from right side
[~,idx] = max(fliplr(a~=0),[],2);
%// Create a mask of elements that are to be picked up in a
%// transposed version of the input array using BSXFUN's broadcasting
out = at(bsxfun(#le,(1:M)',M+1-idx'))
Sample run (to showcase mask usage) -
>> a
a =
1 2 3 0 0
1 0 1 3 2
0 1 2 5 0
>> M = size(a,2);
>> at = a.';
>> [~,idx] = max(fliplr(a~=0),[],2);
>> bsxfun(#le,(1:M)',M+1-idx') %// mask to be used on transposed version
ans =
1 1 1
1 1 1
1 1 1
0 1 1
0 1 0
>> at(bsxfun(#le,(1:M)',M+1-idx')).'
ans =
1 2 3 1 0 1 3 2 0 1 2 5

matlab consecutive of ones in a cell

A column of a cell array contains some 0s and 1s.
A=[0 0 1 1 1 1 0 0 0 1 0 0];
I want to have a result contains just the indices of all ones in a way that all consecutive ones come together in a cell:
result =
3 4 5 6
10
One approach with diff, find & mat2cell -
%// Find lengths of islands of nonzeros
dfA = diff([0 A 0])
lens = find(dfA==-1) - find(dfA==1)
%// Get corresponding indices for each element
vals = A.*(1:numel(A))
%// Pack them up into cells based on the runlengths
out = mat2cell( vals(A==1) , 1 , lens )
Sample run -
>> A
A =
1 0 0 0 1 1 1 1 0 0 0 1 1 0
>> celldisp(out)
out{1} =
1
out{2} =
5 6 7 8
out{3} =
12 13
In the question you refers to "cellarray" while in the code privided the varaible "A" is an array.
Moreover, the "result" provided seems not in line with the content of the array "A": the index of the first "1" is "2" not "3".
If the variable to work with is actually an array, the indexes of the "1" can be easly found using the built-in function "find".

Count the number of the first zero elements

I would line to find the number of the first consecutive zero elements. For example in [0 0 1 -5 3 0] we have two zero consecutive elements that appear first in the vector.
could you please suggest a way without using for loops?
V=[0 0 1 -5 3 0] ;
k=find(V);
Number_of_first_zeros=k(1)-1;
Or,
Number_of_first_zeros=find(V,1,'first')-1;
To solve #The minion comment (if that was the purpose):
Number_of_first_zeros=find(V(find(~V,1,'first'):end),1,'first')-find(~V,1,'first');
Use a logical array to find the zeros and then look at where the zeros and ones are alternating.
V=[1 2 0 0 0 3 5123];
diff(V==0)
ans =
0 1 0 0 -1 0
Create sample data
V=[1 2 0 0 0 3 5123];
Find the zeros. The result will be a logical array where 1 represents the location of the zeros
D=V==0
D =
0 0 1 1 1 0 0
Take the difference of that array. 1 would then represent the start and -1 would represent the end.
T= diff(D)
ans =
0 1 0 0 -1 0
find(T==1) would give you the start and find(T==-1) would give you the end. The first index+1 of T==1 would be the start of the first set of zeros and the first index of T==-1 would be the end of the first set of zeros.
You could find position the first nonzero element using find.
I=find(A, 1);
The number of leading zeros is then I-1.
My solution is quite complex yet it doesn't use the loops and it does the trick. I am pretty sure, that there is a more direct approach.
Just in case no one else posts a working solution here my idea.
x=[1 2 4 0 20 0 10 1 23 45];
x1=find(x==0);
if numel(x1)>1
x2=[x1(2:end), 0];
x3=x2-x1;
y=find(x3~=1);
y(1)
elseif numel(x1)==1
display(1)
else
display('No zero found')
end
x is the dataset. x1 contains the index of all zero elements. x2 contains all those indices except the first one (because matrix dimensions must agree, one zero is added. x3 is the difference between the index and the previous index of zeros in your dataset. Now I find all those differences which are not 1 (do not correspond to sequences of zeros) and the first index (of this data is the required result. The if case is needed in case you have only one or no zero at all.
I'm assuming your question is the following: for the following vector [0 0 1 -5 3 0], I would like to find the index of the first element of a pair of 0 values. Is this correct? Therefore, the desired output for your vector would be '1'?
To extend the other answers to find any such pairs, not just 0 0 (eg. 0 1, 0 2, 3 4 etc), then this might help.
% define the pattern
ptrn = [ 0 0 ];
difference = ptrn(2) - ptrn(1)
V = [0 0 1 -5 3 0 0 2 3 4 0 0 1 0 0 0]
x = diff(V) == difference
indices = find(x)
indices =
1 6 11 14 15