Source code for pylablib.core.utils.indexing

"""
Processing and normalization of different indexing styles.
"""

from ..utils.py3 import textstring

import numpy as np
from ..utils import string as string_utils



def _single_idx(name, names_list, only_exact):
    if isinstance(name,int) or name is None:
        return name
    if only_exact:
        return string_utils.find_list_string(name,names_list,case_sensitive=True,as_prefix=False)[0]
    else:
        try:
            return string_utils.find_list_string(name,names_list,case_sensitive=True,as_prefix=False)[0]
        except KeyError:
            return string_utils.find_list_string(name,names_list,case_sensitive=True,as_prefix=True)[0]
[docs] def string_list_idx(names_to_find, names_list, only_exact=False): """ Index through a list of strings in `names_list`. Return corresponding numerical indices. Case sensitive; first look for exact matching, then for prefix matching (unless ``only_exact=True``). """ if isinstance(names_to_find,list): return [_single_idx(n,names_list,only_exact) for n in names_to_find] else: return _single_idx(names_to_find,names_list,only_exact)
[docs] def is_slice(idx): """ Check if `idx` is slice. """ #return isinstance(idx,slice) return type(idx)==slice
[docs] def is_range(idx): """ Check if `idx` is iterable (list, numpy array, or `builtins.range`). """ if isinstance(idx,list): return len(idx)==0 or isinstance(idx[0],int) if isinstance(idx,np.ndarray): return idx.dtype=="int" return isinstance(idx,range)
[docs] def is_bool_array(idx): """ Check if `idx` is a boolean array. """ if isinstance(idx,list): return len(idx)>0 and isinstance(idx[0],bool) if isinstance(idx,np.ndarray): return idx.dtype=="bool" return False
[docs] def to_range(idx, length): """ Turn list, array, builtins.range, slice into an iterable. """ if is_slice(idx): return range(*idx.indices(length)) else: return idx
[docs] def covers_all(idx, length, strict=False, ordered=True): """ Check if `idx` covers all of the elements (indices from 0 to `length`). If ``strict==True``, strictly checks the condition; otherwise may return ``False`` even if `idx` actually covers everything, but takes less time (i.e., can be used for optimization). If ``ordered==True``, only returns ``True`` when indices follow in order. """ if is_slice(idx): rng=idx.indices(length) return rng==(0,length,1) or ((not ordered) and rng==(length-1,-1,-1)) elif isinstance(idx,range): return (idx[0]==0 and idx[-1]==length-1) or ((not ordered) and (idx[0]==length-1 and idx[-1]==0)) elif is_bool_array(idx): return len(idx)>=length and np.asarray(idx)[:length].all() elif strict: if is_range(idx): if len(idx)<length: return False if ordered: for i,j in enumerate(idx[:length]): if i!=j: return False return True else: included=np.zeros(length,dtype="bool") for i in idx: if i<length: included[i]=True return included.all() return False else: return False
# Index transformers. # Allowed input types: # scalar: integer, string # vector: integer lists or numpy arrays, bool lists or numpy arrays, string lists or numpy arrays, builtin.ranges, slices and string slices
[docs] class IIndex: """ A generic index object. Used to transform a variety of indexes into a subset applicable for specific objects (numpy arrays or lists). Allowed input index types: - scalar: integer, string - vector: integer lists or numpy arrays, bool lists or numpy arrays, string lists or numpy arrays, builtin.ranges, slices and string slices """ def __init__(self): self.idx=None self.ndim=None
[docs] def tup(self): """ Represent index as a tuple for easy unpacking. """ return (self.ndim,self.idx)
[docs] class NumpyIndex(IIndex): """ NumPy compatible index: allows for integers, slices, numpy integer or boolean arrays, integer lists or builtin.ranges. Args: idx: raw index ndim: index dimensionality (either 0 or 1); if supplied, assume that `idx` is already normalized """ def __init__(self, idx, ndim=None): IIndex.__init__(self) if ndim is None: if is_slice(idx): if isinstance(idx.start,textstring) or isinstance(idx.stop,textstring): raise ValueError("can't accept string index for numpy object") self.ndim=1 else: try: if len(idx)==0: self.ndim=1 idx=np.asarray(idx) else: val=idx[0] if isinstance(val,textstring): raise ValueError("can't accept string index for numpy object") if isinstance(val,bool): idx=np.asarray(idx) self.ndim=1 except (IndexError,TypeError,AttributeError): self.ndim=0 self.idx=idx else: self.ndim=ndim self.idx=idx
[docs] class ListIndex(IIndex): """ List compatible index: allows for integers, slices, numpy integer arrays, integer lists or builtin.ranges. Args: idx: raw index names: list of allowed index string values, which is used to convert them into integers ndim: index dimensionality (either 0 or 1); if supplied, assume that `idx` is already normalized """ def __init__(self, idx, names=None, ndim=None): IIndex.__init__(self) if ndim is None: if is_slice(idx): if names is not None and (isinstance(idx.start,textstring) or isinstance(idx.stop,textstring)): start_stop=string_list_idx([idx.start,idx.stop],names) idx=slice(start_stop[0],start_stop[1],idx.step) # crash later if names is None self.ndim=1 else: try: if len(idx)==0: self.ndim=1 idx=[] else: val=idx[0] if isinstance(val,textstring): if names: idx=string_list_idx(idx,names) if type(idx)==list: self.ndim=1 else: self.ndim=0 else: raise ValueError("can't accept string index for list") else: if isinstance(val,bool) or isinstance(val,np.bool_): idx=[i for i,v in enumerate(idx) if v] self.ndim=1 except (IndexError,TypeError,AttributeError): self.ndim=0 self.idx=idx else: self.ndim=ndim self.idx=idx
[docs] class ListIndexNoSlice(ListIndex): """ List compatible index with slice unwrapped into builtin.range: allows for integers, numpy integer arrays, integer lists or builtin.ranges. Args: idx: raw index names: list of allowed index string values, which is used to convert them into integers length: length of the list (used to expand slice indices) ndim: index dimensionality (either 0 or 1); if supplied, assume that `idx` is already normalized """ def __init__(self, idx, names=None, length=None, ndim=None): if ndim is None: ListIndex.__init__(self,idx,names) if is_slice(self.idx): length=len(names) if length is None else length self.idx=range(*self.idx.indices(length)) else: self.ndim=ndim self.idx=idx
[docs] def to_double_index(idx, names): """ Convert double index into a pair of indexes. Assume that one index is purely numerical, while the other can take names (out of the supplied list). Args: idx: raw double index names: list of allowed index string values, which is used to convert them into integers """ if type(idx)==tuple: try: idx1=NumpyIndex(idx[0]) idx2=ListIndexNoSlice(idx[1],names) return idx1,idx2 except ValueError: idx1=NumpyIndex(idx[1]) idx2=ListIndexNoSlice(idx[0],names) return idx1,idx2 else: try: return NumpyIndex(idx),ListIndexNoSlice(range(len(names)),ndim=1) except ValueError: return ListIndex(slice(None),ndim=1),ListIndexNoSlice(idx,names)