Grouping neighbours in separate arrays in MATLAB - matlab

I have an annotated 512x512x3 fingerprint image, with the annotations being the positions of loops and deltas in the image. Red denotes loop, and blue denotes delta.
I need to find the position of these points. I can do this using impixel(), but I wanted a more automated approach.
I did the following:
x=[];
y=[];
for i = 1:512
for j = 1:512
marked = im(i,j,:);
if marked(:,:,1)==255
x=[x i];
y=[y j];
end
end
end
x = unique(x);
y = unique(y);
and got the positions of the pixels where the color red is the position of the loops.
My array y holds the y coordinates of all the red pixels, like this:
[116 117 118 119 120 121 122 191 192 193 194 195 196 197]
At first glance, it is clear that the values can be grouped into 2 groups, for the two red points. But I am unsure how to divide the values into two groups. I thought about creating a threshold using the mean of the values, but that might not work if the points are close by.
Any help might be useful.

If you know in advance how many red points there will be (2 in the example), you could use kmeans -
y_groups = kmeans(y',2);
If you don't know the number of groups, then if the points always contain the same number of pixels, you could work it out from that - in the example there are 7 pixels per point so
npts = numel(y) / 7;
y_groups = kmeans(y',npts);
We could add a loop afterwards to get the mean of each group (there are many other ways you could look into for this too e.g. grpstats):
y_means = NaN(npts,1);
for I = 1:npts
y_means(I) = mean(y(y_groups == I));
end

Related

How do I divide a bounding box into sub boxes in Matlab

I have a bound box and points located inside it in different areas and positions.
I want to divide that box into sub boxes according to the dividing point DP[x,y] into box [ax ay bx by] that may locate close to each other.
Here are examples for illustrating the idea:
I tried to calculate and make it by my hand but they are hundreds, I stuck. Is there any Matlab function to do that?
My question is: How to divide a bounding box into sub boxes according to the specific points?
I used the function point2bbox described here, but it does not a suitable solution in this case because the points are not in the corners of the box.
Here is an example of what I want to do:
bound box = [341 91 24 74]
x = 341, y = 91, width = 24 , height = 74
DP1(349,49) , DP2 (360,70)
points to calculated are: a1(349,91),a2(350,91),a2(360,91), and a3(361,91)
The corners points of big box are: A(341,91), B(365,91), C(341,17), and D(365,17)
bbox1 = [341 91 8 74], bbox2 = [350 91 10 74], and bbox3 =[361 91 4 74].
see the picture below for more clarifying:
Could you suggest any idea to do that, please? I appreciate any help.
Here are the solution steps of my question to divide the Big box to three boxes. Maybe it could help another one.
%% Big box values in [ax,ay,bx,by]
bound = [341,91 ,365,17];
%% Two Divide Points are: [349,49,360,70]in form [x1,y1,x2,y2]
% store as struct
DP =struct('dividePoint1',{349,49},'dividePoint2',{360,70});
DP2 = [DP(1,1).dividePoint1;DP(1,1).dividePoint2];
DP1x = DP(1,1).dividePoint1;
DP1y = DP(1,2).dividePoint1;
DP2x = DP(1,1).dividePoint2;
DP2y = DP(1,2).dividePoint2;
%% Put the two points in one vector
DPmat = [DP1x DP1y;DP2x DP2y];
%% Sort Points
DPSorted = sort(DPmat,'ascend');
%% convert bound to 4 values [ax,ay,bx,by]
axBound =bound(1);ayBound = bound(2);bxBound = bound(3);byBound = bound(4);
%% Initial empty x-axis value
XsValues = {};
%% loop to divide two values to four values DP1,DP1+1,DP2,DP2+1
for k = 1:(numel(DP2))
boxes = [DPSorted(k,1),DPSorted(k,1)+1];
XsValues = [XsValues;boxes];
end
%% rearrang the points
% convert x-axis values to matrix
xBoxes = cell2mat(XsValues);
xValues = [xBoxes(1,:),xBoxes(2,:)];
subBox1 = [axBound,ayBound,xValues(1),byBound];
subBox2 = [xValues(2),ayBound,xValues(3),byBound];
subBox3 = [xBoxes(4),ayBound,bxBound,byBound];
%% all subBoxes in one matrix
AllBoxes = [subBox1;subBox2;subBox3];
I appreciate all of your help.
If it need improve you can help us also.
Well, another in my opinion clearer option is using mat2cell: https://www.mathworks.com/help/matlab/ref/mat2cell.html
Suppose your matrix origMat is a 22x74 matrix, that you want to split it to three matrices sized 8x74, 10x74 and 4x74 - as in your example.
subboxes = mat2cell(origMat, [8,10,4]); % This gives you 3x1 cell array containing your matrices 8x74, 10x74 and 4x74.
If the question is how to get those numbers 8, 10 and 4, well, in your example, you would simply use DP1(1)-A(1); DP2(1)-DP1(1); B(1)-DP2(1).
Now, for a bit more general case. Suppose you have a matrix origMat. We have DP = L*2 array of division points on the matrix, and we want to divide original matrix to smaller boxes both horizontally and vertically according to those division points. In your example you would also split matrix horizontally to 21, 21 and 32 points from top to bottom. This solution ignores top left corner point position of the original matrix as it is easy to shift all points by some offset if required.
origMat = rand(74,22); %74 row 22 col matrix.
DP = [21,8; 42,18]; % 2 division points at row 21, col 8 and row 42, col 18.
[S1, S2] = size(origMat);
SDPy = sort(DP(:,1));
SDPx = sort(DP(:,2));
% widths are to first division point, then distances between division points, then from last division point to the edge of original matrix.
widths = [SDPx(1), SDPx(2:end)-SDPx(1:end-1), S2-SDPx(end)];
heights = [SDPy(1), SDPy(2:end)-SDPy(1:end-1), S1-SDPy(end)];
subboxes = mat2cell(origMat, heights, widths);
Duplicates in x or y will lead to some 0xN or Nx0 matrices. Remove duplicated sorted entries or points at 0 or S1/S2 in that direction to avoid that if it is undesirable.
If you have division points beyond the edges of original matrix, this code won't work. How to handle points beyond the edges mainly depends what you want to do:
If you want to remove problematic points entirely, you should check DP list and delete rows where x or y are out of bounds.
If you want to keep division in the ok dimension and just ignore the out of bounds part, remove all sorted elements that are out of bounds.
Differences are mainly conceptual - do you want to divide some internal small bounding box (inside a much larger image) only by points inside it, or also by coordinates outside? This depends on application.

Condense a set of points of a polygon into a shorter set of points

I have the following polygon which is just a set of 2D points as follows:-
poly0=[80 60
90 60
100 60
110 60
110 50
120 50
130 50
140 50
150 50
160 50
170 50
180 50
190 50
200 50
210 50
210 60
210 70
210 80
210 90
220 90
220 100
210 100
210 110
200 110
200 120
190 120
180 120
180 130
170 130
160 130
150 130
140 130
130 130
130 120
120 120
110 120
110 110
100 110
100 100
90 100
90 90
90 80
90 70
80 70
80 60];
Now I can plot it using.
>> line(poly0(:,1), poly0(:,2),'Color','k','LineWidth',3,'LineStyle',':');
This clearly shows one thing that my original set of polygon points is highly redundant. Basically, multiple points lying on the same straight line are enumerated above which is not needed. I could start checking each pair of points and if they are on the same straight line I could remove them. But that would mean using many for loops. I cannot come up with a smart vectorized way.
How do I get a new set of points which is much shorter in size than the previous but which still represents the exact same polygon? I should only have as many points as there are vertices in the polygon. So in other words how to find the vertices from the above data set in a quick way?
PS: Here the vertex angles are 90 degrees, but if you give a solution don't try to exploit that fact. I want a more general answer.
The two existing answers have large shortcomings:
Durkee's method only works if the distances between subsequent points is exactly identical. Points must have coordinates perfectly representable as floating-point values, so that the distances between subsequent points on a line can be found to be identical. If points are not equidistant, the method does nothing. Also, the start and end of the polygon are not examined together, so if a straight line is formed across the start/end of the polygon, one point too many will remain.
ShadowMan's method is better in that distances do not need to be identical, and the line across the start/end of the polygon is correctly handled. However it also uses floating point equality comparisons, which will not work in general. Only with integer coordinates will this method work correctly. Furthermore it uses vecnorm (which does a square root) and a division, both are relatively expensive operations (when compared to the method shown here).
To see if three points form a straight line, a simple arithmetic rule can be used. Let's say we have points p0, p1 and p2. The vector from p0 to p1 and the vector from p0 to p2 form the basis of a parallelogram, whose area can be computed by the cross product of the two vectors (in 2D, the cross product is understood to use z=0, with the resulting vector having x=0 and y=0, only the z value is useful; thus, the 2D cross product we assume to produce a scalar value). It can be computed as follows:
v1 = p1 - p0;
v2 = p2 - p0;
x = v1(1)*v2(2) - v1(2)*v2(1);
x, the cross product, will be zero if the two vectors are parallel, meaning that the three points are collinear. But the test for equality to 0 must have some tolerance, since floating-point arithmetic is inexact. I use 1e-6 as a tolerance here. Use a value that is several orders of magnitude smaller than the distance between your points.
Given an input set of points p, we can find the corner points with:
p1 = p; % point 1
p0 = circshift(p1,1); % point 0
v1 = p1 - p0; % vector from point 0 to 1
v2 = circshift(p1,-1) - p0; % vector from point 0 to 2
x = v1(:,1).*v2(:,2) - v1(:,2).*v2(:,1); % cross product
idx = abs(x) > 1e-6; % comparison with tolerance
p = p(idx,:); % corner points
Do note that this cross product test will fail if two consecutive points have identical coordinates (i.e. one of the vectors has a zero length). An additional test would be necessary if the data could have duplicated points.
Here are the results of the three methods. I've created a polygon with non-trivial coordinates and not equally spaced vertices. I've also put the start/end gap in the middle of a straight edge. These characteristics are purposeful to show the shortcomings of the other two methods.
This is the code I used to produce the graph:
% Make a polygon that will be difficult for the other two methods
p = [0,0 ; 0.5,0 ; 1,0 ; 1,1 ; 0.5,1 ; 0,1];
p = p + rand(size(p))/3;
p(end+1,:) = p(1,:);
q = [];
for ii = 1:size(p,1)-1
t = p(ii,:) + (p(ii+1,:) - p(ii,:)) .* [0;0.1;1/3;0.45;0.5897545;pi/4;exp(1)/3];
q = [q;t];
end
q = circshift(q,3,1);
figure
subplot(2,2,1)
plot(q(:,1),q(:,2),'bo-')
axis equal
title('input')
subplot(2,2,2)
res1 = method1(q);
plot(res1(:,1),res1(:,2),'ro-')
axis equal
title('Durkee''s method')
subplot(2,2,3)
res2 = method2(q);
plot(res2(:,1),res2(:,2),'ro-')
axis equal
title('ShadowMan''s method')
subplot(2,2,4)
res3 = method3(q);
plot(res3(:,1),res3(:,2),'go-')
axis equal
title('correct method')
% Durkee's method: https://stackoverflow.com/a/55603145/7328782
function P = method1(P)
a = logical([1 diff(P(:,1),2)' 1]);
b = logical([1 diff(P(:,2),2)' 1]);
idx = or(a,b);
P = P(idx,:);
end
% ShadowMan's method: https://stackoverflow.com/a/55603040/7328782
function corners = method2(poly0)
poly0Z = circshift(poly0,1);
poly0I = circshift(poly0,-1);
unitVectIn =(poly0 - poly0I)./vecnorm((poly0 - poly0I),2,2);
unitVectOut =(poly0Z - poly0)./vecnorm((poly0Z - poly0),2,2);
cornerIndices = sum(unitVectIn == unitVectOut,2)==0;
corners = poly0(cornerIndices,:);
end
% vecnorm is new to R2017b, I'm still running R2017a.
function p = vecnorm(p,n,d)
% n is always 2
p = sqrt(sum(p.^2,d));
end
function p = method3(p1)
p0 = circshift(p1,1);
v1 = p1 - p0;
v2 = circshift(p1,-1) - p0;
x = v1(:,1).*v2(:,2) - v1(:,2).*v2(:,1);
idx = abs(x) > 1e-6;
p = p1(idx,:);
end
Okay, I adapted this to deal with non-square corners.
Consider a triangle identified by the points
P = [0 0; 1 0; 2 0; 1.5 1; 1 2; .5 1; 0 0];
This is a 7x2 array, if we then define the 2 derivative vectors as defined by the question I mentioned in the comments.
a = logical([1 diff(P(:,1),2)' 1]);
b = logical([1 diff(P(:,2),2)' 1]);
From there, we may combine the two to get a new indexing variable
idx = or(a,b);
Finally, we may use this to make our plot
line(P(idx,1), P(idx,2),'Color','k','LineWidth',3,'LineStyle',':');
If you're doing a line plot, I think you need to set the last variable to false.
idx(end) = false;
The 'vector' way can be done quite elegantly. I tried a for loop way too and you can do the same thing that way but you asked for vector so here is my way of doing that.
The only change I made to your data was to remove any replicates prior to starting this script. Also, the points provided should be in sequential order going either clockwise or counter clockwise.
poly0Z = circshift(poly0,1);
poly0I = circshift(poly0,-1);
unitVectIn =(poly0 - poly0I)./vecnorm((poly0 - poly0I),2,2);
unitVectOut =(poly0Z - poly0)./vecnorm((poly0Z - poly0),2,2) ;
cornerIndices = sum(unitVectIn == unitVectOut,2)==0
corners = poly0(cornerIndices,:)
line(poly0(:,1), poly0(:,2),'Color','k','LineWidth',2,'LineStyle',':');
hold on
scatter(corners(:,1), corners(:,2),'filled')
The basis of this method is to go to each point, compute the unit vector coming in, and the unit vector going out. Points where the unit vector in does not match the unit vector out are corners.

Translating 2D image with RGB colours along axes

I am trying to create my own voronoi diagram. I have an arbitrary shape defined by their x and y coordinates stored in separate vectors. Inside this shape are some points of interest (with known coordinates) that belong to two different groups and act as seeds to the voronoi diagram. As an example, the whole diagram ranges from x=-10 to x=90 and y=-20 to y=60. The boundary shape is not rectangular but falls within the axes range above.
What I have done so far is to create a 3D matrix (100 X 80 X 3), C, with all ones so that the default colour will be white. I then looped through from i=1:100, j=1:80, testing individual pixels to see if they fall within the shape using inpolygon. If they do, I then find out which point is the pixel closest to and assign it a colour based on whether the closest point belongs to group 1 or 2.
All is good so far. I then used imagesc to display the image with a custom axis range. The problem is that the voronoi diagram has the general shape but it is off in terms of position as the pixel coordinates are different from the actual world coordinates.
I tried to map it using imref2d but I do not know how it really works or how to display the image after using imref2d. Please help me on this.
I am open to other methods too!
Thank you!
Edit:
As requested, let me give a more detailed example and explanation of my problem.
Let us assume a simple diamond shape boundary with the following vectors and 4 points with the following coordinate vectors:
%Boundary vectors
Boundary_X = [-5 40 85 40 -5];
Boundary_Y = [20 50 20 -10 20];
%Point vectors
Group_One_X = [20 30];
Group_One_Y = [10 40];
Group_Two_X = [50 70];
Group_Two_Y = [5 20];
Next I plot all of them, with different groups having different colours.
%Plot boundary and points
hold on
plot(Boundary_X,Boundary_Y)
scatter(Group_One_X,Group_One_Y,10,'MarkerFaceColor','Black',...
'MarkerEdgeColor','Black')
scatter(Group_Two_X,Group_Two_Y,10,'MarkerFaceColor','Red',...
'MarkerEdgeColor','Red')
hold off
axis([-10, 90, -20, 60])
This is the result:
Boundary with points
Next I test the whole graph area pixel by pixel, and colour them either cyan or yellow depending on whether they are closer to group 1 or 2 points.
%Create pixel vector with default white colour
C=ones(100,80,3);
Colour_One = [0 1 1];
Colour_Two = [1 1 0];
%Loop through whole diagram
for i=1:100
for j=1:80
x=i;
y=j
if inpolygon(x,y,Boundary_X,Boundary_Y)
%Code for testing which point is pixel closest to
%If closest to group 1, assign group 1 colour, else group 2
%colour
end
end
end
%Display image
hold on
imagesc(C)
hold off
This is the result
Failed Voronoi Diagram
The shape is somewhat correct for the right side but not for the others. I understand that this because my world coordinates start from negative values but the pixel coordinates start from 1.
Hence I am at a lost as to how can I solve this problem.
Thank you!
One thing to notice is that you have to convert between image and plot coordinates. The plot coordinates are (x,y) where x goes to the right, and y goes up. The matrix coordinates are (i,j) where i goes down, and j to the right. An easy way to do this is with the use of vec_X,vec_Y as shown below.
Another solution for the newer Matlab versions would be - as you said - using imref2d but unfortunately I have no experience with that command.
%Boundary vectors
Boundary_X = [-5 40 85 40 -5];
Boundary_Y = [20 50 20 -10 20];
%Point vectors
Group_One_X = [20 30];
Group_One_Y = [10 40];
Group_Two_X = [50 70];
Group_Two_Y = [5 20];
%Coordinate system
min_X = -10;
max_X = 90;
min_Y = -20;
max_Y = 60;
axis([min_X, max_X, min_Y, max_Y])
%Create pixel vector with default white colour
rows_N = 100;
columns_N = 80;
C=ones(rows_N,columns_N,3);
%These vectors say where each of the pixels is in the plot coordinate
%system
vec_X = linspace(min_X,max_X,columns_N);
vec_Y = linspace(min_Y,max_Y,rows_N);
Colour_One = [0 1 1];
Colour_Two = [1 1 0];
%Loop through whole diagram
for i=1:100
for j=1:80
if inpolygon(vec_X(j),vec_Y(i),Boundary_X,Boundary_Y)
%calculate distance to each point
Distances_One = zeros(size(Group_One_X));
Distances_Two = zeros(size(Group_Two_X));
for k=1:numel(Group_One_X);
Distances_One(k) = norm([Group_One_X(k),Group_One_Y(k)]-[vec_X(j),vec_Y(i)]);%assuming euclidean norm, but can be adjusted to whatever norm you need
end
for k=1:numel(Group_Two_X);
Distances_Two(k) = norm([Group_Two_X(k),Group_Two_Y(k)]-[vec_X(j),vec_Y(i)]);%assuming euclidean norm, but can be adjusted to whatever norm you need
end
if min(Distances_One) < min(Distances_Two);
C(i,j,:) = Colour_One;
else
C(i,j,:) = Colour_Two;
end
end
end
end
%Display image
imagesc(vec_X,vec_Y,C) %lets you draw the image according to vec_X and vec_Y
%Plot boundary and points
hold on
plot(Boundary_X,Boundary_Y)
scatter(Group_One_X,Group_One_Y,10,'MarkerFaceColor','Black',...
'MarkerEdgeColor','Black')
scatter(Group_Two_X,Group_Two_Y,10,'MarkerFaceColor','Red',...
'MarkerEdgeColor','Red')
hold off

How to plot an n-points discrete segment between two given points in Matlab?

Given two points, what's the best way to plot - in Matlab - a n-points discrete segment that has these points as extremes?
What if I have to plot a chain of discrete segments like that?
Thank you in advance!
The following is an example of what I'm trying to achieve in the easiest possible way
Assuming your points are stored in the fashion p = [35,0; 40,0.2; 45,0], i.e.
p =
35.0000 0
40.0000 0.2000
45.0000 0
Then you can create an array for all x values by finding the mininum and maximum values of the x coordinate. Here, the x coordinate is the first column of p, i.e. p(:,1). You can use the colon operator : to create the x array by
x = min(p(:,1)) : 1 : max(p(:,1))
The 1 in the middle is the step width. For your example, this will create the array
x =
35 36 37 38 39 40 41 42 43 44 45
Now you can interpolate all y value linearly with the interp1 function. This does a linear interpolation by default. The syntax is thus
y = interp1(p(:,1), p(:,2), x)
Finally you can plot the vectors x and y using plot. If you only want to print circles, use 'o' as LineSpec specifier. To connect the circles using a line, use '-o'. There's an extensive list of options here. You can also add the color of the line / markers to this format spec. Black circles would be 'ok':
plot(x, y, 'ok')

How do I generate pair of random points in a circle using Matlab?

Let a circle of known radius be plotted in MATLAB.
Assume a pair of random points whose location has to be determined in terms of coordinates (x1,y1) (x2,y2)..(xn,yn). Pairs should be close to each other. For example T1 and R1 should be near.
As shown in figure, there are four random pairs (T1,R1)..(T4,R4).
There coordinates need to be determined wrt to center (0,0).
How can I generate this in MATLAB?
The simplest approach to pick a point from a uniform distribution over a circle with reduce R is using Gibbs sampling. Here is the code:
function [x y] = circular uniform (R)
while true
x = 2*R*rand() - R
y = 2*R*rand() - R
if (x*x + y*y) > R*R
return
end
end
The loop runs 4/π times on average.
(Complete edit after the question was edited).
To complete this task, I think that you need to combine the different approaches that have been mentioned before your edit:
To generate the centers T1,T2,T3,... in the green torus, use the polar coordinates. (Edit: this turned out to be wrong, rejection sampling must also be used here, otherwise, the distribution is not uniform!)
To generate the points R1,R2,R3,... in the circle around T1,T2,T3,... but still in the torus, use the rejection sampling.
With these ingredients, you should be able to do everything you need. Here is the code I wrote:
d=859.23;
D=1432.05;
R=100;
N=400;
% Generate the angle
theta = 2*pi*rand(N,1);
% Generate the radius
r = d + (D-d)*rand(N,1);
% Get the centers of the circles
Tx = r.*cos(theta);
Ty = r.*sin(theta);
% Generate the R points
Rx=zeros(N,1);
Ry=zeros(N,1);
for i=1:N
while true
% Try
alpha = 2*pi*rand();
rr = R*rand();
Rx(i) = Tx(i) + rr*cos(alpha);
Ry(i) = Ty(i) + rr*sin(alpha);
% Check if in the correct zone
if ( (Rx(i)*Rx(i) + Ry(i)*Ry(i) > d*d) && (Rx(i)*Rx(i) + Ry(i)*Ry(i) < D*D) )
break
end
end
end
% Display
figure(1);
clf;
angle=linspace(0,2*pi,1000);
plot( d*cos(angle), d*sin(angle),'-b');
hold on;
plot( D*cos(angle), D*sin(angle),'-b');
for i=1:N
plot(Tx(i),Ty(i),'gs');
plot(Rx(i),Ry(i),'rx');
plot([Tx(i) Rx(i)],[Ty(i) Ry(i)],'-k');
end
hold off;
let R be radious of (0;0) centered circle.
(x,y) : x^2+y^2<=R^2 (LE) to be inside the circle
x = rand()*2*R - R;
y should be in interval (-sqrt(R^2 - x^2);+sqrt(R^2 - x^2))
so, let it be
y = rand()*sqrt(R^2 - x^2)*2-sqrt(R^2 - x^2);
Hope, that's right, i have no matlab to test.
Hope, you'll manage to find close pairs your self.
Ok, i'll spend a bit more time for a hint.
To find a random number k in interval [a,b] use
k = rand()*(b-a)+a
Now it should really help if i still remember the matlab syntaxis. Good luck.
Here is a low quality solution that is very easy to use with uniformly distributed points. Assuming the number of points is small efficiency should not be a concern, if you want better quality you can use something more powerfull than nearest neighbor:
While you have less than n points: Generate a random point
If it is in the circle, store it else go to step 1
While there are unpaired points: check which point is nearest to the first unpaired point, make them a pair
As a result most pairs should be good, but some can be really really bad. I would recommend you to try it and perhaps add a step 4 with k-opt or some other local search if required. And if you really have little points (e.g. less than 20) you can of course just calculate all distances and find the optimum matching.
If you don't really care about the uniform distribution, here is an even easier solution:
While you have less than n points: Generate a random point
If it is in the circle, store it else go to step 1
For each of these points, generate a point near it
If it is in the circle, store it else go to step 3
Generate the random points as #PheuVerg suggested (with a slight vectorized tweak)
n = 8; %must be even!
x = rand(n, 1)*2*R - R;
y = rand(n, 1).*sqrt(R^2 - x.^2).*2-sqrt(R^2 - x.^2);
Then use kmeans clustering to get n/2 centers
[~ c] = kmeans([x y], n/2);
now you have to loop through each center and find it's distance to each point
dists = zeros(n, n/2);
for cc = 1:n/2
for pp = 1:n
dists(pp, cc) = sqrt((c(cc,1) - x(pp))^2 + (c(cc,2) - y(pp))^2);
end
end
now you must find the smallest 2 values for each columns of dists
[sorted, idx] = sort(dists);
so now the top two rows of each column are the two nearest points. But there could be clashes! i.e. points that are nearest to two different centers. So for repeated values you have to loop through and choose swap for the point that will give you the smallest extra distance.
Example data:
x =
0.7894
-0.7176
-0.5814
0.0708
0.5198
-0.2299
0.2245
-0.8941
y =
-0.0800
-0.3339
0.0012
0.9765
-0.4135
0.5733
-0.1867
0.2094
sorted =
0.1870 0 0 0.1555
0.2895 0.5030 0.5030 0.2931
0.3145 1.1733 0.6715 0.2989
1.0905 1.1733 0.7574 0.7929
1.1161 1.2326 0.8854 0.9666
1.2335 1.2778 1.0300 1.2955
1.2814 1.4608 1.2106 1.3051
1.4715 1.5293 1.2393 1.5209
idx =
5 4 6 3
7 6 4 2
1 3 3 8
6 7 8 6
3 8 7 7
2 1 2 4
4 5 1 5
8 2 5 1
So now it's clear that 5 and 7 are pairs, and that 3 and 2 are pairs. But 4 and 6 are both repeated. (in this case it is clear that they are pairs too I guess!) but what I would suggest is to leave point 4 with center 2 and point 6 with center 3. Then we start at column 2 and see the next available point is 8 with a distance of 1.2326. This would leave point 1 paired with point 6 but then it's distance from the center is 1.2106. Had we paired point 6 with 8 and point 4 with 1 we would have got distances of 0.7574 and 1.2778 respectively which is actually less total distance. So finding 'close' pairs is easy but finding the set of pairs with the globally smallest minimum is hard! This solutions gets you something decent quite easily but fi you need the global best then I'm afraid you have quite a bit of work to do still :(
Finally let me add some visualisation. First lets (manually) create a vector that shows which points are paired:
I = [1 2 2 1 3 4 3 4];
Remember that that will depend on your data! Now you can plot is nicely like this:
gscatter(x, y, I)
Hope this gets you close and that you can eliminate the manual pairing of mine at the end by yourself. It shouldn't be too hard to get a crude solution.