Reputation: 166
I'm trying to create a list of all the overlapping subarrays of equal size from a larger 3D array (for patch-based segmentation) where each subarray needs to be flattened (as a 1D vector) so I can make use of the ball tree in sklearn.neighbours.BallTree.
So for example, given a 100x100x100 image, if I were to break this down into 5x5x5 overlapping patches (subarrays), I would have 96x96x96 = 884,736 of them.
However I have not found any way of doing so without numpy allocating more memory for each flattened/vectorized subarray. This seems to be because each subarray is not contiguous in memory.
e.g. For the 100x100x100 image, if I want each 5x5x5 patch as a 1D vector (of length 125), numpy decides to allocate a brand new array in memory for all 884,736 of them which then becomes rather large especially if I want to work with more than a single 100x100x100 image!
I would welcome any solutions for overcoming this memory challenge in python/numpy. I was considering creating a subclass of the numpy.ndarray object which stores a pointer to the location of the patch in the bigger image and but returns the data as a 1D numpy array only when called (and this is then deleted again when not used) but I have not come across enough details on subclassing ndarray objects to do so. I will be really disappointed if the only solution is to implement everything in C/C++ instead. I appreciate any help that can be provided, thanks!
Upvotes: 2
Views: 760
Reputation: 284830
Based on your question, you may already be aware of all of this. However, I'm posting this "answer" as more of a discussion of what the problems are, because many people may not be aware of them....
If you're not, though, you can make a 96x96x96x5x5x5
array from a 100x100x100
image that acts as a 5x5x5
moving window without allocating any additional memory.
However, because you can only have one stride per dimension, there's no way to reshape this into a 96x96x96x125
array without making a copy.
At any rate, here's an example (basically taken straight from one of my previous answers):
import numpy as np
def rolling_window_lastaxis(a, window):
"""Directly taken from Erik Rigtorp's post to numpy-discussion.
<http://www.mail-archive.com/[email protected]/msg29450.html>"""
if window < 1:
raise ValueError, "`window` must be at least 1."
if window > a.shape[-1]:
raise ValueError, "`window` is too long."
shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
strides = a.strides + (a.strides[-1],)
return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
def rolling_window(a, window):
"""Takes a numpy array *a* and a sequence of (or single) *window* lengths
and returns a view of *a* that represents a moving window."""
if not hasattr(window, '__iter__'):
return rolling_window_lastaxis(a, window)
for i, win in enumerate(window):
if win > 1:
a = a.swapaxes(i, -1)
a = rolling_window_lastaxis(a, win)
a = a.swapaxes(-2, i)
return a
x = np.zeros((100,100,100), dtype=np.uint8)
y = rolling_window(x, (5,5,5))
print 'Now *y* will be a 96x96x96x5x5x5 array...'
print y.shape
print 'Representing a "rolling window" into *x*...'
y[0,0,0,...] = 1
y[1,1,0,...] = 2
print x[:10,:10,0] # Note that *x* and *y* share the same memory!
This yields:
Now *y* will be a 96x96x96x5x5x5 array...
(96, 96, 96, 5, 5, 5)
Representing a "rolling window" into *x*...
[[1 1 1 1 1 0 0 0 0 0]
[1 2 2 2 2 2 0 0 0 0]
[1 2 2 2 2 2 0 0 0 0]
[1 2 2 2 2 2 0 0 0 0]
[1 2 2 2 2 2 0 0 0 0]
[0 2 2 2 2 2 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]]
However, as you've already noted, we can't reshape this to 96x96x96x125
without creating a copy. y.shape = (96,96,96,-1)
will raise an error, and z = y.reshape((96,96,96,-1))
will work, but will return a copy.
(The relevant documentation is in numpy.reshape
, if this seems confusing. Basically reshape
will avoid copying when possible, and return a copy if its not, whereas setting the shape
attribute will raise an error when copying isn't possible.)
However, even if you build a more efficient array container, sklearn.neighbors.BallTree
is almost definitely going to make temporary intermediate copies.
You mention that you're doing image segmentation. Why not look into a more efficient algorithm than the rather "brute-force" you seem to be trying? (Or if that's not feasible, give us a few more details as to why... Maybe someone will have a better idea?)
Upvotes: 1