From 6f771cda95fee80991f648a69a934d38ee265872 Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Thu, 27 Apr 2017 09:29:03 -0700 Subject: [PATCH 01/14] added shapes and changed border treatment --- brainiak/searchlight/searchlight.py | 27 +++++++++++++++++++-- examples/searchlight/example_searchlight.py | 3 ++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index e5f7fdf9d..e58c1ac43 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -15,6 +15,7 @@ import numpy as np from mpi4py import MPI import sys +from scipy.spatial.distance import cityblock """Distributed Searchlight """ @@ -23,6 +24,21 @@ "Searchlight", ] +class Cube: + def __init__(self, rad): + self.rad = rad + self.data = np.ones((2*rad+1,2*rad+1,2*rad+1), dtype=np.bool) + +class Diamond: + def __init__(self, rad): + self.rad = rad + self.data = np.zeros((2*rad+1,2*rad+1,2*rad+1), dtype=np.bool) + for r1 in range(2*self.rad+1): + for r2 in range(2*self.rad+1): + for r3 in range(2*self.rad+1): + if(cityblock( (r1,r2,r3) , (self.rad, self.rad, self.rad) ) <= self.rad): + self.data[r1,r2,r3] = True + print(self.data) class Searchlight: """Distributed Searchlight @@ -33,7 +49,7 @@ class Searchlight: Optionally, users can define a block function which runs over larger portions of the volume called blocks. """ - def __init__(self, sl_rad=1, max_blk_edge=10): + def __init__(self, sl_rad=1, max_blk_edge=10, shape=Cube): """Constructor Parameters @@ -48,6 +64,7 @@ def __init__(self, sl_rad=1, max_blk_edge=10): self.sl_rad = sl_rad self.max_blk_edge = max_blk_edge self.comm = MPI.COMM_WORLD + self.shape = shape(sl_rad).data def _get_ownership(self, data): """Determine on which rank each subject currently resides @@ -345,6 +362,10 @@ def _singlenode_searchlight(l, msk, mysl_rad, bcast_var): mysl_rad:-mysl_rad, mysl_rad:-mysl_rad] + def check_mask(mask_cube): + res = mask_cube[self.shape] + return np.any(res) and np.all(res) + import pathos.multiprocessing inlist = [([ll[i:i+2*mysl_rad+1, j:j+2*mysl_rad+1, @@ -356,7 +377,9 @@ def _singlenode_searchlight(l, msk, mysl_rad, bcast_var): k:k+2*mysl_rad+1], mysl_rad, bcast_var) - if msk[i+mysl_rad, j+mysl_rad, k+mysl_rad] else None + if check_mask(msk[i:i+2*mysl_rad+1, + j:j+2*mysl_rad+1, + k:k+2*mysl_rad+1]) else None for i in range(0, outmat.shape[0]) for j in range(0, outmat.shape[1]) for k in range(0, outmat.shape[2])] diff --git a/examples/searchlight/example_searchlight.py b/examples/searchlight/example_searchlight.py index 2da316b4f..ca29435f5 100644 --- a/examples/searchlight/example_searchlight.py +++ b/examples/searchlight/example_searchlight.py @@ -17,6 +17,7 @@ import sys from brainiak.searchlight.searchlight import Searchlight +from brainiak.searchlight.searchlight import Diamond """Distributed Searchlight Example example usage: mpirun -n 4 python3 example_searchlight.py @@ -66,7 +67,7 @@ data[pt[0]:pt[0]+kernel_dim,pt[1]:pt[1]+kernel_dim,pt[2]:pt[2]+kernel_dim,idx] -= kernel * weight # Create searchlight object -sl = Searchlight(sl_rad=1, max_blk_edge=5) +sl = Searchlight(sl_rad=1, max_blk_edge=5, shape=Diamond) # Distribute data to processes sl.distribute([data], mask) From 1350f1161a7af37b7b4659348ebf091f7861cfba Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Thu, 27 Apr 2017 10:23:00 -0700 Subject: [PATCH 02/14] fixed for pr-check --- brainiak/searchlight/searchlight.py | 39 +++++++++++++++++++++++---- tests/searchlight/test_searchlight.py | 3 ++- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index e58c1ac43..a5feba853 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -24,21 +24,50 @@ "Searchlight", ] + class Cube: + """Cube + + Searchlight shape which is a (2*rad+1)^3 cube + """ def __init__(self, rad): + """Constructor + + Parameters + ---------- + + rad: radius, in voxels, of the sphere inscribed in the + searchlight cube, not counting the center voxel + + """ self.rad = rad - self.data = np.ones((2*rad+1,2*rad+1,2*rad+1), dtype=np.bool) + self.data = np.ones((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) + class Diamond: + """Diamond + + Searchlight shape which is a (2*rad+1)^3 diamond + """ def __init__(self, rad): + """Constructor + + Parameters + ---------- + + rad: radius, in voxels, of the sphere inscribed in the + searchlight cube, not counting the center voxel + + """ self.rad = rad - self.data = np.zeros((2*rad+1,2*rad+1,2*rad+1), dtype=np.bool) + self.data = np.zeros((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) for r1 in range(2*self.rad+1): for r2 in range(2*self.rad+1): for r3 in range(2*self.rad+1): - if(cityblock( (r1,r2,r3) , (self.rad, self.rad, self.rad) ) <= self.rad): - self.data[r1,r2,r3] = True - print(self.data) + if(cityblock((r1, r2, r3), + (self.rad, self.rad, self.rad)) <= self.rad): + self.data[r1, r2, r3] = True + class Searchlight: """Distributed Searchlight diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py index 85a6e26ae..250a1176b 100644 --- a/tests/searchlight/test_searchlight.py +++ b/tests/searchlight/test_searchlight.py @@ -78,7 +78,8 @@ def sfn(l,msk,myrad,bcast_var): for d0 in range(rad, global_outputs.shape[0]-rad): for d1 in range(rad, global_outputs.shape[1]-rad): for d2 in range(rad, global_outputs.shape[2]-rad): - if mask[d0, d1, d2]: + msk_blk = mask[d0:d0+2*rad+1, d1:d1+2*rad+1, d2:d2+2*rad+1] + if np.all(msk_blk) and np.any(msk_blk): assert np.array_equal(np.array(global_outputs[d0,d1,d2]), np.array([d0,d1,d2])) From 160914926505c71434967cfe749d34b70ebdd1ac Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Fri, 28 Apr 2017 06:52:40 -0700 Subject: [PATCH 03/14] Added test for shapes --- brainiak/searchlight/searchlight.py | 8 ++- tests/searchlight/test_searchlight.py | 1 - tests/searchlight/test_shapes.py | 80 +++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 tests/searchlight/test_shapes.py diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index a5feba853..26b1f2eac 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -78,7 +78,7 @@ class Searchlight: Optionally, users can define a block function which runs over larger portions of the volume called blocks. """ - def __init__(self, sl_rad=1, max_blk_edge=10, shape=Cube): + def __init__(self, sl_rad=1, max_blk_edge=-1, shape=Cube): """Constructor Parameters @@ -91,9 +91,13 @@ def __init__(self, sl_rad=1, max_blk_edge=10, shape=Cube): """ self.sl_rad = sl_rad - self.max_blk_edge = max_blk_edge + if max_blk_edge == -1: + self.max_blk_edge = 3 * sl_rad + else: + self.max_blk_edge = max_blk_edge self.comm = MPI.COMM_WORLD self.shape = shape(sl_rad).data + self.bcast_var = None def _get_ownership(self, data): """Determine on which rank each subject currently resides diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py index 250a1176b..6c4c8275c 100644 --- a/tests/searchlight/test_searchlight.py +++ b/tests/searchlight/test_searchlight.py @@ -136,4 +136,3 @@ def do_test(dim0, dim1, dim2, ntr, nsubj, max_blk_edge, rad): do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=2) do_test(dim0=7, dim1=5, dim2=9, ntr=5, nsubj=1, max_blk_edge=4, rad=3) -test_correctness() diff --git a/tests/searchlight/test_shapes.py b/tests/searchlight/test_shapes.py new file mode 100644 index 000000000..90833d742 --- /dev/null +++ b/tests/searchlight/test_shapes.py @@ -0,0 +1,80 @@ +# Copyright 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from mpi4py import MPI +import sys + +from brainiak.searchlight.searchlight import Searchlight +from brainiak.searchlight.searchlight import Diamond + +def test_cube(): + sl = Searchlight(sl_rad=3) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50,50,50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0,dim1,dim2), dtype=np.bool) + data = [np.empty((dim0,dim1,dim2,ntr), dtype=np.object) if i % size == rank else None for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17,10:17,10:17] = True + + def sfn(l, msk, myrad, bcast_var): + return 1.0 + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(sfn) + + if rank == 0: + assert global_outputs[13,13,13] == 1.0 + global_outputs[13,13,13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i,j,k] == None + +def test_diamond(): + sl = Searchlight(sl_rad=3) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50,50,50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0,dim1,dim2), dtype=np.bool) + data = [np.empty((dim0,dim1,dim2,ntr), dtype=np.object) if i % size == rank else None for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17,10:17,10:17] = Diamond(3) + + def sfn(l, msk, myrad, bcast_var): + return 1.0 + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(sfn) + + if rank == 0: + assert global_outputs[13,13,13] == 1.0 + global_outputs[13,13,13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i,j,k] == None + + From 8879b0eec7df03fb6339d338d533e2a01412146e Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Fri, 28 Apr 2017 07:38:22 -0700 Subject: [PATCH 04/14] pr changes --- brainiak/searchlight/searchlight.py | 41 ++++++++++++++---- tests/searchlight/test_searchlight.py | 62 +++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index 26b1f2eac..3cf305a44 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -25,7 +25,25 @@ ] -class Cube: +class Shape: + """Shape + + Searchlight shape which is contained in a (2*rad+1)^3 cube + """ + def __init__(self, rad): + """Constructor + + Parameters + ---------- + + rad: radius, in voxels, of the sphere inscribed in the + searchlight cube, not counting the center voxel + + """ + self.rad = rad + + +class Cube(Shape): """Cube Searchlight shape which is a (2*rad+1)^3 cube @@ -40,14 +58,19 @@ def __init__(self, rad): searchlight cube, not counting the center voxel """ + super(Cube, self).__init__(rad) self.rad = rad self.data = np.ones((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) -class Diamond: +class Diamond(Shape): """Diamond - Searchlight shape which is a (2*rad+1)^3 diamond + Searchlight shape which is a (2*rad+1)^3 diamond. A diamond + is inscribed in the (2*rad+1)^3 cube. Any location in the cube + which has a Manhattan distance of less than rad from the center + point is set to True. + """ def __init__(self, rad): """Constructor @@ -59,7 +82,7 @@ def __init__(self, rad): searchlight cube, not counting the center voxel """ - self.rad = rad + super(Diamond, self).__init__(rad) self.data = np.zeros((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) for r1 in range(2*self.rad+1): for r2 in range(2*self.rad+1): @@ -78,7 +101,7 @@ class Searchlight: Optionally, users can define a block function which runs over larger portions of the volume called blocks. """ - def __init__(self, sl_rad=1, max_blk_edge=-1, shape=Cube): + def __init__(self, sl_rad=1, max_blk_edge=10, shape=Cube): """Constructor Parameters @@ -89,12 +112,12 @@ def __init__(self, sl_rad=1, max_blk_edge=-1, shape=Cube): max_blk_edge: max edge length, in voxels, of the 3D block + shape: brainiak.searchlight.searchlight.Shape indicating the + shape in voxels of the searchlight region + """ self.sl_rad = sl_rad - if max_blk_edge == -1: - self.max_blk_edge = 3 * sl_rad - else: - self.max_blk_edge = max_blk_edge + self.max_blk_edge = max_blk_edge self.comm = MPI.COMM_WORLD self.shape = shape(sl_rad).data self.bcast_var = None diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py index 6c4c8275c..3a0b6fb64 100644 --- a/tests/searchlight/test_searchlight.py +++ b/tests/searchlight/test_searchlight.py @@ -17,10 +17,72 @@ import sys from brainiak.searchlight.searchlight import Searchlight +from brainiak.searchlight.searchlight import Diamond """Distributed Searchlight Test """ + +def test_cube(): + sl = Searchlight(sl_rad=3) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50,50,50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0,dim1,dim2), dtype=np.bool) + data = [np.empty((dim0,dim1,dim2,ntr), dtype=np.object) if i % size == rank else None for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17,10:17,10:17] = True + + def sfn(l, msk, myrad, bcast_var): + return 1.0 + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(sfn) + + if rank == 0: + assert global_outputs[13,13,13] == 1.0 + global_outputs[13,13,13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i,j,k] == None + +def test_diamond(): + sl = Searchlight(sl_rad=3) + comm = MPI.COMM_WORLD + rank = comm.rank + size = comm.size + dim0, dim1, dim2 = (50,50,50) + ntr = 30 + nsubj = 3 + mask = np.zeros((dim0,dim1,dim2), dtype=np.bool) + data = [np.empty((dim0,dim1,dim2,ntr), dtype=np.object) if i % size == rank else None for i in range(0, nsubj)] + + # Put a spot in the mask + mask[10:17,10:17,10:17] = Diamond(3) + + def sfn(l, msk, myrad, bcast_var): + return 1.0 + + sl.distribute(data, mask) + global_outputs = sl.run_searchlight(sfn) + + if rank == 0: + assert global_outputs[13,13,13] == 1.0 + global_outputs[13,13,13] = None + + for i in range(global_outputs.shape[0]): + for j in range(global_outputs.shape[1]): + for k in range(global_outputs.shape[2]): + assert global_outputs[i,j,k] == None + + + def test_instantiate(): sl = Searchlight(sl_rad=5, max_blk_edge=10) assert sl From f017d9275196483f33c3fee47b766de2ec72f570 Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Fri, 28 Apr 2017 07:39:18 -0700 Subject: [PATCH 05/14] Removed test shapes --- tests/searchlight/test_shapes.py | 80 -------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 tests/searchlight/test_shapes.py diff --git a/tests/searchlight/test_shapes.py b/tests/searchlight/test_shapes.py deleted file mode 100644 index 90833d742..000000000 --- a/tests/searchlight/test_shapes.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright 2016 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -from mpi4py import MPI -import sys - -from brainiak.searchlight.searchlight import Searchlight -from brainiak.searchlight.searchlight import Diamond - -def test_cube(): - sl = Searchlight(sl_rad=3) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50,50,50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0,dim1,dim2), dtype=np.bool) - data = [np.empty((dim0,dim1,dim2,ntr), dtype=np.object) if i % size == rank else None for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17,10:17,10:17] = True - - def sfn(l, msk, myrad, bcast_var): - return 1.0 - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(sfn) - - if rank == 0: - assert global_outputs[13,13,13] == 1.0 - global_outputs[13,13,13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i,j,k] == None - -def test_diamond(): - sl = Searchlight(sl_rad=3) - comm = MPI.COMM_WORLD - rank = comm.rank - size = comm.size - dim0, dim1, dim2 = (50,50,50) - ntr = 30 - nsubj = 3 - mask = np.zeros((dim0,dim1,dim2), dtype=np.bool) - data = [np.empty((dim0,dim1,dim2,ntr), dtype=np.object) if i % size == rank else None for i in range(0, nsubj)] - - # Put a spot in the mask - mask[10:17,10:17,10:17] = Diamond(3) - - def sfn(l, msk, myrad, bcast_var): - return 1.0 - - sl.distribute(data, mask) - global_outputs = sl.run_searchlight(sfn) - - if rank == 0: - assert global_outputs[13,13,13] == 1.0 - global_outputs[13,13,13] = None - - for i in range(global_outputs.shape[0]): - for j in range(global_outputs.shape[1]): - for k in range(global_outputs.shape[2]): - assert global_outputs[i,j,k] == None - - From 3c367ecbd7573eb75cc0745c7e29a58092259885 Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Sat, 29 Apr 2017 07:05:59 -0700 Subject: [PATCH 06/14] documenting data_ attribute --- brainiak/searchlight/searchlight.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index 3cf305a44..19c0436bf 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -29,7 +29,14 @@ class Shape: """Shape Searchlight shape which is contained in a (2*rad+1)^3 cube + + Attributes + ---------- + + data_ : a 3D boolean numpy array of size (2*rad+1)^3 which is set + to True within the boundaries of the desired shape """ + def __init__(self, rad): """Constructor @@ -58,9 +65,9 @@ def __init__(self, rad): searchlight cube, not counting the center voxel """ - super(Cube, self).__init__(rad) + super().__init__(rad) self.rad = rad - self.data = np.ones((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) + self.data_ = np.ones((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) class Diamond(Shape): @@ -82,14 +89,14 @@ def __init__(self, rad): searchlight cube, not counting the center voxel """ - super(Diamond, self).__init__(rad) - self.data = np.zeros((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) + super().__init__(rad) + self.data_ = np.zeros((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) for r1 in range(2*self.rad+1): for r2 in range(2*self.rad+1): for r3 in range(2*self.rad+1): if(cityblock((r1, r2, r3), (self.rad, self.rad, self.rad)) <= self.rad): - self.data[r1, r2, r3] = True + self.data_[r1, r2, r3] = True class Searchlight: @@ -119,7 +126,7 @@ def __init__(self, sl_rad=1, max_blk_edge=10, shape=Cube): self.sl_rad = sl_rad self.max_blk_edge = max_blk_edge self.comm = MPI.COMM_WORLD - self.shape = shape(sl_rad).data + self.shape = shape(sl_rad).data_ self.bcast_var = None def _get_ownership(self, data): From 8bea46135b7b2d29a3448a715aef747be2e3e938 Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Mon, 1 May 2017 03:44:53 -0700 Subject: [PATCH 07/14] fixed shape test --- brainiak/searchlight/searchlight.py | 6 ++---- tests/searchlight/test_searchlight.py | 11 +++++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index 19c0436bf..6e4282012 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -428,16 +428,14 @@ def _singlenode_searchlight(l, msk, mysl_rad, bcast_var): def check_mask(mask_cube): res = mask_cube[self.shape] return np.any(res) and np.all(res) - + import pathos.multiprocessing inlist = [([ll[i:i+2*mysl_rad+1, j:j+2*mysl_rad+1, k:k+2*mysl_rad+1, :] for ll in l], - msk[i:i+2*mysl_rad+1, - j:j+2*mysl_rad+1, - k:k+2*mysl_rad+1], + self.shape, mysl_rad, bcast_var) if check_mask(msk[i:i+2*mysl_rad+1, diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py index 3a0b6fb64..df458bc09 100644 --- a/tests/searchlight/test_searchlight.py +++ b/tests/searchlight/test_searchlight.py @@ -23,7 +23,7 @@ """ -def test_cube(): +def test_searchlight_with_cube(): sl = Searchlight(sl_rad=3) comm = MPI.COMM_WORLD rank = comm.rank @@ -38,6 +38,7 @@ def test_cube(): mask[10:17,10:17,10:17] = True def sfn(l, msk, myrad, bcast_var): + assert np.all(msk) return 1.0 sl.distribute(data, mask) @@ -52,8 +53,8 @@ def sfn(l, msk, myrad, bcast_var): for k in range(global_outputs.shape[2]): assert global_outputs[i,j,k] == None -def test_diamond(): - sl = Searchlight(sl_rad=3) +def test_searchlight_with_diamond(): + sl = Searchlight(sl_rad=3, shape=Diamond) comm = MPI.COMM_WORLD rank = comm.rank size = comm.size @@ -64,9 +65,11 @@ def test_diamond(): data = [np.empty((dim0,dim1,dim2,ntr), dtype=np.object) if i % size == rank else None for i in range(0, nsubj)] # Put a spot in the mask - mask[10:17,10:17,10:17] = Diamond(3) + mask[10:17,10:17,10:17] = Diamond(3).data_ def sfn(l, msk, myrad, bcast_var): + assert np.all(msk[Diamond(3).data_]) + assert not np.any(msk[~Diamond(3).data_]) return 1.0 sl.distribute(data, mask) From 08cd4c8352c0a983b68e5a2651dce1a0ffded3a5 Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Mon, 1 May 2017 03:54:17 -0700 Subject: [PATCH 08/14] style fixes --- brainiak/searchlight/searchlight.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index 6e4282012..eb3e1fe62 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -428,14 +428,14 @@ def _singlenode_searchlight(l, msk, mysl_rad, bcast_var): def check_mask(mask_cube): res = mask_cube[self.shape] return np.any(res) and np.all(res) - + import pathos.multiprocessing inlist = [([ll[i:i+2*mysl_rad+1, j:j+2*mysl_rad+1, k:k+2*mysl_rad+1, :] for ll in l], - self.shape, + self.shape, mysl_rad, bcast_var) if check_mask(msk[i:i+2*mysl_rad+1, From 2678f6aeb7034f3483a7283bc74850f9f1eadb7f Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Mon, 1 May 2017 03:55:58 -0700 Subject: [PATCH 09/14] Fixed description of Diamond --- brainiak/searchlight/searchlight.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index eb3e1fe62..5a332d703 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -73,8 +73,8 @@ def __init__(self, rad): class Diamond(Shape): """Diamond - Searchlight shape which is a (2*rad+1)^3 diamond. A diamond - is inscribed in the (2*rad+1)^3 cube. Any location in the cube + Searchlight shape which is a diamond + inscribed in a (2*rad+1)^3 cube. Any location in the cube which has a Manhattan distance of less than rad from the center point is set to True. From 32358d40a69bf258a70e2f5bb5d1f6c09384dadd Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Mon, 1 May 2017 06:31:39 -0700 Subject: [PATCH 10/14] removed trailing whitespace --- brainiak/searchlight/searchlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index 5a332d703..9d691d4aa 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -73,7 +73,7 @@ def __init__(self, rad): class Diamond(Shape): """Diamond - Searchlight shape which is a diamond + Searchlight shape which is a diamond inscribed in a (2*rad+1)^3 cube. Any location in the cube which has a Manhattan distance of less than rad from the center point is set to True. From 9550b946b2fe79f8e5e58c488d7e5820d21afcf5 Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Mon, 1 May 2017 06:56:58 -0700 Subject: [PATCH 11/14] added check_mask_ function --- brainiak/searchlight/searchlight.py | 53 +++++++++++++++++---------- tests/searchlight/test_searchlight.py | 6 +-- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index 9d691d4aa..299cacf73 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -28,13 +28,14 @@ class Shape: """Shape - Searchlight shape which is contained in a (2*rad+1)^3 cube + Searchlight shape which is contained in a cube sized + (2*rad+1,2*rad+1,2*rad+1) Attributes ---------- - data_ : a 3D boolean numpy array of size (2*rad+1)^3 which is set - to True within the boundaries of the desired shape + mask_ : a 3D boolean numpy array of size (2*rad+1,2*rad+1,2*rad+1) + which is set to True within the boundaries of the desired shape """ def __init__(self, rad): @@ -53,7 +54,7 @@ def __init__(self, rad): class Cube(Shape): """Cube - Searchlight shape which is a (2*rad+1)^3 cube + Searchlight shape which is a cube of size (2*rad+1,2*rad+1,2*rad+1) """ def __init__(self, rad): """Constructor @@ -67,17 +68,16 @@ def __init__(self, rad): """ super().__init__(rad) self.rad = rad - self.data_ = np.ones((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) + self.mask_ = np.ones((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) class Diamond(Shape): """Diamond Searchlight shape which is a diamond - inscribed in a (2*rad+1)^3 cube. Any location in the cube - which has a Manhattan distance of less than rad from the center - point is set to True. - + inscribed in a cube of size (2*rad+1,2*rad+1,2*rad+1). + Any location in the cube which has a Manhattan distance of less than rad + from the center point is set to True. """ def __init__(self, rad): """Constructor @@ -90,13 +90,13 @@ def __init__(self, rad): """ super().__init__(rad) - self.data_ = np.zeros((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) + self.mask_ = np.zeros((2*rad+1, 2*rad+1, 2*rad+1), dtype=np.bool) for r1 in range(2*self.rad+1): for r2 in range(2*self.rad+1): for r3 in range(2*self.rad+1): if(cityblock((r1, r2, r3), (self.rad, self.rad, self.rad)) <= self.rad): - self.data_[r1, r2, r3] = True + self.mask_[r1, r2, r3] = True class Searchlight: @@ -126,7 +126,7 @@ def __init__(self, sl_rad=1, max_blk_edge=10, shape=Cube): self.sl_rad = sl_rad self.max_blk_edge = max_blk_edge self.comm = MPI.COMM_WORLD - self.shape = shape(sl_rad).data_ + self.shape = shape(sl_rad).mask_ self.bcast_var = None def _get_ownership(self, data): @@ -390,6 +390,25 @@ def run_block_function(self, block_fn): return outmat + def _check_mask(self, mask_cube): + """Check if a mask region overlaps with the searchlight shape + + Parameters + ---------- + + mask_cube: Boolean numpy array of size (2*rad+1,2*rad+1,2*rad+1), + which is to be matched against the searchlight shape + + Returns + ------- + + True if every true location in the searchlight shape is also set to + True in the corresponding mask_cube location. + """ + + res = mask_cube[self.shape] + return np.any(res) and np.all(res) + def run_searchlight(self, voxel_fn, pool_size=None): """Perform a function at each active voxel @@ -425,10 +444,6 @@ def _singlenode_searchlight(l, msk, mysl_rad, bcast_var): mysl_rad:-mysl_rad, mysl_rad:-mysl_rad] - def check_mask(mask_cube): - res = mask_cube[self.shape] - return np.any(res) and np.all(res) - import pathos.multiprocessing inlist = [([ll[i:i+2*mysl_rad+1, j:j+2*mysl_rad+1, @@ -438,9 +453,9 @@ def check_mask(mask_cube): self.shape, mysl_rad, bcast_var) - if check_mask(msk[i:i+2*mysl_rad+1, - j:j+2*mysl_rad+1, - k:k+2*mysl_rad+1]) else None + if self._check_mask(msk[i:i+2*mysl_rad+1, + j:j+2*mysl_rad+1, + k:k+2*mysl_rad+1]) else None for i in range(0, outmat.shape[0]) for j in range(0, outmat.shape[1]) for k in range(0, outmat.shape[2])] diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py index df458bc09..9b3568094 100644 --- a/tests/searchlight/test_searchlight.py +++ b/tests/searchlight/test_searchlight.py @@ -65,11 +65,11 @@ def test_searchlight_with_diamond(): data = [np.empty((dim0,dim1,dim2,ntr), dtype=np.object) if i % size == rank else None for i in range(0, nsubj)] # Put a spot in the mask - mask[10:17,10:17,10:17] = Diamond(3).data_ + mask[10:17,10:17,10:17] = Diamond(3).mask_ def sfn(l, msk, myrad, bcast_var): - assert np.all(msk[Diamond(3).data_]) - assert not np.any(msk[~Diamond(3).data_]) + assert np.all(msk[Diamond(3).mask_]) + assert not np.any(msk[~Diamond(3).mask_]) return 1.0 sl.distribute(data, mask) From 73bcad3d00777f0c9cdfe507f543b474064b98de Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Wed, 3 May 2017 06:46:10 -0700 Subject: [PATCH 12/14] Added documentation to clarify border regions --- brainiak/searchlight/searchlight.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index 299cacf73..7d8648ca0 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -410,7 +410,10 @@ def _check_mask(self, mask_cube): return np.any(res) and np.all(res) def run_searchlight(self, voxel_fn, pool_size=None): - """Perform a function at each active voxel + """Perform a function at each voxel which is valid for the + user-provided shape. This means, the user-provided mask, centered at + the active voxel, must be True at each location in which the shape is + True. Parameters ---------- From 0de82937e9eaaee1f1b3fcf0f19f5377ffc54d34 Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Wed, 10 May 2017 04:54:21 -0700 Subject: [PATCH 13/14] Added documentation --- brainiak/searchlight/searchlight.py | 43 ++++++++++----------------- tests/searchlight/test_searchlight.py | 13 ++++---- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index 7d8648ca0..dc44f540a 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -390,30 +390,10 @@ def run_block_function(self, block_fn): return outmat - def _check_mask(self, mask_cube): - """Check if a mask region overlaps with the searchlight shape - - Parameters - ---------- - - mask_cube: Boolean numpy array of size (2*rad+1,2*rad+1,2*rad+1), - which is to be matched against the searchlight shape - - Returns - ------- - - True if every true location in the searchlight shape is also set to - True in the corresponding mask_cube location. - """ - - res = mask_cube[self.shape] - return np.any(res) and np.all(res) - def run_searchlight(self, voxel_fn, pool_size=None): - """Perform a function at each voxel which is valid for the - user-provided shape. This means, the user-provided mask, centered at - the active voxel, must be True at each location in which the shape is - True. + """Perform a function at each voxel which is set to True in the + user-provided mask. The mask passed to the searchlight function will be + further masked by the user-provided searchlight shape. Parameters ---------- @@ -439,6 +419,15 @@ def run_searchlight(self, voxel_fn, pool_size=None): pool_size: Number of parallel processes in shared memory process pool + Returns + ------- + + A volume which is the same size as the mask, however a number of voxels + equal to the searchlight radius has been removed from each border of + the volume. This volume contains the values returned from the + searchlight function at each voxel which was set to True in the mask, + and None elsewhere. + """ def _singlenode_searchlight(l, msk, mysl_rad, bcast_var): @@ -453,12 +442,12 @@ def _singlenode_searchlight(l, msk, mysl_rad, bcast_var): k:k+2*mysl_rad+1, :] for ll in l], - self.shape, + msk[i:i+2*mysl_rad+1, + j:j+2*mysl_rad+1, + k:k+2*mysl_rad+1] * self.shape, mysl_rad, bcast_var) - if self._check_mask(msk[i:i+2*mysl_rad+1, - j:j+2*mysl_rad+1, - k:k+2*mysl_rad+1]) else None + if msk[i+mysl_rad, j+mysl_rad, k+mysl_rad] else None for i in range(0, outmat.shape[0]) for j in range(0, outmat.shape[1]) for k in range(0, outmat.shape[2])] diff --git a/tests/searchlight/test_searchlight.py b/tests/searchlight/test_searchlight.py index 9b3568094..8d673edc4 100644 --- a/tests/searchlight/test_searchlight.py +++ b/tests/searchlight/test_searchlight.py @@ -38,8 +38,9 @@ def test_searchlight_with_cube(): mask[10:17,10:17,10:17] = True def sfn(l, msk, myrad, bcast_var): - assert np.all(msk) + if np.all(msk) and np.any(msk): return 1.0 + return None sl.distribute(data, mask) global_outputs = sl.run_searchlight(sfn) @@ -68,9 +69,10 @@ def test_searchlight_with_diamond(): mask[10:17,10:17,10:17] = Diamond(3).mask_ def sfn(l, msk, myrad, bcast_var): - assert np.all(msk[Diamond(3).mask_]) assert not np.any(msk[~Diamond(3).mask_]) - return 1.0 + if np.all(msk[Diamond(3).mask_]): + return 1.0 + return None sl.distribute(data, mask) global_outputs = sl.run_searchlight(sfn) @@ -143,9 +145,8 @@ def sfn(l,msk,myrad,bcast_var): for d0 in range(rad, global_outputs.shape[0]-rad): for d1 in range(rad, global_outputs.shape[1]-rad): for d2 in range(rad, global_outputs.shape[2]-rad): - msk_blk = mask[d0:d0+2*rad+1, d1:d1+2*rad+1, d2:d2+2*rad+1] - if np.all(msk_blk) and np.any(msk_blk): - assert np.array_equal(np.array(global_outputs[d0,d1,d2]), np.array([d0,d1,d2])) + if mask[d0, d1, d2]: + assert np.array_equal(np.array(global_outputs[d0,d1,d2]), np.array([d0,d1,d2])) def block_test(data, mask, max_blk_edge, rad): From e564dce497c3bd9fa19101480bcca50b448595bd Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Wed, 10 May 2017 04:55:47 -0700 Subject: [PATCH 14/14] removed trailing whitespace --- brainiak/searchlight/searchlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brainiak/searchlight/searchlight.py b/brainiak/searchlight/searchlight.py index dc44f540a..0f009da44 100644 --- a/brainiak/searchlight/searchlight.py +++ b/brainiak/searchlight/searchlight.py @@ -393,7 +393,7 @@ def run_block_function(self, block_fn): def run_searchlight(self, voxel_fn, pool_size=None): """Perform a function at each voxel which is set to True in the user-provided mask. The mask passed to the searchlight function will be - further masked by the user-provided searchlight shape. + further masked by the user-provided searchlight shape. Parameters ----------