I would like to extract the 3D cubes (3x3x3) from a boolean 3D array of (180x180x197). This is similar to scipy.ndimage.measurements.label but need to fixed size of (3x3x3). Is there a fast way of doing this than using for loops.
-
Recent similar topic: http://stackoverflow.com/questions/39429900/split-a-3d-numpy-array-into-3d-blocks – hpaulj Sep 11 '16 at 16:18
2 Answers
In your special case, I suggest to use ndimage.minimum_filter Let's say your array is called ``a''. The following:
centers = ndimage.minimum_filter(a, 3, mode="constant")
will only contain ones where your array contained such box of True's Then you can use scipy.ndimage.measurements.label with the default structure to classify the boxes and maybe identify connected boxs. To locate them you can use ndimage.measurements.find_objects
edit:
The way above, you will correctly get the centers of all cubes in the array. To be clear, I think that is the answer to your initial question. In the comments, it turned out, that indeed only non-overlapping cubes are needed. Therefore, one needs to analyze the output of minimum_filter, where I can imagine many approaches.
One can use the following to obtain only one cube per cluster:
s = ndimage.generate_binary_structure(3,3)
labels, num = ndimage.measurements.label(centers, s)
locations = ndimage.measurements.find_objects(labels)
locations = map(lambda slices: [slices[i].start for i in xrange(3)], locations)
Now the problem occurs that cubes are lost which are not overlapping but share a face. Actually one can imagine quite complicated structures of non-overlapping, facesharing cubes. Certainly, there are several solutions (sets of non-overlapping cubes) that can be found for this problem. So it is a completely new task to choose a set of cubes from the found centers and I think you will have to find one which is ideal for you.
One way could be to iterate through all solutions and set each found cube to False:
get_starting_point = numpy.vectorize(lambda sl: sl.start) #to be applied on slices
s = ndimage.generate_binary_structure(3,3)
result = []
while True:
labels, num = ndimage.measurements.label(centers, s)
if not num:
break
locations = ndimage.measurements.find_objects(labels)
sp = get_starting_point(locations)
result.append(sp)
for p in sp:
centers[p[0]-1:p[0]+2, p[1]-1:p[1]+2, p[2]-1:p[2]+2] = False
numiter = len(results)
results = numpy.vstack(results)
I guess only very few iterations will be necessary.
I hope this is what you were looking for

- 974
- 4
- 14
-
I tried this, however, this does not do what I need to do. I need to find the 3x3x3 cubes from the set of True values. And once a cube is identified, those True values cannot be in the next cube. And the cubes have to be separately labeled. I was not able to identify the way to do this. Thanks for the help/. – ssm Sep 12 '16 at 17:42
-
Well if I understand right, it does almost what you want but additionally you need non-overlapping cubes. What do you want to get then if 2 or more cubes overlap? Which one is a true cube and which is not? The above gives you a True for each center of a cube. If cubes are overlapping, the maximum distance of these centers is then less then 3. As long as you don't specify what to do with overlapping cubes, you can at least identify them by using a uniform_filter. – dnalow Sep 12 '16 at 18:29
-
If a set of cells belong to a cube they cannot belong to any other cube. Say if you have 3x3x5 Trues in a place. I need it to show only 1 3x3x3 cube (can be any of the two) and discard the other. – ssm Sep 12 '16 at 19:01
-
I really think all you need is mentioned in the answer. If you use the scipy.ndimage.measurements.label with a 3x3 cube of True's kind of structure (`ndimage.generate_binary_structure(3,3)`) you will get a new label for each non-overlapping cube. Using further ndimage.measurements.find_objects, you will find the locations (slices) of these labels. Now you just have to pick one nd-index of these (e.g. the first). This will be the center location of a cube and discard all overlapping ones. – dnalow Sep 12 '16 at 19:20
-
Hi, But say you have 3x3x6 block of True values , I need it to produce 2 cubes. I don't think your proposed method would be able to do that. Or can it? if so please advice me on the steps. – ssm Sep 12 '16 at 22:17
-
Ok, if the cubes are not having contact, it works. If they overlap it works, but if they share a face, then it might not give you what you want (2cubes) but only one of them.. i will edit the answer to discuss the different cases later... – dnalow Sep 13 '16 at 09:51
-
Thanks a lot. i had to add the modify the code slightly to get what I want. But thanks a lot for the help I will add an extra answer with the complete solution. – ssm Sep 13 '16 at 14:54
Final solution with help from dnalow
get_starting_point = numpy.vectorize(lambda sl: sl.start)
s = ndimage.generate_binary_structure(3,3)
result = []
while True:
centers = ndimage.minimum_filter(b, 3, mode="constant")
labels, num = ndimage.measurements.label(centers, s)
if not num:
break
locations = ndimage.measurements.find_objects(labels)
sp = get_starting_point(locations)
for p in sp:
if numpy.all(a[p[0]-1:p[0]+2, p[1]-1:p[1]+2, p[2]-1:p[2]+2]):
result.append(p)
b[p[0]-1:p[0]+2, p[1]-1:p[1]+2, p[2]-1:p[2]+2] = False
numiter = len(result)
results = numpy.vstack(result)

- 620
- 6
- 24