Language : MATLAB
Problem Defenition:
I have a set of 2D points in space. I would like to group the points based on their euclidean distance. My data has a property that two groups are always separated by at least R units. Hence for a given point, all points that are closer than 50 units can be considered to be its neighbors. Combining points having common neighbors would result in the groups (that is the idea at least).
Proposed Method:
Use delaunay triangulation in matlab and get list of edges of the resulting triangles. Remove all edges that are greater than R units. Each group of points left are the groups I am looking for. Remaining unconnected points can be ignored.
Attempt:
I tried to implement the above in MATLAB, but I am making a mistake in grouping the left over points. I am attaching my code.
DT = delaunayTriangulation(double(frame(:,1:2)));
edgeList = edges(DT);
edgeVertex1 = frame(edgeList(:,1),:);
edgeVertex2 = frame(edgeList(:,2),:);
dVec = edgeVertex1 - edgeVertex2;
edgeLengths = sqrt(sum(abs(dVec).^2,2));
requiredEdges = edgeLengths < NEIGH_RADIUS;
edgeLengthsFiltered = edgeLengths(requiredEdges);
edgeListFiltered = edgeList(requiredEdges,:);
% Clustering
edgeOrigins = edgeListFiltered(:,1);
edgeEndings = edgeListFiltered(:,2);
nodeList = unique(edgeOrigins);
if isempty(nodeList)
Result = struct([]);
super_struct(i).result = Result;
else
groups = cell(10,1);
groups{1} = nodeList(1);
groupLength = 2;
flag = 0;
% grouping
for j = 1:1:length(nodeList);
neighbourList = [nodeList(j); edgeEndings(edgeOrigins==nodeList(j))];
% add current node as part of neighbourList
for k = 1:1:groupLength-1
te = ismembc(groups{k}, neighbourList);
if sum(te) ~=0
temp = sort([groups{k}; neighbourList]);
groups{k} = temp([true;diff(temp(:))>0]);
flag = 1;
break;
end
end
if ~flag
groups{groupLength} = neighbourList;
groupLength = groupLength + 1;
end
flag = 0;
end
largeGroups = cell(1,1);
largeGroups_c = 1;
for j = 1:1:groupLength -1;
if ~ isempty(groups{j})
for k = j+1:1:groupLength - 1
te = ismembc(groups{j}, groups{k});
if sum(te) ~= 0
temp = sort([groups{j}; groups{k}]);
groups{j} = temp([true;diff(temp(:))>0]);
groups{k} =[];
end
end
% ignore small groups
if length(groups{j}) > MIN_PTS_IN_GROUP
largeGroups{largeGroups_c} = groups{j};
largeGroups_c = largeGroups_c+1;
end
end
end
in the above code, frame is the variable that has the list of points. The constants NEIGH_RADIUS represents R from the question. The other constant MIN_PTS_IN_GROUP is user defined to select the minimum no of points needed to consider it a cluster of interest.
When I run the above code, there are still instances where a single group of points are still represented as multiple groups.
The red lines border a single group as identified by the code above. Clearly there are intersecting groups which is wrong.
Question 1
Can someone suggest a better (and correct) way of grouping?
Question 2
Any other alternate methods of obtaining the groups faster than Triangulation would also be great!
Thank you in advance
If you know the number of group you search for, you can use kmeans function in matlab statistic toolbox or you can find other implentation on matlab exchange (kmeans clustering)
Related
I have to plot some Venn diagrams to show intersections between arrays of strings. Let me explain: I have a category of names that includes three others that do not intersect each other. I want to understand which of these three occupies the largest percentage of the macro-category mentioned above. I would like to do it with MATLAB but I see that this development environment is somewhat devoid of such functions. If you have any ideas in mind I would be grateful.
Thanks in advance!
Venn Diagrams by Plotting Circles
Not the most elegant way but one method of plotting a Venn diagram can be plotting circles and filling the individual circles using a text() annotation/label. Unfortunately, here I manually placed where the labels are centred. Automating the label positioning may take several additional steps which I will leave out for simplicity. To find the intersections of the respective datasets I simply used the intersect() function.
A = {'A','B','C','D','E','F','G','H','I','L'};
B = {'A','B','C','D'};
C = {'E','F','G','H','I','L'};
%Finding the intersections of the arrays%
Intersection_AB = intersect(A,B);
fprintf("Intersection AB: ")
disp(Intersection_AB);
fprintf("\n");
Intersection_BC = intersect(B,C);
fprintf("Intersection BC: ")
disp(Intersection_BC);
fprintf("\n");
Intersection_AC = intersect(A,C);
fprintf("Intersection AC: ")
disp(Intersection_AC);
fprintf("\n");
Intersection_ABC = intersect(Intersection_AB,C);
fprintf("Intersection ABC: ")
disp(Intersection_ABC);
fprintf("\n");
clc;
clf;
Plotting_Interval = 0.01;
Angles_In_Radians = (0: Plotting_Interval: 2*pi);
Circle_Plot = #(X_Offset,Y_Offset,Radius) plot(X_Offset + Radius*cos(Angles_In_Radians),Y_Offset + Radius*sin(Angles_In_Radians));
hold on
%Plotting the 3 circles%
X_Offset_A = 0; Y_Offset_A = 2; Radius_A = 3;
Circle_A = Circle_Plot(X_Offset_A,Y_Offset_A,Radius_A);
fill(Circle_A.XData, Circle_A.YData,'r','FaceAlpha',0.2,'LineWidth',1);
X_Offset_B = -2; Y_Offset_B = -2; Radius_B = 3;
Circle_B = Circle_Plot(X_Offset_B,Y_Offset_B,Radius_B);
fill(Circle_B.XData, Circle_B.YData,'g','FaceAlpha',0.2,'LineWidth',1);
X_Offset_C = 2; Y_Offset_C = -2; Radius_C = 3;
Circle_Plot(X_Offset_C,Y_Offset_C,Radius_C);
Circle_C = Circle_Plot(X_Offset_C,Y_Offset_C,Radius_C);
fill(Circle_C.XData, Circle_C.YData,'b','FaceAlpha',0.2,'LineWidth',1);
title("Venn Diagram");
%Writing all the labels%
A_Label = strjoin(string(A));
text(X_Offset_A,Y_Offset_A,A_Label,'color','r');
B_Label = strjoin(string(B));
text(X_Offset_B,Y_Offset_B,B_Label,'color','g');
C_Label = strjoin(string(C));
text(X_Offset_C,Y_Offset_C,C_Label,'color','b');
AB_Label = strjoin(string(Intersection_AB));
text(-1.2,0,AB_Label);
BC_Label = strjoin(string(Intersection_BC));
text(0,-2,BC_Label);
AC_Label = strjoin(string(Intersection_AC));
text(1.2,0,AC_Label);
ABC_Label = strjoin(string(Intersection_ABC));
text(0,0,ABC_Label);
%Setting the labels to be relative to the centres%
set(findall(gcf,'type','text'),'HorizontalAlignment','center');
axis equal
axis off
Ran using MATLAB R2019b
I have written the 3x3 average filter. It works fine but it shows the same output image three times instead of one. How to resolve the problem?
The code is
function [filtr_image] = avgFilter(noisy_image)
[x,y] = size(noisy_image);
filtr_image = zeros(x,y);
for i = 2:x-1
for j =2:y-1
sum = 0;
for k = i-1:i+1
for l = j-1:j+1
sum = sum+noisy_image(k,l);
end
end
filtr_image(i,j) = sum/9.0;
filtr_image = uint8(filtr_image);
end
end
end
thanks in advance
What is most likely happening is the fact that you are supplying a colour image when the code is specifically meant for grayscale. The reason why you see "three" is because when you do this to allocate your output filtered image:
[x,y] = size(noisy_image)
If you have a 3D matrix, the number of columns reported by size will be y = size(noisy_image,2)*size(noisy_image,3);. As such, when you are iterating through each pixel in your image, in column major order each plane would be placed side by side each other. What you should do is either convert your image into grayscale from RGB or filter each plane separately.
Also, you have an unnecessary casting performed in the loop. Just do it once outside of the loop.
Option #1 - Filter per plane
function [filtr_image] = avgFilter(noisy_image)
[x,y,z] = size(noisy_image);
filtr_image = zeros(x,y,z,'uint8');
for a = 1 : z
for i = 2:x-1
for j =2:y-1
sum = 0;
for k = i-1:i+1
for l = j-1:j+1
sum = sum+noisy_image(k,l,a);
end
end
filtr_image(i,j,a) = sum/9.0;
end
end
end
end
Then you'd call it by:
filtr_image = avgFilter(noisy_image);
Option #2 - Convert to grayscale
filtr_image = avgFilter(rgb2gray(noisy_image));
Minor Note
You are using sum as a variable. sum is an actual function in MATLAB and you would be overshadowing this function with your variable. This will have unintended consequences if you have other functions that rely on sum later down the line.
I can't see why your code would repeat the image (unless it's a pattern cause by an integer overflow :/ ) but here are some suggestions:
if you want to use loops, at least drop the inner loops:
[x,y] = size(noisy_image);
filtr_image = zeros(x,y);
for i = 2:x-1
for j =2:y-1
% // you could do this in 1 line if you use mean2(...) instead
sub = noisy_image(i-1:i+1, j-1:j+1);
filtr_image = uint8(mean(sub(:)));
end
end
However do you know about convolution? Matlab has a built in function for this:
filter = ones(3)/9;
filtr_image = uint8(conv2(noisy_image, filter, 'same'));
I know basic commands in order to identify objects in a picture like:
level = graythresh(bw);
bw = im2bw(bw,level);
cc = bwconncomp(bw, 4);
cc.NumObjects;
graindata = regionprops(cc, 'basic');
perimeter = regionprops(cc, 'perimeter');
Those codes above is the code I am using.
In the picture attached, I can get the number to be 4. So the code identify that there is in total 4 objects.
However, this picture actually contains two objects. If we replicate this picture and move the replicate to the up, down, left and right, we can see that there is only two objects. But they are "separated" by the boundary.
It is not doable to change the way of making the image so the only way I can think of is to use some function or codes in matlab.
I will really appreciate it if someone can provide some matlab function to solve this problem.
All you need to do is loop over the border rows and columns and merge any regions that line up on opposite sides. The following code will produce an image with the regions labelled by number in the way you want.
cc=bwconncomp(bw);
[rows,cols] = size(reg);
% matrix of region labels
regions = uint8(zeros(rows,cols));
% label each pixel with an integer for its region number
for i = 1:length(cc.PixelIdxList)
region(cc.PixelIdxList{i}) = i;
end
% loop over rows, merge the regions if pixels line up
for i = 1:rows
left = region(i,1);
right = region(i,end);
if (left>0) && (right>0) && (left~=right)
region(region==right) = left;
end
end
% loop over columns, merge the regions if pixels line up
for j = 1:cols
top = region(1,j);
bottom = region(end,j);
if (top>0) && (bottom>0) && (top~=bottom)
region(region==bottom) = top;
end
end
Hello again logical friends!
I’m aware this is quite an involved question so please bear with me! I think I’ve managed to get it down to two specifics:- I need two loops which I can’t seem to get working…
Firstly; The variable rollers(1).ink is a (12x1) vector containing ink values. This program shares the ink equally between rollers at each connection. I’m attempting to get rollers(1).ink to interact with rollers(2) only at specific timesteps. The ink should transfer into the system once for every full revolution i.e. nTimesSteps = each multiple of nBins_max. The ink should not transfer back to rollers(1).ink as the system rotates – it should only introduce ink to the system once per revolution and not take any back out. Currently I’ve set rollers(1).ink = ones but only for testing. I’m truly stuck here!
Secondly; The reason it needs to do this is because at the end of the sim I also wish to remove ink in the form of a printed image. The image should be a reflection of the ink on the last roller in my system and half of this value should be removed from the last roller and taken out of the system at each revolution. The ink remaining on the last roller should be recycled and ‘re-split’ in the system ready for the next rotation.
So…I think it’s around the loop beginning line86 where I need to do all this stuff. In pseudo, for the intermittent in-feed I’ve been trying something like:
For k = 1:nTimeSteps
While nTimesSteps = mod(nTimeSteps, nBins_max) == 0 % This should only output when nTimeSteps is a whole multiple of nBins_max i.e. one full revolution
‘Give me the ink on each segment at each time step in a matrix’
End
The output for averageAmountOfInk is the exact format I would like to return this data except I don’t really need the average, just the actual value at each moment in time. I keep getting errors for dimensional mismatches when I try to re-create this using something like:
For m = 1:nTimeSteps
For n = 1:N
Rollers(m,n) = rollers(n).ink’;
End
End
I’ll post the full code below if anyone is interested to see what it does currently. There’s a function at the end also which of course needs to be saved out to a separate file.
I’ve posted variations of this question a couple of times but I’m fully aware it’s quite a tricky one and I’m finding it difficult to get my intent across over the internets!
If anyone has any ideas/advice/general insults about my lack of programming skills then feel free to reply!
%% Simple roller train
% # Single forme roller
% # Ink film thickness = 1 micron
clc
clear all
clf
% # Initial state
C = [0,70; % # Roller centres (x, y)
10,70;
21,61;
11,48;
21,34;
27,16;
0,0
];
R = [5.6,4.42,9.8,6.65,10.59,8.4,23]; % # Roller radii (r)
% # Direction of rotation (clockwise = -1, anticlockwise = 1)
rotDir = [1,-1,1,-1,1,-1,1]';
N = numel(R); % # Amount of rollers
% # Find connected rollers
isconn = #(m, n)(sum(([1, -1] * C([m, n], :)).^2)...
-sum(R([m, n])).^2 < eps);
[Y, X] = meshgrid(1:N, 1:N);
conn = reshape(arrayfun(isconn, X(:), Y(:)), N, N) - eye(N);
% # Number of bins for biggest roller
nBins_max = 50;
nBins = round(nBins_max*R/max(R))';
% # Initialize roller struct
rollers = struct('position',{}','ink',{}','connections',{}',...
'rotDirection',{}');
% # Initialise matrices for roller properties
for ii = 1:N
rollers(ii).ink = zeros(1,nBins(ii));
rollers(ii).rotDirection = rotDir(ii);
rollers(ii).connections = zeros(1,nBins(ii));
rollers(ii).position = 1:nBins(ii);
end
for ii = 1:N
for jj = 1:N
if(ii~=jj)
if(conn(ii,jj) == 1)
connInd = getConnectionIndex(C,ii,jj,nBins(ii));
rollers(ii).connections(connInd) = jj;
end
end
end
end
% # Initialize averageAmountOfInk and calculate initial distribution
nTimeSteps = 1*nBins_max;
averageAmountOfInk = zeros(nTimeSteps,N);
inkPerSeg = zeros(nTimeSteps,N);
for ii = 1:N
averageAmountOfInk(1,ii) = mean(rollers(ii).ink);
end
% # Iterate through timesteps
for tt = 1:nTimeSteps
rollers(1).ink = ones(1,nBins(1));
% # Rotate all rollers
for ii = 1:N
rollers(ii).ink(:) = ...
circshift(rollers(ii).ink(:),rollers(ii).rotDirection);
end
% # Update all roller-connections
for ii = 1:N
for jj = 1:nBins(ii)
if(rollers(ii).connections(jj) ~= 0)
index1 = rollers(ii).connections(jj);
index2 = find(ii == rollers(index1).connections);
ink1 = rollers(ii).ink(jj);
ink2 = rollers(index1).ink(index2);
rollers(ii).ink(jj) = (ink1+ink2)/2;
rollers(index1).ink(index2) = (ink1+ink2)/2;
end
end
end
% # Calculate average amount of ink on each roller
for ii = 1:N
averageAmountOfInk(tt,ii) = sum(rollers(ii).ink);
end
end
image(5:20) = (rollers(7).ink(5:20))./2;
inkPerSeg1 = [rollers(1).ink]';
inkPerSeg2 = [rollers(2).ink]';
inkPerSeg3 = [rollers(3).ink]';
inkPerSeg4 = [rollers(4).ink]';
inkPerSeg5 = [rollers(5).ink]';
inkPerSeg6 = [rollers(6).ink]';
inkPerSeg7 = [rollers(7).ink]';
This is an extended comment rather than a proper answer, but the comment box is a bit too small ...
Your code overwhelms me, I can't see the wood for the trees. I suggest that you eliminate all the stuff we don't need to see to help you with your immediate problem (all those lines drawing figures for example) -- I think it will help you to debug your code yourself to put all that stuff into functions or scripts.
Your code snippet
For k = 1:nTimeSteps
While nTimesSteps = mod(nTimeSteps, nBins_max) == 0
‘Give me the ink on each segment at each time step in a matrix’
End
might be (I don't quite understand your use of the while statement, the word While is not a Matlab keyword, and as you have written it the value returned by the statement doesn't change from iteration to iteration) equivalent to
For k = 1:nBins_max:nTimeSteps
‘Give me the ink on each segment at each time step in a matrix’
End
You seem to have missed an essential feature of Matlab's colon operator ...
1:8 = [1 2 3 4 5 6 7 8]
but
1:2:8 = [1 3 5 7]
that is, the second number in the triplet is the stride between successive elements.
Your matrix conn has a 1 at the (row,col) where rollers are connected, and a 0 elsewhere. You can find the row and column indices of all the 1s like this:
[ri,ci] = find(conn==1)
You could then pick up the (row,col) locations of the 1s without the nest of loops and if statements that begins
for ii = 1:N
for jj = 1:N
if(ii~=jj)
if(conn(ii,jj) == 1)
I could go on, but won't, that's enough for one comment.
I have 4 different lengths of data (in rows) and they all have a differing ammount of columns. I need to apply an equation to each of these columns and then extract the max value from each of them.
The equation I am trying to use is:
averg = mean([interpolate(1:end-2),interpolate(3:end)],2); % this is just getting your average value.
real_num = interpolate(2:end-1);
streaking1 = (abs(real_num-averg)./averg)*100;
An example of one of my data sets is 5448 rows by 13 columns
EDIT
This is the current adapation of Ben A.'s Solution and it is working.
A = interpolate;
averg = (A(1:end-2,:) + A(3:end,:))/2;
center_A = A(2:end-1,:);
streaking = [];
for idx = 1:size(A,2)
streaking(:,idx) = (abs(center_A(idx,:)-averg(idx,:))./averg(idx,:))*100;
end
I'm not entirely sure that I fully follow what you're doing in each step, but here is a stab at it:
A = interpolate;
averg = (A(1:end-2,:) + A(3:end,:))/2;
center_A = A(2:end-1,:);
streaking = [];
for idx = 1:size(A,2)
streaking(:,idx) = (abs(center_A(idx,:)-averg(idx,:))./averg(idx,:))*100;
end
Averg will be a vector of means for each column. I just use the values in the given column as the real_num variable that you had before. I'm not clear why you would need to index that the way you are as nothing is at risk of breaking index rules.
If this helps, great! If not let me know and I'll see if I can revise somewhat.