How to vectorize conditional triple nested for loop - MATLAB - matlab

I have two 3D arrays:
shape is a 240 x 121 x 10958 array
area is a 240 x 1 x 10958 array
The values of the arrays are of the type double. Both have NaN as fill values where there is no relevant data.
For each [240 x 121] page of the shape array, there are several elements filled by the same number. For example, there will be a block of 1s, a block of 2s, etc. For each corresponding page of the area array there is a single column of numeric values 240 rows long. What I need to do is progressively go through each page of the shape array (moving along the 3rd, 10958-long axis) and replace each numbered element in that page with the number that fills the row of the matching number in the area array.
For example, if I'm looking at shape(:,:,500), I want to replace all the 8s on that page with area(8,1,500). I need to do this for numbers 1 through 20, and I need to do it for all 10958 pages of the array.
If I extract a single page and only replace one number I can get it to work:
shapetest = shape(:,:,500);
shapetest(shapetest==8)=area(8,1,500);
This does exactly what I need for one page and for one number. Going through numbers 1-20 with a for loop doesn't seem like an issue, but I can't find a vectorized way to do this for all the pages of the original 3D array. In fact, I couldn't even get it work for a single page without extracting that page as its own matrix like I did above. I tried things like this to no avail:
shape(shape(:,:,500)==8)=area(8,1,500);
If I can't do it for one page, I'm even more lost as to how to do it for all at once. But I'm inexperienced in MATLAB, and I think I am just ignorant of the proper syntax.
Instead, I ended up using a cell array and the following very inefficient nested for loops:
MyCell=num2cell(shape,[2 1]);
shapetest3=reshape(MyCell,1,10958);
for w=1:numel(shapetest3)
test_result{1,w}=zeros(121,240)*NaN;
end
for k=1:10958
for i=1:29040 % 121 x 240
for n=1:20
if shapetest3{1,k}(i)==n
test_result{1,k}(i)=area(n,1,k);
end
end
end
end
This gets the job done, and I can easily turn it back to an array, but it is very slow, and I am confident there is a much better vectorized way. I'd appreciate any help or tips. Thanks in advance.

To vectorize the mapping operation, we can use shape as an index into area. But because the mapping is different for each plane, we need to loop over the planes to accomplish this. In short, it'll look like this:
test_result = zeros(size(shape)); % pre-allocate output
for k=1:size(area,3) % loop over planes
lut = area(:,1,k);
test_result(:,:,k) = lut(shape(:,:,k));
end
The above only works if shape only contains integer values in the range [1,N], where N = size(area,1). That is, for other values in shape we'll be doing wrong indexing. We will need to fix shape to avoid this. The question here is, what do we want to do with those out-of-range values?
As an example, preparing shape to deal with NaN values:
code = size(area,1) + 1; % this is an unused code word
shape(isnan(shape)) = code;
area(code,1,:) = NaN;
This replaces all NaN values in shape with the value code, which is one larger than any code value we were mapping. Then, we extend area to have one more value, a value for the input code. The value we fill in here is the value that the output test_result will have where shape is NaN. In this case, we write NaN, such that NaN in the input maps to NaN in the output.
Something similar can be done with values below 0 and above 240 (shape(shape<1 | shape>240) = code), or with non-integer values (shape(mod(shape,1)~=0) = code).

Related

How to change pixel values of an RGB image in MATLAB?

So what I need to do is to apply an operation like
(x(i,j)-min(x)) / max(x(i,j)-min(x))
which basically converts each pixel value such that the values range between 0 and 1.
First of all, I realised that Matlab saves our image(rows * col * colour) in a 3D matrix on using imread,
Image = imread('image.jpg')
So, a simple max operation on image doesn't give me the max value of pixel and I'm not quite sure what it returns(another multidimensional array?). So I tried using something like
max_pixel = max(max(max(Image)))
I thought it worked fine. Similarly I used min thrice. My logic was that I was getting the min pixel value across all 3 colour planes.
After performing the above scaling operation I got an image which seemed to have only 0 or 1 values and no value in between which doesn't seem right. Has it got something to do with integer/float rounding off?
image = imread('cat.jpg')
maxI = max(max(max(image)))
minI = min(min(min(image)))
new_image = ((I-minI)./max(I-minI))
This gives output of only 1s and 0s which doesn't seem correct.
The other approach I'm trying is working on all colour planes separately as done here. But is that the correct way to do it?
I could also loop through all pixels but I'm assuming that will be time taking. Very new to this, any help will be great.
If you are not sure what a matlab functions returns or why, you should always do one of the following first:
Type help >functionName< or doc >functionName< in the command window, in your case: doc max. This will show you the essential must-know information of that specific function, such as what needs to be put in, and what will be output.
In the case of the max function, this yields the following results:
M = max(A) returns the maximum elements of an array.
If A is a vector, then max(A) returns the maximum of A.
If A is a matrix, then max(A) is a row vector containing the maximum
value of each column.
If A is a multidimensional array, then max(A) operates along the first
array dimension whose size does not equal 1, treating the elements as
vectors. The size of this dimension becomes 1 while the sizes of all
other dimensions remain the same. If A is an empty array whose first
dimension has zero length, then max(A) returns an empty array with the
same size as A
In other words, if you use max() on a matrix, it will output a vector that contains the maximum value of each column (the first non-singleton dimension). If you use max() on a matrix A of size m x n x 3, it will result in a matrix of maximum values of size 1 x n x 3. So this answers your question:
I'm not quite sure what it returns(another multidimensional array?)
Moving on:
I thought it worked fine. Similarly I used min thrice. My logic was that I was getting the min pixel value across all 3 colour planes.
This is correct. Alternatively, you can use max(A(:)) and min(A(:)), which is equivalent if you are just looking for the value.
And after performing the above operation I got an image which seemed to have only 0 or 1 values and no value in between which doesn't seem right. Has it got something to do with integer/float rounding off?
There is no way for us to know why this happens if you do not post a minimal, complete and verifiable example of your code. It could be that it is because your variables are of a certain type, or it could be because of an error in your calculations.
The other approach I'm trying is working on all colour planes separately as done here. But is that the correct way to do it?
This depends on what the intended end result is. Normalizing each colour (red, green, blue) seperately will result in a different result as compared to normalizing the values all at once (in 99% of cases, anyway).
You have a uint8 RGB image.
Just convert it to a double image by
I=imread('https://upload.wikimedia.org/wikipedia/commons/thumb/0/0b/Cat_poster_1.jpg/1920px-Cat_poster_1.jpg')
I=double(I)./255;
alternatively
I=im2double(I); %does the scaling if needed
Read about image data types
What are you doing wrong?
If what you want todo is convert a RGB image to [0-1] range, you are approaching the problem badly, regardless of the correctness of your MATLAB code. Let me give you an example of why:
Say you have an image with 2 colors.
A dark red (20,0,0):
A medium blue (0,0,128)
Now you want this changed to [0-1]. How do you scale it? Your suggested approach is to make the value 128->1 and either 20->20/128 or 20->1 (not relevant). However when you do this, you are changing the color! you are making the medium blue to be intense blue (maximum B channel, ) and making R way way more intense (instead of 20/255, 20/128, double brightness! ). This is bad, as this is with simple colors, but with combined RGB values you may even change the color itsef, not only the intensity. Therefore, the only correct way to convert to [0-1] range is to assume your min and max are [0, 255].

Build a matrix starting from instances of structure fields in MATLAB

I'm really sorry to bother so I hope it is not a silly or repetitive question.
I have been scraping a website, saving the results as a collection in MongoDB, exporting it as a JSON file and importing it in MATLAB.
At the end of the story I obtained a struct object organised
like this one in the picture.
What I'm interested in are the two last cell arrays (which can be easily converted to string arrays with string()). The first cell array is a collection of keys (think unique products) and the second cell array is a collection of values (think prices), like a dictionary. Each field is an instance of possible values for a set of this keys (think daily prices). My goal is to build a matrix made like this:
KEYS VALUES_OF_FIELD_1 VALUES_OF_FIELD2 ... VALUES_OF_FIELDn
A x x x
B x z NaN
C z x y
D NaN y x
E y x z
The main problem is that, as shown in the image and as I tried to explain in the example matrix, I don't always have a value for all the keys in every field (as you can see sometimes they are 321, other times 319 or 320 or 317) and so the key is missing from the first array. In that case I should fill the missing value with a NaN. The keys can be ordered alphabetically and are all unique.
What would you think would be the best and most scalable way to approach this problem in MATLAB?
Thank you very much for your time, I hope I explained myself clearly.
EDIT:
Both arrays are made of strings in my case, so types are not a problem (I've modified the example). The main problem is that, since the keys vary in each field, firstly I have to find all the (unique) keys in the structure, to build the rows, and then for each column (field) I have to fill the values putting NaN where the key is missing.
One thing to remember you can't simply use both strings and number in one matrix. So, if you combine them together they can be either all strings or all numbers. I think all strings will work for you.
Before make a matrix make sure that all the cells have same element.
new_matrix = horzcat(keys,values1,...valuesn);
This will provide a matrix for each row (according to your image). Now you can use a for loop to get matrices for all the rows.
For now, I've solved it by considering the longest array of keys in the structure as the complete set of keys, let's call it keys_set.
Then I've created for each field in the structure a Map object in this way:
for i=1:length(structure)
structure(i).myMap = containers.Map(structure(i).key_field, structure(i).value_field);
end
Then I've built my matrix (M) by checking every map against the keys_set array:
for i=1:length(keys_set)
for j=1:length(structure)
if isKey(structure(j).myMap,char(keys_set(i)))
M(i,j) = string(structure(j).myMap(char(keys_set(i))));
else
M(i,j) = string('MISSING');
end
end
end
This works, but it would be ideal to also be able to check that keys_set is really complete.
EDIT: I've solved my problem by using this function and building the correct set of all the possible keys:
%% Finding the maximum number of keys in all the fields
maxnk = length(structure(1).key_field);
for i=2:length(structure)
if length(structure(i).key_field) > maxnk
maxnk = length(structure(i).key_field);
end
end
%% Initialiting the matrix containing all the possibile set of keys
keys_set=string(zeros(maxnk,length(structure)));
%% Filling the matrix by putting "0" if the dimension is smaller
for i=1:length(structure)
d = length(string(structure(i).key_field));
if d == maxnk
keys_set(:,i) = string(structure(i).key_field);
else
clear tmp
tmp = [string(structure(i).key_field); string(zeros(maxnk-d,1))];
keys_set(:,i) = tmp;
end
end
%% Merging without duplication and removing the "0" element
keys_set = union_several(keys_set);
keys_set = keys_set(keys_set ~= string(0));

Average across matrices within a structures

I have a structure P with 20 matrices. Each matrix is 53x63x46 double. The names of the matrices are fairly random, for instance S154, S324, S412, etc. Is there any way I can do an average across these matrices without having to type out like this?
M=(P.S154 + P.S324 + P.S412 + ...)/20
Also, does it make sense to use structure for computation like this. According to this post, perhaps it should be converted to cell array.
struct2cell(P)
is a cell array each of whose elements is one of your structure fields (the field names are discarded). Then
cell2mat(struct2cell(P))
is the result of concatenating these matrices along the first axis. You might reasonably ask why it does that rather than, say, making a new axis and giving you a 4-dimensional array, but expecting sensible answers to such questions is asking for frustration. Anyway, unless I'm getting the dimensions muddled,
reshape(cell2mat(struct2cell(P)),[53 20 63 46])))
will then give you roughly the 4-dimensional array you're after, with the "new" axis being (of course!) number 2. So now
mean(reshape(cell2mat(struct2cell(P)),[53 20 63 46]),2)
will compute the mean along that axis. The result will have shape [53 1 63 46], so now you will need to fix up the axes again:
reshape(mean(reshape(cell2mat(struct2cell(P)),[53 20 63 46]),2),[53 63 46])
If you are using structures, and by your question, you have fieldnames for each matrix.
Therefore, you need to:
1 - use function fieldnames to extract all the matrix names inside your structure. - http://www.mathworks.com/help/matlab/ref/fieldnames.html
2- then you can access it by doing like:
names = fieldnames(P);
matrix1 = P.names{1}
Using a for loop you can then make your calculations pretty fast!

How to take the mean of columns from an array within an array?

I am currently working on a project in Matlab where I have a cell array of cell arrays. The first cell array is 464 columns long and 1 row deep. Each of these cells is another cell array that is 96 columns and 365 rows. I need to be able to get the mean of the 96 columns for each of the 464 arrays and place each of the 464 arrays on a different row in a new array called mean. I have tried to write code to just do one column as follow:
mean = Homes{1,1}(1:)
But I when ever I try to run this code I got the follow error:
mean = Homes{1,1}(1:)
|
Error: Unbalanced or unexpected parenthesis or bracket.
Basically my final array name mean needs to be 96 columns by 464 rows. I am stuck and could really use your help.
Thank you.
I suggest you to try the following code on a smaller matrix. See if it gives you the desired results.
a=cell(1,4); %for you it will be a=cell(1,464)
for i=1:4
a{i}=randi(10,[5 10]); %for you it will be a{i}=randi(10,[365 96]);
end
a1=cell2mat(a); %just concatenating
a1=mean(a1); %getting the mean for each column. in your case, you should get the mean for 96*464
a2=reshape(a1,[10 4]); %now what reshape does it it takes first 10 elements and arranges it into first column.
%Therefore, since I finally want a 4x10 matrix (in your case 464x96), i.e. mean of 10 elements in first cell array on first row and so on...
%Thus, 1st 10 elements should go to first column after doing reshape (since you want to keep them together). Therefore, instead of directly making matrix as 4x10, first make it as 10x4 and then take transpose (which is the next step).
a2=a2'; %thus you get a 4x10 matrix.
In your case specifically, the code will be
a=cell(1,464);
for i=1:464
a{i}=randi(10,[365 96]);
end
a1=cell2mat(a);
a1=mean(a1);
a2=reshape(a1,[96 365]);
a2=a2';

For loop inside another for loop to make new set of vectors

I would like to use a for loop within a for loop (I think) to produce a number of vectors which I can use separately to use polyfit with.
I have a 768x768 matrix and I have split this into 768 separate cell vectors. However I want to split each 1x768 matrix into sections of 16 points - i.e. 48 new vectors which are 16 values in length. I want then to do some curve fitting with this information.
I want to name each of the 48 vectors something different however I want to do this for each of the 768 columns. I can easily do this for either separately but I was hoping that there was a way to combine them. I tried to do this as a for statement within a for statement however it doesn't work, I wondered if anyone could give me some hints on how to produce what I want. I have attached the code.
Qne is my 768*768 matrix with all the points.
N1=768;
x=cell(N,1);
for ii=1:N1;
x{ii}=Qnew(1:N1,ii);
end
for iii = 1:768;
x2{iii}=x{iii};
for iv = 1:39
N2=20;
x3{iii}=x2{iii}(1,(1+N2*iv:N2+N2*iv));
%Gx{iv}=(x3{iv});
end
end
Use a normal 2D matrix for your inner split. Why? It's easy to reshape, and many of the fitting operations you'll likely use will operate on columns of a matrix already.
for ii=1:N1
x{ii} = reshape(Qnew(:, ii), 16, 48);
end
Now x{ii} is a 2D matrix, size 16x48. If you want to address the jj'th split window separately, you can say x{ii}(:, jj). But often you won't have to. If, for example, you want the mean of each window, you can just say mean(x{ii}), which will take the mean of each column, and give you a 48-element row vector back out.
Extra reference for the unasked question: If you ever want overlapping windows of a vector instead of abutting, see buffer in the signal processing toolbox.
Editing my answer:
Going one step further, a 3D matrix is probably the best representation for equal-sized vectors. Remembering that reshape() reads out columnwise, and fills the new matrix columnwise, this can be done with a single reshape:
x = reshape(Qnew, 16, 48, N1);
x is now a 16x48x768 3D array, and the jj'th window of the ii'th vector is now x(:, jj, ii).