Insert certain value after occurence of a set of n equal values - matlab

Example:
input = [1 255 0 0 0 9 9 9 1 6 6 6 6 6 6 1]; % array of numbers (uint8)
output = [1 255 0 0 0 255 9 9 9 255 1 6 6 6 255 6 6 6 255 1];
% output must have 255 inserted at positions 6, 10, 15, 19
% because 0, 9, 6, 6 have occurred three times respectively
outputIndex = [6 10 15 19];
% outputIndex must indicate the positions where 255 was inserted

This could be one vectorized approach to get things done efficiently -
%// Input
A = [1 255 0 0 0 9 9 9 1 6 6 6 6 6 6 1]
%// Input paramter (how many times a value must be repeated for detection)
search_count = 3;
%// Find difference between consecutive elemnts and set all non zero
%// differences as ones, otherwise as zeros in a binary array
diffA = diff(A)~=0
%// Find start and end indices of "islands" of same value
starts = strfind([1 diffA],[1 zeros(1,search_count-1)])
ends = strfind([diffA 1],[zeros(1,search_count-1) 1])+search_count
%// For each island of same valued elements, find out where first group ends
firstgrp = starts + search_count
%// Find how many times a group of that same value of search_count times repeats
%// within each "island" of same valued elements. Also get the max repeats.
pattern_repeats = floor((ends - starts)./search_count)
max_repeat = max(pattern_repeats)
%// All possible repeat indices within all islands
all_repeats = bsxfun(#plus,firstgrp,[0:max_repeat-1]'*(search_count)) %//'
%// Use a binary mask to select only those repeats allowed with pattern_repeat
out_idx = all_repeats(bsxfun(#lt,[0:max_repeat-1]',pattern_repeats)) %//'
out_idx = out_idx + [0:numel(out_idx)-1]' %//'
%// Create output arary, insert 255 at out_idx locations and put values
%// from input array into rest of the locations
out = zeros(1,numel(A)+numel(out_idx));
out(out_idx) = 255
out(out==0) = A
Code run -
>> A
A =
Columns 1 through 13
1 255 0 0 0 9 9 9 1 6 6 6 6
Columns 14 through 16
6 6 1
>> out_idx
out_idx =
6
10
15
19
>> out
out =
Columns 1 through 13
1 255 0 0 0 255 9 9 9 255 1 6 6
Columns 14 through 20
6 255 6 6 6 255 1

I don't understand the downvotes, it's actually an interesting question.
Here the long answer:
n = 3;
subst = 255;
input = [1 255 0 0 0 9 9 9 1 6 6 6 6 6 6 61];
%// mask
X = NaN(1,numel(input));
%// something complicated (see below)
X(filter(ones(1,n-1),1,~([0. diff(input)])) == n-1) = 1;
%// loop to split multiple occurences of n-tuples
for ii = 1:numel(input)
if X(ii) == 1 && ii < numel(X)-n+1
X(ii+1:ii+n-1) = NaN(1,n-1);
end
end
%// output vector
D = [input; X.*subst];
E = D(:);
output = E(isfinite(E))
%// indices of inserted 255
D = [input.*0; X.*subst];
E = D(:);
outputIndex = find(E(isfinite(E)))
Explanation of the complicated part:
%// finite differences of input
A = [0 diff(input)];
%// conversion to logical
B = ~A;
%// mask consecutive values
mask = filter(ones(1,n-1),1,B) == n-1;
%// set masked values to 1
X(mask) = 1;
If you have the image processing toolbox you can save the loop with this fancy oneliner for getting the mask:
mask = accumarray(bwlabel(filter(ones(1,n-1),1,~([0. diff(input)])) == n-1).'+1,1:numel(input),[],#(x) {getfield(sort(x),{find(mod(cumsum(1:numel(x)),n) == 1)})});
X = NaN(1,numel(input));
X(vertcat(mask{2:end})) = subst;
%// output vector
D = [input; X];
E = D(:);
output = E(isfinite(E))
%// indices of inserted 255
D = [input.*0; X];
E = D(:);
outputIndex = find(E(isfinite(E)))

Related

Combine index-based and logical addressing in Matlab

Consider a matrix X. I have to update a submatrix of X, X(row1:row2, col1:col2), with a matrix Z (of size row2-row1+1, col2-col1+1) but only on those positions where a logical matrix L (of size row2-row1+1, col2-col1+1) is true.
E.g. if
X=[ 1 2 3 4 5 6
11 12 13 14 15 16
21 22 23 24 25 26
31 32 33 34 34 36]
Z=[31 41
32 42]
L=[ 1 0
0 1]
row1 = 2; row2 = 3; col1 = 3; col2 = 4
then after the update I should get:
X=[ 1 2 3 4 5 6
11 12 31 14 15 16
21 22 23 42 25 26
31 32 33 34 34 36]
Currently I do the following:
Y = X(row1:row2, col1:col2);
Y(L) = Z(L);
X(row1:row2, col1:col2) = Y;
This code is in a tight loop and according to Matlab's (v2019a) profiler is the main bottleneck of my program. In the real code X is a 2000x1500x3 cube; row1, row2, col1, col2, Z and L change in the loop.
The question is whether it can be rewritten into a single / faster assignment.
Thanks.
Honestly, without seeing your actual code, I get the sense that your solution may be as fast as you can get. The reason I say that is because I tested a few different solutions by creating some random sample data closer to your actual problem. I assumed X is an image of type uint8 with size 2000-by-1500-by-3, Z is size N-by-N (i.e. we will only be modifying the first page of X), L is an N-by-N logical array, and the row and column indices are randomly chosen:
X = randi([0 255], 2000, 1500, 3, 'uint8');
N = 20; % Submatrix size
Z = randi([0 255], N, N, 'uint8');
L = (rand(N, N) > 0.5);
row1 = randi([1 2000-N]);
row2 = row1+N-1
col1 = randi([1 1500-N]);
col2 = col1+N-1;
I then tested 3 different solutions: your original solution, a solution using find and sub2ind to create a linear index for X, and a solution that creates a logical index for X:
% Original solution:
Y = X(row1:row2, col1:col2, 1);
Y(L) = Z(L);
X(row1:row2, col1:col2, 1) = Y;
% Linear index solution:
[rIndex, cIndex] = find(L);
X(sub2ind(size(X), rIndex+row1-1, cIndex+col1-1)) = Z(L);
% Logical index solution
[R, C, ~] = size(X);
fullL = false(R, C);
fullL(row1:row2, col1:col2) = L;
X(fullL) = Z(L);
I tested these repeatedly with randomly-generated sample data using timeit and found that your original solution is consistently the fastest. The linear index solution is very close, but slightly slower. The logical index solution takes more than twice as long.
Let's define some example data:
X = randi(9,5,6);
Y = 10+X;
row1 = 2;
row2 = 4;
col1 = 3;
col2 = 4;
L = logical([0 1; 0 0; 1 1]);
Then:
ind_subm = bsxfun(#plus, (row1:row2).',size(X,1)*((col1:col2)-1));
% linear index for submatrix
ind_subm_masked = ind_subm(L);
% linear index for masked submatrix
X(ind_subm_masked) = Y(ind_subm_masked);
Example results:
X before:
X =
6 2 1 7 9 6
3 3 3 5 5 7
6 3 8 6 5 4
7 4 1 3 3 4
2 5 9 5 5 9
L:
L =
3×2 logical array
0 1
0 0
1 1
X after:
X =
6 2 1 7 9 6
3 3 3 15 5 7
6 3 8 6 5 4
7 4 11 13 3 4
2 5 9 5 5 9

Filtering 3D matrix above certain threshold?

I have two 3D matrices of the same size. Say, A contain values ranging from 0 to 1 and B contains certain discrete values.
I want to extract values from B matrix which are above certain threshold values in A?
Can anyone help me out?
Like this? Have a look at logical indexing.
% Input.
B = reshape(1:18, 2, 3, 3)
A = rand(size(B))
% Threshold.
thr = 0.75
% Output.
output = B(A > thr)
B =
ans(:,:,1) =
1 3 5
2 4 6
ans(:,:,2) =
7 9 11
8 10 12
ans(:,:,3) =
13 15 17
14 16 18
A =
ans(:,:,1) =
0.80533 0.24370 0.89180
0.90358 0.22422 0.69243
ans(:,:,2) =
0.119366 0.168337 0.771999
0.206004 0.065481 0.979772
ans(:,:,3) =
0.0057303 0.1469925 0.0556628
0.0454038 0.4122576 0.9847027
thr = 0.75000
output =
1
2
5
11
12
18

Get submatrix made from random rows and columns of large matix

I would like to extract (square) sub-matrices from a matrix, randomly with a size between some min and max values.
I would also like to retain the row and column indices which were selected.
Are there any built-in functions for this purpose? I would appreciate any general algorithm to achieve the desired results.
Just for an example without considering the square matrix constraint:
Input:
| 1 2 3 4 5 6 7 8 <- column indices
--+----------------
1 | 4 3 1 4 0 1 0 1
2 | 2 0 1 5 6 3 2 0
3 | 5 5 0 6 7 8 9 0
4 | 2 3 5 6 7 9 0 1
^
Row indices
Output sample:
| 1 4 5 7 8 <- column indices
--+----------
1 | 4 4 0 0 1
2 | 2 5 6 2 0
4 | 2 6 7 0 1
^
Row indices
Edit: I would like to have a function like this
matrixsample(numberOfSamples, minSize, maxSize, satisfyingFunction)
the samples should vary their size between the minSize and maxSize.
If numberOfSamples = 10, minSize = 2 and maxSize = 6 then output should be randomly selected rows and columns like:
sampleMatrix1 2x2 sampleMatrix2 3x3 sampleMatrix3 5x5 ... sampleMatrix7 6x6 ... sampleMatrx10 4x4
satisfyingFunction could test any attribute of the matrix; like being non-singular, rank > x, etc.
In MATLAB, you will find the randperm function useful for selecting random rows/columns.
To get a randomly sized sub-matrix, use randi([minVal, maxVal]) to get a random integer between minVal and maxVal.
Getting a submatrix from random (ordered) combination of rows and columns:
M = randi([0,9],4,8); % 4x8 matrix of random 1 digit integers, your matrix here!
nRows = randi([2, 4]); % The number of rows to extract, random between 2 and 4
nCols = randi([5, 7]); % The number of columns to extract, random between 5 and 7
idxRows = sort(randperm(size(M,1), nRows)); % Random indices of rows
idxCols = sort(randperm(size(M,2), nCols)); % Random indices of columns
output = M(idxRows, idxCols); % Select the sub-matrix
If you want to make the sub-matrix square then simply use the same value for nRows and nCols.
Showing this method works with your example input/output values:
M = [4 3 1 4 0 1 0 1; 2 0 1 5 6 3 2 0; 5 5 0 6 7 8 9 0; 2 3 5 6 7 9 0 1];
idxRows = [1 2 4]; idxCols = [1 4 5 7 8];
output = M(idxRows, idxCols)
% >> 4 4 0 0 1
% 2 5 6 2 0
% 2 6 7 0 1
Edit in response to extended question scope:
You can package the above up into a short function, which returns the row and column indices, as well as the submatrix.
function output = getsubmatrix(M, nRows, nCols)
% Get a submatrix of random rows/columns
idxRows = sort(randperm(size(M,1), nRows));
idxCols = sort(randperm(size(M,2), nCols));
submatrix = M(idxRows, idxCols);
output = {submatrix, idxRows, idxCols};
end
Then you can use this in some sampling function as you described:
function samples = matrixsample(matrix, numberOfSamples, minSize, maxSize, satisfyingFunction)
% output SAMPLES which contains all sample matrices, with row & column indices w.r.t MATRIX
samples = cell(numberOfSamples,1);
% maximum iterations trying to satisfy SATISFYINGCONDITION
maxiters = 100;
for ii = 1:numberOfSamples
iters = 0; submatrixfound = false; % reset loop exiting conditions
nRows = randi([minSize, maxSize]); % get random submatrix size
nCols = nRows; % Square matrix
while iters < maxiters && submatrixfound == false
% Get random submatrix, and the indices
submatrix = getsubmatrix(matrix, nRows,nCols);
% satisfyingFunction MUST RETURN BOOLEAN
if satisfyingFunction(submatrix{1})
samples{ii} = submatrix; % If satisfied, assign to output
submatrixfound = true; % ... and move on!
end
iters = iters + 1;
end
end
end
Test example:
s = matrixsample(magic(10), 5, 2, 8, #(M)max(M(:)) < 90)
If the table is given, you will do it by randsample:
minRowVal = 3;
maxRowVal = 4;
minColVal = 2;
maxColVal = 4;
kRow = randi([minRowVal maxRowVal]) ;
kCol = randi([minColVal maxColVal]);
table(sort(randsample(size(table,1),kRow)),sort(randsample(size(table,2),kCol))
choose some sample by the given size of kRow and kCol, then select the information from the table.

Matlab/Octave: how to write n-dimensional zero padding algorithm without eval

I would like to write a "syntactical sugar" Octave or Matlab zero-padding function, to which the user sends an n-dimensional object and a vector of <= n entries. The vector contains new, equal or larger dimensions for the object, and the object is zero-padded to match these dimensions. Any dimensions not specified are left alone. One expected use is, given for example a 5d block X of 3d medical image volumes, I can call
y = simplepad(X, [128 128 128]);
and thus pad the first three dimensions to a power of two for wavelet analysis (in fact I use a separate function nextpwr2 to find these dimensions) while leaving the others.
I have racked my brains on how to write this method avoiding the dreaded eval, but cannot thus far find a way. Can anyone suggest a solution? Here is more or less what I have:
function y = simplepad(x, pad)
szx = size(x);
n_pad = numel(pad);
szy = [pad szx(n_pad+1:end)];
y = zeros(szy);
indices_string = '(';
for n = 1:numel(szx)
indices_string = [indices_string, '1:', num2str(szx(n))];
if n < numel(szx)
indices_string = [indices_string, ','];
else
indices_string = [indices_string, ')'];
end
end
command = ['y',indices_string,'=x;'];
eval(command);
end
Here's a solution that should handle all the little corner cases:
function A = simplepad(A, pad)
% Add singleton dimensions (i.e. ones) to the ends of the old size of A
% or pad as needed so they can be compared directly to one another:
oldSize = size(A);
dimChange = numel(pad)-numel(oldSize);
oldSize = [oldSize ones(1, dimChange)];
pad = [pad ones(1, -dimChange)];
% If all of the sizes in pad are less than or equal to the sizes in
% oldSize, there is no padding done:
if all(pad <= oldSize)
return
end
% Use implicit zero expansion to pad:
pad = num2cell(pad);
A(pad{:}) = 0;
end
And a few test cases:
>> M = magic(3)
M =
8 1 6
3 5 7
4 9 2
>> simplepad(M, [1 1]) % No change, since the all values are smaller
ans =
8 1 6
3 5 7
4 9 2
>> simplepad(M, [1 4]) % Ignore the 1, pad the rows
ans =
8 1 6 0
3 5 7 0
4 9 2 0
>> simplepad(M, [4 4]) % Pad rows and columns
ans =
8 1 6 0
3 5 7 0
4 9 2 0
0 0 0 0
>> simplepad(M, [4 4 2]) % Pad rows and columns and add a third dimension
ans(:,:,1) =
8 1 6 0
3 5 7 0
4 9 2 0
0 0 0 0
ans(:,:,2) =
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
As I understand, you want just pass the some dynamic arguments to function.
You can do this by converting these arguments to cell and call your function with passing cell content. So, your function will look like:
function y = simplepad(x, pad)
szx = size(x);
n_pad = numel(pad);
szy = [pad szx(n_pad+1:end)];
y = x;
szyc = num2cell(szy);
y(szyc{:}) = 0; % warning: assume x array only grows
end

Grouping unique values in a vector and putting them in a matrix

I have a vector that contains repeated numbers like so:
[1 1 1 1 5 5 5 5 93 93 93 6 6 6 6 6 6] and so on. What I want to do is to group the similar values (1's, 5's, etc.). I would like to have each of the unique values in a row of a big matrix, like:
[ 1 1 1 1 0 0
5 5 5 5 0 0
93 93 93 0 0 0
6 6 6 6 6 6]
I don't know the maximum number of occurrence of a unique value, so it is ok to create an initial zero matrix with a large number of columns (that I am sure is bigger than the maximum number of occurrence of a unique value).
Any help is highly appreciated.
How about this?
A = [1 1 1 1 5 5 5 5 93 93 93 6 6 6 6 6 6];
[a,b] = hist(A,unique(A))
f = #(x) [ones(1,a(x)) zeros(1,max(a)-a(x))]
X = cell2mat( arrayfun(#(x) {f(x)*b(x)}, 1:numel(b) )' )
returns:
X =
1 1 1 1 0 0
5 5 5 5 0 0
6 6 6 6 6 6
93 93 93 0 0 0
I know the order is different, is that important? Otherwise:
n = hist(A,1:max(A)) % counts how often every number apperas
[a b] = unique(A,'stable') % gets all unique numbers
n = n(a) % correlates count and numbers
f = #(x) [ones(1,n(x)) zeros(1,max(n)-n(x))] % creates the logical index
% vector for every single row
X = cell2mat( arrayfun(#(x) {f(x)*b(x)}, 1:numel(b) )' ) %fills the rows
or inspired by Luis Mendo's Answer a little shorter:
n = hist(A,1:max(A));
a = unique(A,'stable')
n = n(a)
Y = repmat(a',1,max(n)).*bsxfun(#le, cumsum(ones(max(n),numel(n))), n)'
returns:
X =
1 1 1 1 0 0
5 5 5 5 0 0
93 93 93 0 0 0
6 6 6 6 6 6
For the bored people out there, there is a one-line solution:
X = getfield(cell2mat(arrayfun(#(x,y) padarray( padarray(x,[0 y],'replicate','pre'),[0 max(hist(A,1:max(A)))-y],'post'),1:max(A),hist(A,1:max(A)),'uni',0)'),{unique(A,'stable'),2:1+max(hist(A,1:max(A)))})
Or an almost lovely two-liner:
n = hist(A,1:max(A))
X = getfield(cell2mat(arrayfun(#(x,y) padarray( padarray(x,[0 y],'replicate',...
'pre'),[0 max(n)-y],'post'),1:max(A),n,'uni',0)'),...
{unique(A,'stable'),2:1+max(n)})
just for fun ;)
Vectorized solution (no loops):
x = [1 1 1 1 5 5 5 5 93 93 93 6 6 6 6 6 6]; %// data
ind = [find(diff(x)) numel(x)]; %// end of each run of equal values
values = x(ind); %// unique values (maintaining order)
count = diff([0 ind]); %// count of each value
result = bsxfun(#le, meshgrid(1:max(count),1:numel(values)), count.'); %'// mask
result = bsxfun(#times, result, values.'); %'// fill with the values
EDIT:
Alternative procedure that avoids the second bsxfun:
x = [1 1 1 1 5 5 5 5 93 93 93 6 6 6 6 6 6]; %// data
ind = [find(diff(x)) numel(x)];
values = x(ind); %// unique values (maintaining order)
count = diff([0 ind]); %// count of each value
mask = bsxfun(#le, ndgrid(1:max(count),1:numel(values)), count);
result = zeros(size(mask)); %// pre-allocate and pre-shape (transposed) result
result(mask) = x; %// fill in values
result = result.';
This could be one approach -
%%// Input
array1 = [1 1 1 1 5 5 5 5 93 93 93 6 6 6 6 6 6];
%// Main Processing
id = unique(array1,'stable'); %//Find the unique numbers/IDs
mat1 = zeros(numel(id),nnz(array1==mode(array1))); %%// Create a matrix to hold the final result
for k=1:numel(id)
extent_each_id = nnz(array1==id(k)); %%// Count of no. of occurances for each ID
mat1(k,1:extent_each_id)=id(k); %%// Starting from the left to the extent for each ID store that ID
end
Gives -
mat1 =
1 1 1 1 0 0
5 5 5 5 0 0
93 93 93 0 0 0
6 6 6 6 6 6