Source code for ClearMap.IO.NPY

# -*- coding: utf-8 -*-
"""
NPY
===

IO interface to numpy arrays.
"""
__author__    = 'Christoph Kirst <christoph.kirst.ck@gmail.com>'
__license__   = 'GPLv3 - GNU General Public License v3 (see LICENSE.txt)'
__copyright__ = 'Copyright © 2020 by Christoph Kirst'
__webpage__   = 'https://idisco.info'
__download__  = 'https://www.github.com/ChristophKirst/ClearMap2'


import numpy as np

import ClearMap.IO.Source as src
from ClearMap.Utils.exceptions import ClearMapPermissionError

###############################################################################
### Source class
###############################################################################

[docs] class Source(src.Source): """Numpy array source.""" def __init__(self, array=None, shape=None, dtype=None, order=None, name=None, mode=None): """Numpy source class constructor. Arguments --------- array : array The underlying data array of this source. """ super().__init__(name=name, mode=mode) self._array = _array(shape=shape, dtype=dtype, order=order, array=array) def __getattr__(self, name): #numpy attributes if name != '_array' and hasattr(self, '_array') and hasattr(self._array, name): return getattr(self._array, name) else: raise AttributeError(f'Not such attribute {name!r}!') @property def array(self): """The underlying data array. Returns ------- array : array The underlying data array of this source. """ return self._array @array.setter def array(self, value): self._array = _array(value) @property def shape(self): """The shape of the source. Returns ------- shape : tuple The shape of the source. """ return self._array.shape @shape.setter def shape(self, value): self._array.shape = value @property def dtype(self): """The data type of the source. Returns ------- dtype : dtype The data type of the source. """ return self._array.dtype @dtype.setter def dtype(self, value): self._array = np.asarray(self._array, dtype=value) @property def order(self): """The order of how the data is stored in the source. Returns ------- order : str Returns 'C' for C contigous and 'F' for fortran contigous, None otherwise. """ return order(self.array) @order.setter def order(self, value): self._array = np.asarray(self._array, order = value) @property def element_strides(self): """The strides of the array elements. Returns ------- strides : tuple Strides of the array elements. Note ---- The strides of the elements module itemsize instead of bytes. """ return tuple(s // self._array.itemsize for s in self._array.strides) @property def offset(self): """The offset of the memory map in the file. Returns ------- offset : int Offset of the memeory map in the file. """ if self._array.base is not None: return np.byte_bounds(self._array)[0] - np.byte_bounds(self._array.base)[0] else: return 0 ### Parallel processing
[docs] def as_virtual(self): #TODO: convert to shared memory array ? -> needs to be implemented to make block processing work for in memory numpy arrays ! return self
[docs] def as_buffer(self): return self._array
### Data def __getitem__(self, *args): return self.array.__getitem__(*args) def __setitem__(self, *args): if not self.is_writable: raise ClearMapPermissionError(f'Source {self} was opened read-only (mode="r"). ' f'Use io.edit() to open for writing.') self.array.__setitem__(*args)
#class Array(np.ndarray): # """Array wrapper around numpy ndarray.""" # # def __new__(cls, array): # obj = np.asarray(array).view(cls) # #obj.order = order(obj); # return obj # # def __array_finalize__(self, obj): # if obj is None: return # #self.order = getattr(obj, 'order', None) # # def array(self): # return self.view(np.ndarray); # # @property # def order(self): # return order(self); # # @property # def array_strides(self): # return tuple(np.array(self.strides, dtype = int) / self.itemsize); # # def __str__(self): # if self.shape is not None: # shape = '%r' % ((self.shape,)); # else: # shape = ''; # # if self.dtype is not None: # dtype = '[%s]' % self.dtype; # else: # dtype = ''; # # if order(self) is not None: # _order = '|%s|' % order(self); # else: # _order = ''; # # array = super(Array, self).__str__(); # if len(array) > 100: # e = array[100:].find('\n'); # if e != -1: # array = array[:100 + e] + '...'; # if len(array) > 0: # array = '\n' + array; # else: # array = ''; # # return 'Array' + shape + dtype + _order + array # # def __repr__(self): # return self.__str__(); ############################################################################### ### Functionality ###############################################################################
[docs] def order(array): """Returns the contigous order of an array. Arguments --------- array : ndarray Returns ------- order : 'C', 'F', None """ if isinstance(array, src.Source): return array.order elif isinstance(array, np.ndarray): if array.flags['C_CONTIGUOUS']: return 'C' elif array.flags['F_CONTIGUOUS']: return 'F' else: return None else: return None
############################################################################### ### IO Interface ###############################################################################
[docs] def is_numpy(source): if isinstance(source, (Source, np.ndarray, list, tuple)): return True #elif isinstance(source, str): # and fu.file_extension(source) == 'npy': # return True; else: return False
[docs] def read(source, slicing = None, as_source = None, as_array = None, processes = None, **kwargs): if isinstance(source, (list, tuple)): source = np.array(source) if isinstance(source, Source): if slicing is not None: source = source.__getitem__(slicing) if as_array: return source.array else: return source elif isinstance(source, np.ndarray): if slicing is not None: source = source.__getitem__(slicing) if as_source: return Source(array = source) else: return source # elif isinstance(source, str): # and fu.file_extension(source) == 'npy': # source = np.load(source); # if slicing is not None: # source = source.__getitem__(slicing); # if as_source: # return Source(array = source); # else: # return source; else: raise ValueError('The source is not a valid numpy source!')
#TODO: add processes keyword for parallel writing
[docs] def write(sink, data, slicing = None, **kwargs): if isinstance(sink, Source) and not sink.is_persistable: raise PermissionError(f'Source {sink} was opened in mode="{sink.mode}" ' f'and cannot persist changes to disk. ' f'Use io.edit() to open for in-place editing.') if slicing is None: slicing = () if sink is None: return data.__getitem__(slicing) if isinstance(sink, (src.Source, np.ndarray)): sink.__setitem__(slicing, data) return sink # elif isinstance(sink, str): # and fu.file_extension(sink) == 'npy' # if slicing != (): # if not fu.is_file(sink): # raise ValueError('Cannot write slice to a not existing file %s!' % sink) # memmap = np.lib.format.open_memmap(sink) # memmap.__setitem__(slicing, data) # else: # np.save(sink, data) # return sink else: raise ValueError('The sink is not a valid numpy sink!')
[docs] def create(shape = None, dtype = None, order = None, array = None, as_source = True, **kwargs): """Create a numpy array. Arguments --------- shape : tuple or None The shape of the memory map to create. dtype : dtype The data type of the memory map. order : 'C', 'F', or None The contiguous order of the memmap. array : array, Source or None Optional source with data to fill the numpy array with. as_source : bool If True, return as Source class. Returns ------- array : np.array The numpy array. Note ---- By default numpy arrays are initialized as fortran contiguous if order is None. """ array = _array(shape=shape, dtype=dtype, order=order, array=array) if as_source: return Source(array=array) else: return array
############################################################################### ### Helpers ############################################################################### def _order(array): return order(array) def _array(shape = None, dtype = None, order = None, array = None): """Create a numpy array. Arguments --------- shape : tuple or None The shape of the memory map to create. dtype : dtype The data type of the memory map. order : 'C', 'F', or None The contiguous order of the memmap. array : array, Source or None Optional source with data to fill the memory map. Returns ------- array : np.ndarray The array. """ if isinstance(array, (list, tuple)): array = np.asarray(array, order=order, dtype=dtype) if isinstance(array, np.ndarray): shape = shape if shape is not None else array.shape dtype = dtype if dtype is not None else array.dtype order = order if order is not None else _order(array) if shape != array.shape: raise ValueError('Shape %r and array shape %r mismatch!' % (shape, array.shape)) if dtype != array.dtype or order != _order(array): array = np.asarray(array, order=order, dtype=dtype) else: if shape is None: raise ValueError('Cannot create array without shape!') array = np.zeros(shape, dtype=dtype, order=order) return array ############################################################################### ### Tests ############################################################################### def _test(): import numpy as np import ClearMap.IO.NPY as npy #reload(npy); s = npy.Source(array=np.zeros((5,7))) print(s) import ClearMap.IO.Slice as slc t = slc.Slice(source= s, slicing= (1,)) print(t) v = t.as_virtual() print(v) x = np.ones(250*1000*1000) xs = npy.Source(array=x) print(xs) del x del xs