8

I have a binary image of one granule in Matlab. I can find the convex hull of the granule with the following function:

[K, V] = convhull(granule);

How can I find all pixels which belong to the convex hull, but don't belong to the granule in the original image? I mean I'd like to do something like that:

granule2 = zeros(size(granule));
granule2(K == 1 & granule == 0) = 2;

It doesn't work, because K is of size x by 3, where x is the number of triangles in the convex hull.

Edit: according to the documentation, the convex hull should be an array with the indexes of points making up the convex hull in each row. So how can I find all points which are inside of the volume determined by these points.

Edit2: Let me put it in another words: I have an image which is a 3D array of points. It's not a sphere and it has some indents (so the convex hull doesn't lay on the surface of my image).

I want to find the convex hull and after that find all points which are inside the convex hull, but are outside the granule. Here is how it would look like in 2D (I want to find the red pixelsenter image description here):

Edit3: NicolaSysnet, Your algorithm should return all the pixels (their indexes) which are red in my picture (the picture is in 2D,because it was easier to draw). enter image description here

user2738748
  • 1,106
  • 2
  • 19
  • 36
  • What's the size of `granule`? – Divakar Oct 05 '15 at 21:48
  • 2
    You are using the `[K,V]` exactly opposite of what the [documentation](http://mathworks.com/help/matlab/ref/convhull.html) says. I assume this is wrong in your code as well, since `K==1` is a rather obscure assignment here – Adriaan Oct 05 '15 at 21:50
  • Yes, sorry, I was wrong about the order of the vaariables returned by the fuction. The size of granule is 1024 by 1024 by 1024. – user2738748 Oct 06 '15 at 10:47
  • I think you are not after pixels but after vertices, correct? That is you are after all vertices that defines the facets of the hull and that don't touch your granule? – gregswiss Oct 13 '15 at 00:00
  • IMO unless you also have boundary of your granule (ie the surface) the problem is ill-defined... – gregswiss Oct 13 '15 at 00:18
  • you refer to granule as an 'image' and then you say it's 3d data? which is it? 'convhull' takes vertices as input, not images. – gregswiss Oct 13 '15 at 00:27
  • No, I am after the pixels. It's very easy to get the vertices - this is what the convhull function returns. What do you mean in your second comment, gregwiss? I don't have an explicit boundary of my granule in my code, but I can create it with the edge function. But how does it help? – user2738748 Oct 13 '15 at 12:40
  • My granule is an image (for me an image can also be 3D, but maybe it's not the best name) which is 3D (the exact size is 1024 by 1024 by 1024 pixels). "Convhull" can take also a set of points. This is a quotation from the documentation K = convhull(X) returns the 2-D or 3-D convex hull of the points X.This variant supports the definition of points in matrix format. X is of size mpts-by-ndim, where mpts is the number of points and ndim is the dimension of the space where the points reside, 2 ≦ ndim ≦ 3.The output facets are equivalent to those generated by the 2-input or 3-input calling syntax. – user2738748 Oct 13 '15 at 12:42
  • 1
    @user2738748 you're question is very confusing. You appear to want the pixels between the convex hull and the original shape? The answer is completely dependent on things like screen resolution, elevation and azimuth of the graph. Since pixels are always 2D you're request for 3D makes no sense. If you in fact want 3D points between the hull and your shape the answer is `inf`. If you specify a min gap between points, ie 3D grid size then the answer is bounded. – Matt Oct 19 '15 at 14:48
  • @Matt, when I say "picture", I don't mean a png picture. I have a 3D array of points that I want to analyse. I made the picture in 2D, because it is simpler, but I want to analyse my data in 3D. – user2738748 Oct 20 '15 at 08:01
  • 1
    @user2738748 you didn't clarify anything. There are an infinite number of points between the surface enclosed by the convex hull and the actual surface. You're asking for an unbounded answer. Take your edit3 for example. What indices? The points aren't on the granule so they have no indices. They are points between the hull and the granule in empty space. Are you asking for all the points on the granule that are not on the hull? You need more clarification. – Matt Oct 20 '15 at 14:15
  • 1
    @Matt, my input is an 3D array, let's say, of the size 1024 by 1024 by 1024. Each point has three indices: x, y, z. The range of the indices if from 1 to 1024. Each point also has a value - if it's 0, it doesn't belong to the granule, if it is 1, it does. All points have three indices, not only those on the granule (or inside it). When I say a "point", I don't mean a point on a plane (a mathematical point), but a point from my original array (and I have over a billion such points - this is a finite number). I assumed that is clear for everone who knows Matlab. – user2738748 Oct 20 '15 at 20:56

4 Answers4

3
[K, V] = convhull(granule);
granule2 = zeros(size(granule));
tmp = granule(K,:)==0; %// all points on the convex hull but not in the granule
tmp2 = tmp(:,1)==tmp(:,2); %// both indices of your granule are zero
granule2(tmp(tmp2)) = 2;

K are the row numbers of your points corresponding to the points on the convex hull, V is just the volume spanned by that convex hull. Thus, you can use this row index to find your zero indices in granule.

Using the below example:

granule = rand(1e3,2);
[K, V] = convhull(granule);
granule2 = zeros(size(granule));
tmp = granule(K,:)<0.1; %// all points on the convex hull but not in the granule
tmp2 = tmp(:,1)==tmp(:,2); %// both indices of your granule are below 0.1
granule2(tmp(tmp2)) = 2;

results in sum(tmp2)=11, thus there are 11 points in this case which are on the convex hull and have both indices below 0.1 (I could not use ==0 since there are no zeros in my array).

You might want to switch up the condition for tmp2 based on what you actually want for it.

Unsurprisingly more people have struggled with this and actually written code for this, see the MathWorks Central, code by John D'Errico.

Adriaan
  • 17,741
  • 7
  • 42
  • 75
  • Thanks of your answer. But could you explain in more details why I need the tmp2 variable? The tmp variable seems to do exactly what I want. – user2738748 Oct 06 '15 at 10:52
  • `tmp` is a 2D array of logicals, `tmp2` is a 1D array, where *both* entries of each row in `tmp` are true. Since you posted no data for me to work with I was not sure which you needed, thus I used it as such for more versatility and added the note that you might want to switch `tmp2` to whatever you need. – Adriaan Oct 06 '15 at 12:48
  • Ok, but why do I need to execute this line: tmp2 = tmp(:,1)==tmp(:,2);? Isn't it redundant? I don't see the point. – user2738748 Oct 06 '15 at 16:14
  • that line makes sure that `tmp2` gets filled with values satisfying the condition for both columns, thus making it a 1D array. `tmp` is a 2D array satisfying the condition per value and not per row. So it could be redundant, depending on what your data and condition is. Since you have still not posted those I have no clue what to expect from your data and what you actually want to achieve with this condition. I added this purely out of versatility in the answer. – Adriaan Oct 06 '15 at 16:28
  • My data is of size (1024)^3 (it's in 3D). How can I post it? My granule is more or less elliptical, has some holes and indents. It takes up approximately 5% of the variable "granule". – user2738748 Oct 06 '15 at 16:59
  • Since K is of size x by 3, how can this line work? granule(K,:) == 0; Here K should be a 1D array, shouldn't it? And what do you mean by " both indices of your granule are zero"? Each point has three indices (or two, it doesn't matter), but they range from 1 to 1024. How can they be 0? – user2738748 Oct 06 '15 at 17:01
  • And what should I do if my image is 3D? I printed out the size of K in my case and its x by 3. So I guess your example isn't applicable to my problem. – user2738748 Oct 06 '15 at 17:29
  • That is very weird indeed. If I set my `granule` to 3D `k` becomes 3D as well. No idea what MathWorks intended with this... I'll investigate – Adriaan Oct 06 '15 at 17:32
  • It is 2D in my case (when my data is 3D). – user2738748 Oct 06 '15 at 18:09
  • I don't understand the answer personally - but then again the question is not clear either... what's the significance of the 0.1 threshold for example? – gregswiss Oct 13 '15 at 00:24
  • I don't see the link between checking to points are zero and the fact that they are on the granule or not? – gregswiss Oct 13 '15 at 06:01
  • @gregswiss me neither, I just added this for the OPs benefit so he could rearrange that line to fit his purposes (i.e. `==0`). I think he has a dimpled ball or something and want to know whether all points given by `convhull` are on the ball itself, but they are by definition, otherwise `convhull` would not have found those. – Adriaan Oct 13 '15 at 06:04
  • exactly! re your question in the comment (about K being n x 3) it makes perfect sense because the convex hull in 3d is defined by a set of facets. Each row of the output K defines a facets (each element of the row point to a vertex in the original input array). – gregswiss Oct 13 '15 at 06:07
3

This should work:

ix=find(granule);
[x,y,z]=ind2sub(size(granule),ix);
K=convhulln([x,y,z]);
% take the index of the points on the frontier
front=unique([K(:,1);K(:,2);K(:,3)]);

% calculate the minimum box containing the whole granule
minx=min(x(front));
maxx=max(x(front));
miny=min(y(front));
maxy=max(y(front));
minz=min(z(front));
maxz=max(z(front));
% Make a mesh for the points of this box
[X, Y, Z]=meshgrid(minx:maxx,miny:maxy,minz:maxz);
X=X(:);Y=Y(:);Z=Z(:);
% Remove the points of the granule from the box
ind=find(granule(sub2ind(size(granule),X,Y,Z))==0);
X=X(ind);Y=Y(ind);Z=Z(ind);

% For every face
for face=1:length(K),
    % take the coordinates of the vertices of the face
    P1=[x(K(face,1)), y(K(face,1)), z(K(face,1))];
    P2=[x(K(face,2)), y(K(face,2)), z(K(face,2))];
    P3=[x(K(face,3)), y(K(face,3)), z(K(face,3))];

    % calculate the normal to the face (croos product of two vectors on the face, 
    % we take the two sides of the face using the first point as pivot)
    normal=cross(P2-P1,P3-P1);
    % take another point on the convex hull
    inner=1;
    direction=0;
    while direction==0,
        P4=[x(inner), y(inner), z(inner)];

        % calculate the projection of the other point on the normal (always using P1 as pivot) 
        % to verify which is the sign for the projection of the points inside the convex hull
        direction=dot(P4-P1,normal);
        inner=inner+1;
    end
    % do the same for all the remaining points of the mesh
    project=sum(([X,Y,Z]-repmat(P1,size(X,1),1)).*repmat(normal,size(X,1),1),2);
    % if the sign of the projection is different to the one of the test point, 
    % the point is outside the convex hull so remove it
    ind=find(sign(project)*sign(direction)>=1);
    X=X(ind);Y=Y(ind);Z=Z(ind);
    plot3(X,Y,Z,'.')
end

What this code does is verifying, for every face, that every point in a rectangular mesh lays on the same side of this face of the convex hull.

To speed-up calculations the rectangular mesh is made only for the coordinates that touch the granule projection on the three coordinate planes, in this way, if the granule occupy the 5% of the area you can cut 90-95% of the points to check, without calculations.

NicolaSysnet
  • 486
  • 2
  • 10
  • Thanks, this is a good idea. But what about points which are inside the granule, not on its surface? – user2738748 Oct 20 '15 at 06:35
  • The mesh generated in `[X, Y, Z]=meshgrid(minx:maxx,miny:maxy,minz:maxz);` comprises both the points inside the surface and all the points outside the surface that are contained in a box containing the whole granule. So the points inside the granule are accounted for. – NicolaSysnet Oct 20 '15 at 07:25
  • @user2738748 I tried this algorithm on a cube cut by a sfere in one of its vertices, and it gave me, as a result, the intersection of the cutting sfere with the cube (without the tetrahedron starting from the cut out vetex). I thought that was what you requested. – NicolaSysnet Oct 20 '15 at 07:36
  • Is the intersection concave? Could you please post some pictures. In your case I'd expect to get the points which don't belong to the cut cube, but are between the cut cube and a plane which creates the convex hull. You shouldn't get all points which belonged to the original cube. – user2738748 Oct 20 '15 at 07:56
  • I posted a picture of what I expect of your cube. – user2738748 Oct 20 '15 at 08:05
  • @user2738748 It is exactly its output. In X,Y,Z you will find the indexes in the 1024X1024X1024 matrix that you highlighted in red. – NicolaSysnet Oct 20 '15 at 08:16
  • Thanks a lot! This is exactly what I was looking for. – user2738748 Oct 20 '15 at 08:22
1

What you need to do is use convhull to generate the watertight surface of your hull and then I see two options:

  • For each point in your 1024^3 grid check if it's inside or outside the hull - maybe using a 'in polyherdron test' (such as this)

  • Alternatively rather than testing on every voxel (3d pixel), you could potentially voxelise (ie rasterise) your convex hull - that is transforming this watertight surface into a set of voxel. An option for that is available here.

Once you know which points are within the convex hull you can easily remove the points from the granule.

gregswiss
  • 1,456
  • 9
  • 20
  • 1
    Checking if every voxel is inside my convex hull isn't a good idea - I have over 50 billion such voxels. And your second link doesn't work. – user2738748 Oct 18 '15 at 21:05
  • sorry for the broken link should be fixed. – gregswiss Oct 19 '15 at 00:00
  • 1
    also you don't need to test the points that belong to the granule that should eliminate a fair number... lots of points... flood filling should work ok. Would be interested to see your solution! – gregswiss Oct 19 '15 at 00:10
  • What do you mean by flood filling? I can't use the imfill function, because the convex hull is a set of not connected points. – user2738748 Oct 20 '15 at 06:38
  • @user2738748 you'd need to first convert (rasterise) your convex hull to a set of connected points before using flood filling - the output of which would be a 3d grid of logical values (onhull/outside hull). The second issue is that imfill will work only in 2d. It looks like the 'voxeliser' I pointed out in my answer handles both of these. – gregswiss Oct 20 '15 at 23:29
  • The advantage of the technique is the algorithm computational complexity. In particular all faces of the hull need to be visited only once. – gregswiss Oct 20 '15 at 23:31
0

Currently working on a similar question, I found a function on the Matlab Exchange platform called inhull, that finds if some test points are inside or outside a given convex hull.

It works great but the time processing might be pretty long if the number of points to test is big though...

Eskapp
  • 3,419
  • 2
  • 22
  • 39