def _init(self, **kwargs): for key, val in kwargs: setattr(self, key, val) if self.permutation is None: self.permutation = tuple(range(self._dim)) if self.slicer is None: # same layout as on-disk self.spatial = self._spatial self.affine = self._affine self.shape = self._shape self.slicer = expand_index([Ellipsis], self._shape) return self
def slice(self, index, new_shape=None, _pre_expanded=False): """Extract a sub-part of the array. Indices can only be slices, ellipses, integers or None. Parameters ---------- index : tuple[slice or ellipsis or int or None] Other Parameters ---------------- new_shape : sequence[int], optional Output shape of the sliced object _pre_expanded : bool, default=False Set to True of `expand_index` has already been called on `index` Returns ------- subarray : type(self) MappedArray object, with the indexing operations and affine matrix relating to the new sub-array. """ index = expand_index(index, self.shape) new_shape = guess_shape(index, self.shape) if any(isinstance(idx, list) for idx in index) > 1: raise ValueError('List indices not currently supported ' '(otherwise we enter advanced indexing ' 'territory and it becomes too complicated).') new = copy(self) new.shape = new_shape # compute new affine if self.affine is not None: spatial_shape = [ sz for sz, msk in zip(self.shape, self.spatial) if msk ] spatial_index = [idx for idx in index if not is_newaxis(idx)] spatial_index = [ idx for idx, msk in zip(spatial_index, self.spatial) if msk ] affine, _ = affine_sub(self.affine, spatial_shape, tuple(spatial_index)) else: affine = None new.affine = affine # compute new slicer perm_shape = [self._shape[d] for d in self.permutation] new.slicer = compose_index(self.slicer, index, perm_shape) # compute new spatial mask spatial = [] i = 0 for idx in new.slicer: if is_newaxis(idx): spatial.append(False) else: # original axis if not is_droppedaxis(idx): spatial.append(self._spatial[self.permutation[i]]) i += 1 new.spatial = tuple(spatial) return new
def slice(self, index, new_shape=None): # overload slicer -> slice individual arrays index = expand_index(index, self.shape) new_shape = guess_shape(index, self.shape) assert len(index) > 0, "index should never be empty here" if any(isinstance(idx, list) for idx in index) > 1: raise ValueError('List indices not currently supported ' '(otherwise we enter advanced indexing ' 'territory and it becomes too complicated).') index = list(index) shape_cat = self.shape[self._dim_cat] # find out which index corresponds to the concatenated dimension # + compute the concatenated dimension in the output array new_dim_cat = self._dim_cat nb_old_dim = -1 for map_dim_cat, idx in enumerate(index): if is_newaxis(idx): # an axis was added: dim_cat moves to the right new_dim_cat = new_dim_cat + 1 elif is_droppedaxis(idx): # an axis was dropped: dim_cat moves to the left new_dim_cat = new_dim_cat - 1 nb_old_dim += 1 else: nb_old_dim += 1 if nb_old_dim >= self._dim_cat: # found the concatenated dimension break index_cat = index[map_dim_cat] index_cat = neg2pos(index_cat, shape_cat) # /!\ do not call it again if is_droppedaxis(index_cat): # if the concatenated dimension is dropped, return the # corresponding array (sliced) if index_cat < 0 or index_cat >= shape_cat: raise IndexError('Index {} out of bounds [0, {}]'.format( index_cat, shape_cat)) nb_pre = 0 for i in range(len(self._arrays)): if nb_pre < index_cat: # we haven't found the volume yet nb_pre += self._arrays[i].shape[self._dim_cat] continue break if nb_pre > index_cat: i = i - 1 nb_pre -= self._arrays[i].shape[self._dim_cat] index_cat = index_cat - nb_pre index[map_dim_cat] = index_cat return self._arrays[i].slice(tuple(index), new_shape) # else, we may have to drop some volumes and slice the others assert is_sliceaxis(index_cat), "This should not happen" arrays = self._arrays step = index_cat.step or 1 if step < 0: # if negative step: # 1) invert everything invert_index = [slice(None)] * self.dim invert_index[self._dim_cat] = slice(None, None, -1) arrays = [array[tuple(invert_index)] for array in arrays] # 2) update index_cat index_cat = invert_slice(index_cat, shape_cat, neg2pos=False) # compute navigator # (step is positive) start, step, nb_elem_total = slice_navigator(index_cat, shape_cat, do_neg2pos=False) nb_pre = 0 # nb of slices on the left of the cursor kept_arrays = [] # arrays at least partly in bounds starts = [] # start in each kept array stops = [] # stop in each kept array size_since_start = 0 # nb of in-bounds slices left of the cursor while len(arrays) > 0: # pop array array, *arrays = arrays size_cat = array.shape[self._dim_cat] if nb_pre + size_cat < start: # discarded volumes at the beginning nb_pre += size_cat continue if nb_pre < start: # first volume kept_arrays.append(array) starts.append(start - nb_pre) elif index_cat.stop is None or nb_pre < index_cat.stop: # other kept volume kept_arrays.append(array) skip = size_since_start - (size_since_start // step) * step starts.append(skip) # compute stopping point nb_elem_prev = size_since_start // step nb_elem_remaining = nb_elem_total - nb_elem_prev nb_elem_this_volume = (size_cat - starts[-1]) // step if nb_elem_remaining <= nb_elem_this_volume: # last volume stops.append(nb_elem_remaining) break # read as much as possible size_since_start += size_cat nb_pre += size_cat stops.append(None) continue # slice kept arrays arrays = [] for array, start, stop in zip(kept_arrays, starts, stops): index[map_dim_cat] = slice(start, stop, step) arrays.append(array[tuple(index)]) # create new CatArray new = copy(self) new._arrays = arrays new._dim_cat = new_dim_cat new.shape = new_shape return new