1

Is there any function in R, Python, Lua, Java, Matlab or any programming language that can reduce the size of the 3D input_array from c(6, 6, 6) to c(4, 4, 4) by deleting all-zero matrices on the periphery of the 3 dimensions?

## Create an empty array with all zeros
input_array <- array(data = c(0), dim = c(6, 6, 6)) 

## Populate the central locations of the array with 1
input_array[, ,2:5] <- matrix(c(0, 0, 0, 0, 0, 0, 
                                0, 1, 1, 1, 1, 0,  
                                0, 1, 1, 1, 1, 0, 
                                0, 1, 1, 1, 1, 0, 
                                0, 1, 1, 1, 1, 0, 
                                0, 0, 0, 0, 0, 0), 
                                nrow = 6, ncol = 6) 

## Show the input_array
input_array 

## The target output
output_array <- array(data = c(1), dim = c(4, 4, 4)) 

So, in other words, I am looking for a function that takes the input_array as an input and spits the output_array as an output. I want to maintain the 3D nature of the array throughout the conversion. The reason I am looking into this, is that I have very large 3D arrays with lots of zeros around the periphery and by removing all-zeros matrices out of the three dimensions, I can achieve considerable reduction in the sizes of these arrays and hence allow for more efficient processing.

In case there is no function out there, what could be a logic to write a new function so as to get this done? Use whatever language you prefer if you have anything to share, any feedback or help is very much appreciated.

mansanto
  • 360
  • 4
  • 16
  • Is it supposed to shave off surfaces based on whether they are all zero (if so: repeatedly?) or just one from each side? – Paul Panzer Feb 10 '17 at 05:39
  • repeatedly as long as the surface is all-zeros, this could be one surface or more than one. – mansanto Feb 10 '17 at 05:40
  • Pick a language. Still working on a more elegant way in R, but functional: `input_array[apply(input_array, 1, function(x){any(x != 0)}), apply(input_array, 2, function(x){any(x != 0)}), apply(input_array, 3, function(x){any(x != 0)})]` – alistaire Feb 10 '17 at 05:42
  • 1
    @alistaire - you can shrink it up a bit by working on logical vectors - `input_array[,,apply(input_array!=0, 3, any)]` for instance. – thelatemail Feb 10 '17 at 05:48
  • 1
    Also worth linking for future searchers - http://stackoverflow.com/questions/25375664/remove-layer-of-array-that-contains-all-zeros/25375698 – thelatemail Feb 10 '17 at 05:49
  • 1
    Nice! Less repetitive, though pretty much the same computations still: `abind::asub(input_array, lapply(1:3, function(d){apply(input_array != 0, d, any)}))` – alistaire Feb 10 '17 at 05:54

3 Answers3

3

You can do reasonably well in R with the help of the abind package, which is handy for dealing with arrays:

abind::asub(input_array,    # take this and subset
            lapply(1:3, function(d){    # iterate over its dimensions
                apply(input_array != 0, d, any)    # look for any non-zero elements
            }))
## , , 1
## 
##      [,1] [,2] [,3] [,4]
## [1,]    1    1    1    1
## [2,]    1    1    1    1
## [3,]    1    1    1    1
## [4,]    1    1    1    1
## 
## , , 2
## 
##      [,1] [,2] [,3] [,4]
## [1,]    1    1    1    1
## [2,]    1    1    1    1
## [3,]    1    1    1    1
## [4,]    1    1    1    1
## 
## , , 3
## 
##      [,1] [,2] [,3] [,4]
## [1,]    1    1    1    1
## [2,]    1    1    1    1
## [3,]    1    1    1    1
## [4,]    1    1    1    1
## 
## , , 4
## 
##      [,1] [,2] [,3] [,4]
## [1,]    1    1    1    1
## [2,]    1    1    1    1
## [3,]    1    1    1    1
## [4,]    1    1    1    1
alistaire
  • 42,459
  • 4
  • 77
  • 117
2

Thanks for all of those who shared their thoughts or cited other similar posts. I reached this through your answers:

## Create an empty array with all zeros
input_array <- array(data = c(0), dim = c(6, 6, 6)) 

## Populate the central locations of the array with 1
input_array[, ,2:5] <- matrix(c(0, 0, 0, 0, 0, 0, 
                                0, 1, 1, 1, 1, 0,  
                                0, 1, 1, 1, 1, 0, 
                                0, 1, 1, 1, 1, 0, 
                                0, 1, 1, 1, 1, 0, 
                                0, 0, 0, 0, 0, 0), 
                                nrow = 6, ncol = 6) 

## Show the input_array
input_array 

## The target output
(output_array <- input_array[apply(input_array != 0, 3, any),
                             apply(input_array != 0, 2, any),
                             apply(input_array != 0, 1, any)])  

I haven't tested it with different inputs, so further testing for different inputs may need to be performed.

mansanto
  • 360
  • 4
  • 16
1

Here's one option (python/numpy):

xcrit = np.where(np.any(input, axis=(0,1)))[0]
ycrit = np.where(np.any(input, axis=(0,2)))[0]
zcrit = np.where(np.any(input, axis=(1,2)))[0]
output = input[zcrit[0]:zcrit[-1]+1,ycrit[0]:ycrit[-1]+1,xcrit[0]:xcrit[-1]+1]

explanation np.any returns True if along the dimensions passed at least one cell is True. it returns a boolean array shaped like the remaining dimensions

np.where finds the indices of the True cells of its argument. in this example we use it to find the indices of the first and last not-all-zero slice along each coordinate

Sample run:

>>> input = np.zeros((6,6,6))
>>> input[1:-2,1:-2,2:-1] = 1
>>> xcrit = np.where(np.any(input, axis=(0,1)))[0]
>>> ycrit = np.where(np.any(input, axis=(0,2)))[0]
>>> zcrit = np.where(np.any(input, axis=(1,2)))[0]
>>> output = input[zcrit[0]:zcrit[-1]+1,ycrit[0]:ycrit[-1]+1,xcrit[0]:xcrit[-1]+1]
>>> # verify
... input.sum(), output.sum(), output.size
(27.0, 27.0, 27)
Paul Panzer
  • 51,835
  • 3
  • 54
  • 99