#!/usr/bin/env python
"""Handle slice by index / location (value)"""
from __future__ import print_function
from datetime import datetime, timedelta
from itertools import count
import re
import numpy as np
from viscid import logger
from viscid.compat import izip, string_types
from viscid.npdatetime import (is_datetime_like, is_timedelta_like,
as_datetime64, as_timedelta64, time_diff)
_R_MULTILEVEL_BRACKETS = r"\[[^\]]*\[[^\]]*\]"
_R_IN_BRACKS = r"\[[^\[\]]+\]"
_R_DATE = r"[0-9]{4}-[0-9]{2}-[0-9]{2}"
_R_TIME02 = r"[0-9]{2}(?::[0-9]{2}){0,2}(?:\.[0-9]+)?"
_R_TIME12 = r"[0-9]{2}(?::[0-9]{2}){1,2}(?:\.[0-9]+)?"
RE_DTIME_GROUP = (r"(?:[utUT]*{date}(?:[tT]{time02})?|[utUT]+{time02}|"
r"[utUT]*{time12})".format(date=_R_DATE, time02=_R_TIME02,
time12=_R_TIME12))
_R_DTIME = re.compile(r"(\s*{0}\s*)".format(RE_DTIME_GROUP))
# _R_DTIME_SLC is _R_DTIME that must begin with r'[ut]+'
RE_DTIME_SLC_GROUP = (r"[utUT]+(?:{date}(?:[tT]{time})?|{time})"
r"".format(date=_R_DATE, time=_R_TIME02))
_R_DTIME_SLC = re.compile(r"(\s*{0}\s*)".format(RE_DTIME_SLC_GROUP))
_emit_deprecated_float_warning = True
__all__ = ["prune_comp_sel", "raw_sel2sel_list", "fill_nd_sel_list",
"standardize_sel_list", "standardize_sel", "standardize_value",
"std_sel_list2index", "std_sel2index",
"sel_list2values", "sel2values", "selection2values",
"make_fwd_slice", "all_slices_none"
]
[docs]def prune_comp_sel(sel_list, comp_names):
"""Discover and remove vector component from selection list"""
comp_slc = slice(None)
comp_idx = None
# discover sel_names and rip out any vector-component slices if given
for i, s in enumerate(sel_list):
try:
s = s.strip().lower()
if s in comp_names:
if comp_idx is not None:
raise IndexError("Multiple vector component slices given, {0}"
"".format(tuple(sel_list)))
comp_slc = comp_names.index(s)
comp_idx = i
except (TypeError, AttributeError):
pass
if comp_idx is not None:
sel_list.pop(comp_idx)
comp_idx = None
return sel_list, comp_slc
[docs]def raw_sel2sel_list(sel):
"""Turn generic selection into something standard we can work with
Args:
sel (object): some slice selection
Returns:
(sel_list, input_type): items in sel_list are guarenteed to be
of type {slice, int, complex, string, numpy.ndarray}
Raises:
TypeError: slice-by-array invalid dtype
ValueError: slice-by-array not 1D
"""
# type(None) includes np.newaxis
valid_types = (slice, int, np.integer, float, np.floating, complex, np.complex,
np.complexfloating, datetime, np.datetime64, timedelta,
np.timedelta64, type(Ellipsis), type(None))
if isinstance(sel, tuple):
sel_list = list(sel)
# input_type = 'tuple'
elif isinstance(sel, list):
# DANGER, this is different behavior from tuple since sel
# will become an ndarray before we return... maybe
# supply a warning like numpy 1.15 does?
sel_list = [sel]
# input_type = 'list'
elif isinstance(sel, valid_types):
sel_list = [sel]
# input_type = 'single-value'
elif isinstance(sel, string_types):
sel = sel.replace('_', ',')
# mutate commas between brackets (they indicate strings)
if re.search(_R_MULTILEVEL_BRACKETS, sel) is not None:
raise IndexError("Slice-by-array in Viscid is limited to 1D "
"arrays since coordinate arrays become "
"ambiguous otherwise.")
sel = re.sub(_R_IN_BRACKS, lambda x: x.group(0).replace(",", "@"), sel)
sel_list = [s for s in sel.split(",")]
# put the commas back into the arrays
sel_list = [s.replace('@', ',') for s in sel_list]
# input_type = 'string'
else:
sel_list = [sel]
# input_type = 'other'
for i, s in enumerate(sel_list):
if not isinstance(s, valid_types + string_types):
sel_list[i] = np.asarray(s)
for _, s in enumerate(sel_list):
if isinstance(s, np.ndarray):
is_valid_dtype = (np.issubdtype(s.dtype, np.integer)
or np.issubdtype(s.dtype, np.complexfloating)
or np.issubdtype(s.dtype, np.bool_))
if not is_valid_dtype:
if s.dtype == np.object:
raise IndexError("Slice interpreted as slice-by-array of "
"object dtype. If you did\nnot intend to "
"slice-by-array, then you probably gave "
"an n-dimensional slice\nas a list instead "
"of a tuple... oops.")
raise IndexError("Slice-by-array with invalid dtype '{0}' (must "
"be (int, bool, complex, timedeta64, datetime64)."
"".format(s.dtype))
is_valid_shape = len(s.shape) == 1
if not is_valid_shape:
# print("what is s??", type(s), s.shape, s)
raise IndexError("Slice-by-array in Viscid is limited to 1D "
"arrays since coordinate arrays become "
"ambiguous otherwise.")
return sel_list
[docs]def fill_nd_sel_list(sel_list, ax_names):
"""fully determine a sparsely selected sel_list"""
if (len(sel_list) == 1 and not isinstance(sel_list[0], np.ndarray)
and sel_list in ([Ellipsis], ['...'], ['Ellipsis'], ['ellipsis'])):
# short circuit all logic if sel_list == [Ellipsis]
full_sel_list = [slice(None) for _ in ax_names]
full_ax_names = [name for name in ax_names]
full_newdim_flags = [False for _ in ax_names]
return full_sel_list, full_ax_names, full_newdim_flags
sel_list0 = tuple(sel_list)
sel_names = [None] * len(sel_list)
# discover sel_names
for i, s in enumerate(sel_list):
if isinstance(s, string_types) and '=' in s:
sel_names[i], sel_list[i] = [ss.strip() for ss in s.split('=', 1)]
# discover which items in sel_list are ellipsis or newaxis
pre_elip_newax_idxs = []
post_elip_newax_idxs = []
ellipsis_idx = -1
for i, s in enumerate(sel_list):
if isinstance(s, (list, np.ndarray)):
pass
elif s in (Ellipsis, ) or (isinstance(s, string_types)
and s.strip().lower() in ('ellipsis', '...')):
sel_list[i] = Ellipsis
if ellipsis_idx < 0:
ellipsis_idx = i
else:
raise IndexError("Only one ellipsis per slice please, {0}"
"".format(sel_list0))
elif s in (None, np.newaxis) or (isinstance(s, string_types)
and s.strip().lower() in ('none',
'newaxis')):
sel_list[i] = np.newaxis
if ellipsis_idx < 0:
pre_elip_newax_idxs.append(i)
else:
post_elip_newax_idxs.append(i)
newax_idxs = pre_elip_newax_idxs + post_elip_newax_idxs
n_newax = len(newax_idxs)
n_named_axes = len([None for name in sel_names if name is not None])
n_named_newaxes = len([None for sel, name in zip(sel_list, sel_names)
if name is not None and sel == np.newaxis])
# replace ellipsis with some number of slice(None)
if ellipsis_idx >= 0:
if n_named_axes > n_named_newaxes:
raise IndexError("Field indexing with Ellipsis can only be used "
"with named axes if those named axes are "
"numpy.newaxis, sel = {0}".format(sel_list0))
n_fill = len(ax_names) + n_newax - (len(sel_list) - 1)
idx = ellipsis_idx
sel_list = sel_list[:idx] + [slice(None)] * n_fill + sel_list[idx + 1:]
sel_names = sel_names[:idx] + [None] * n_fill + sel_names[idx + 1:]
newax_idxs = [i if i < idx else i + n_fill - 1 for i in newax_idxs]
# now let's assemble the final full slice list / ax names...
full_ax_names = []
full_sel_list = []
full_newdim_flags = []
remaining_ax_names = list(ax_names)
n_newax_seen = 0
n_unnamed_newax_seen = 0
for i, name, sel in zip(count(), sel_names, sel_list):
if name is None:
if i in newax_idxs:
name = 'new-x{0:d}'.format(n_unnamed_newax_seen)
n_unnamed_newax_seen += 1
n_newax_seen += 1
else:
name = remaining_ax_names.pop(0)
else: # named selection
if i in newax_idxs:
n_newax_seen += 1
if name in full_ax_names or name in ax_names:
raise IndexError("New axis duplicates name, {0}"
"".format(sel_list0))
else: # named selection that is not newaxis
if name in full_ax_names:
j = full_ax_names.index(name)
if full_sel_list[j] != slice(None):
raise IndexError("Axis '{0}' is repeated in slice {1}"
"".format(name, sel_list0))
full_ax_names[j] = name
full_sel_list[j] = sel
full_newdim_flags[j] = False
continue # <- poor style, sorry
else:
n_skipped = remaining_ax_names.index(name)
full_ax_names += remaining_ax_names[:n_skipped]
full_sel_list += [slice(None)] * n_skipped
full_newdim_flags += [False] * n_skipped
remaining_ax_names = remaining_ax_names[n_skipped + 1:]
full_ax_names.append(name)
full_sel_list.append(sel)
full_newdim_flags.append(i in newax_idxs)
if remaining_ax_names:
full_ax_names += remaining_ax_names
full_sel_list += [slice(None)] * len(remaining_ax_names)
full_newdim_flags += [False] * len(remaining_ax_names)
# print("sel0: ", sel_list0, '\n',
# "sel_list: ", sel_list, '\n',
# "full_ax_names: ", full_ax_names, '\n',
# "full_sel_list: ", full_sel_list, '\n',
# "full_newdim_flags: ", full_newdim_flags, '\n',
# "----\n",
# "len(full_ax_names): ", len(full_ax_names), '\n',
# "len(ax_names): ", len(ax_names), '\n',
# "n_newax: ", n_newax, '\n',
# sep='')
assert len(full_ax_names) == len(ax_names) + n_newax
assert len(full_sel_list) == len(ax_names) + n_newax
return full_sel_list, full_ax_names, full_newdim_flags
#######################################################################
def _warn_deprecated_float(val, varname='value'):
global _emit_deprecated_float_warning # pylint: disable=global-statement
if _emit_deprecated_float_warning:
frame = _user_written_stack_frame()
s = ("DEPRECATION...\n"
"Slicing by float is deprecated. Slicing by location is now \n"
"performed with an imaginary number, or a string with a trailing \n"
"'f', as in 0j, 'x=0j', or 'x=0f'. This warning comes from:\n"
" {0}:{1}\n"
" >>> {2}"
"".format(frame[1], frame[2], frame[4][0].strip()))
logger.warning(s)
_emit_deprecated_float_warning = False
def _is_time_str(s):
m = re.match(_R_DTIME, s)
return m is not None and m.end() - m.start() == len(s)
def _split_slice_str(sel):
all_times = re.findall(_R_DTIME_SLC, sel)
at_sel = re.sub(_R_DTIME_SLC, '@', sel)
split_sel = [s.strip() for s in at_sel.split(':')]
for s in all_times:
split_sel[split_sel.index('@')] = s
return split_sel
#######################################################################
[docs]def standardize_sel_list(sel_list):
"""turn all selection list elements into fundamental python types"""
for i, sel in enumerate(sel_list):
sel_list[i] = standardize_sel(sel)
return sel_list
[docs]def standardize_sel(sel):
"""turn selection list element into fundamental python types"""
if isinstance(sel, string_types):
sel = sel.strip().lower()
if sel[0] == '[' and sel[-1] == ']':
sel = standardize_value(sel, bool_argwhere=True)
elif ':' in sel:
sel = slice(*[standardize_value(s, bool_argwhere=True)
for s in _split_slice_str(sel)])
assert isinstance(sel.step, (type(None), int, np.integer))
assert not isinstance(sel.step, (np.datetime64, np.timedelta64))
else:
sel = standardize_value(sel, bool_argwhere=True)
elif isinstance(sel, slice):
sel = slice(*[standardize_value(s, bool_argwhere=True)
for s in [sel.start, sel.stop, sel.step]])
if sel.step is None:
sel = slice(sel.start, sel.stop, 1)
else:
sel = standardize_value(sel, bool_argwhere=True)
return sel
[docs]def standardize_value(sel, bool_argwhere=False):
"""Turn a value element to fundamental type or array
Returns:
One of the following::
- None
- np.newaxis
- Ellipsis
- bool
- int
- complex (slice by value)
- numpy.datetime64
- numpy.timedelta64
- ndarray
- numpy.integer
- numpy.bool\_
- numpy.timedelta64
- numpy.datetime64
- numpy.complex
"""
if isinstance(sel, (np.datetime64, np.timedelta64)):
pass
elif isinstance(sel, (int, np.integer)):
pass
elif isinstance(sel, (complex, np.complex, np.complexfloating)):
assert sel.real == 0.0
elif isinstance(sel, (float, np.floating)):
_warn_deprecated_float(sel)
sel = 1j * sel
elif isinstance(sel, (list, np.ndarray)):
assert len(sel.shape) == 1
assert isinstance(sel[0], (int, bool, np.integer, np.complex,
np.complexfloating, np.bool_))
if bool_argwhere and isinstance(sel[0], (bool, np.bool_)):
sel = np.argwhere(sel).reshape(-1)
elif sel in (np.newaxis, None, True, False, Ellipsis):
pass
elif isinstance(sel, string_types):
sel = sel.strip().lower()
if sel in ('newaxis', 'numpy.newaxis', 'np.newaxis'):
sel = np.newaxis
elif sel in ('none', ''):
sel = None
elif sel == 'true':
sel = True
elif sel == 'false':
sel = False
elif sel in ('...', 'ellipsis'):
sel = Ellipsis
elif sel[0] == '[' and sel[-1] == ']':
sel = sel[1:-1].replace(',', ' ')
if 'true' in sel or 'false' in sel:
sel = sel.lower()
sel = sel.replace('true', '1')
sel = sel.replace('false', '0')
_orig_sel = sel
sel = np.fromstring(sel, dtype='i', sep=' ')
if bool_argwhere:
sel = np.argwhere(sel).reshape(-1)
else:
sel = sel.astype(np.bool_)
if sel.shape == ():
raise ValueError("bool array as string did not parse: [{0}]"
"".format(_orig_sel))
else:
sel = sel.replace('f', 'j')
n_js = sel.count('j')
if n_js > 0:
_orig_sel = sel
sel = np.array(sel.split()).astype(np.complex)
assert np.allclose(sel.real, 0.0)
sel = sel.imag
if sel.shape == ():
raise ValueError("float array as string did not parse: "
"[{0}]".format(_orig_sel))
if n_js != len(sel):
_warn_deprecated_float(_orig_sel)
sel = 1j * sel
elif all(_is_time_str(s) for s in sel.split()):
try:
sel = as_timedelta64([s.lstrip('ut') for s in sel.split()])
except ValueError:
sel = as_datetime64([s.lstrip('ut') for s in sel.split()])
elif 'e' in sel or 'E' in sel:
_warn_deprecated_float(sel)
sel = np.fromstring(sel, dtype=np.float, sep=' ')
else:
sel = np.fromstring(sel, dtype=np.integer, sep=' ')
elif _is_time_str(sel):
try:
sel = as_timedelta64(sel.lstrip('ut'))
except ValueError:
sel = as_datetime64(sel.lstrip('ut'))
else:
try:
sel = int(sel)
except ValueError:
try:
if 'j' in sel or 'f' in sel:
sel = sel.replace('f', 'j')
sel = complex(sel)
else:
sel = float(sel)
_warn_deprecated_float(sel)
sel = 1j * sel
except ValueError:
raise ValueError("Unexpected std type '{0}'".format(sel))
elif is_timedelta_like(sel, conservative=True):
sel = as_timedelta64(sel)
elif is_datetime_like(sel, conservative=True):
sel = as_datetime64(sel)
else:
raise ValueError("Unexpected std type '{0}' ({1})"
"".format(sel, type(sel)))
return sel
#######################################################################
[docs]def std_sel_list2index(std_sel_list, crd_arrs, val_endpoint=True, interior=False,
tdunit='s', epoch=None, tol=100):
"""turn standardized selection list into index slice"""
return [std_sel2index(std_sel, crd_arr, val_endpoint=val_endpoint,
interior=interior, tdunit=tdunit, epoch=epoch)
for std_sel, crd_arr in zip(std_sel_list, crd_arrs)
]
[docs]def std_sel2index(std_sel, crd_arr, val_endpoint=True, interior=False,
tdunit='s', epoch=None):
"""Turn single standardized selection into slice (by int or None)
Normally (val_endpoint=True, interior=False), the rules for float
lookup val_endpoints are::
- The slice will never include an element whose value in arr
is < start (or > if the slice is backward)
- The slice will never include an element whose value in arr
is > stop (or < if the slice is backward)
- !! The slice WILL INCLUDE stop if you don't change
val_endpoint. This is different from normal slicing, but
it's more natural when specifying a slice as a float.
If interior=True, then the slice is expanded such that start and
stop are interior to the sliced array.
Args:
std_sel: single standardized selection
arr (ndarray): filled with floats to do the lookup
val_endpoint (bool): iff True then include stop in the slice when
slicing-by-value (DOES NOT EFFECT SLICE-BY-INDEX).
Set to False to get python slicing symantics when it
comes to excluding stop, but fair warning, python
symantics feel awkward here. Consider the case
[0.1, 0.2, 0.3][:0.25]. If you think this should include
0.2, then leave keep val_endpoint=True.
interior (bool): if True, then extend both ends of the slice
such that slice-by-location endpoints are interior to the
slice
epoch (datetime64-like): Epoch for to go datetime64 <-> float
tdunit (str): Presumed time unit for floats
tol (int): number of machine epsilons to consider
"close enough"
"""
idx = None
if interior and not val_endpoint:
logger.warning("For interior slices, val_endpoint must be True, I'll "
"change that for you.")
val_endpoint = True
if isinstance(std_sel, slice):
assert isinstance(std_sel.step, (int, np.integer, type(None)))
start_val = None
stop_val = None
orig_step = std_sel.step
ustep = 1 if std_sel.step is None else int(std_sel.step)
sgn = np.sign(ustep)
if (isinstance(std_sel.start, (int, np.integer, type(None)))
and not isinstance(std_sel.start, (np.datetime64, np.timedelta64))):
ustart = std_sel.start
else:
ustart, tol = _unify_sbv_types(std_sel.start, crd_arr, tdunit='s',
epoch=epoch)
start_val = ustart
diff = crd_arr - ustart + (tol * sgn)
zero = np.array([0]).astype(diff.dtype)[0]
if ustep > 0:
diff = np.ma.masked_less(diff, zero)
else:
diff = np.ma.masked_greater(diff, zero)
if np.ma.count(diff) == 0:
# start value is past the wrong end of the array
if ustep > 0:
ustart = len(crd_arr)
else:
# start = -len(arr) - 1
# having a value < -len(arr) won't play
# nice with make_fwd_slice, but in this
# case, the slice will have no data, so...
return slice(0, 0, ustep)
else:
ustart = np.argmin(np.abs(diff))
if (isinstance(std_sel.stop, (int, np.integer, type(None)))
and not isinstance(std_sel.stop, (np.datetime64, np.timedelta64))):
ustop = std_sel.stop
else:
ustop, tol = _unify_sbv_types(std_sel.stop, crd_arr, tdunit='s',
epoch=epoch)
stop_val = ustop
diff = crd_arr - ustop - (tol * sgn)
zero = np.array([0]).astype(diff.dtype)[0]
if ustep > 0:
diff = np.ma.masked_greater(diff, zero)
else:
diff = np.ma.masked_less(diff, zero)
if ustep > 0:
if ustop < crd_arr[0]:
# stop value is past the wong end of the array
ustop = 0
else:
ustop = int(np.argmin(np.abs(diff)))
if val_endpoint:
ustop += 1
else:
if ustop > crd_arr[-1]:
# stop value is past the wrong end of the array
ustop = len(crd_arr)
else:
ustop = int(np.argmin(np.abs(diff)))
if val_endpoint:
if ustop > 0:
ustop -= 1
else:
# 0 - 1 == -1 which would wrap to the end of
# of the array... instead, just make it None
ustop = None
idx = slice(ustart, ustop, orig_step)
if interior:
_a, _b, _c = _interiorize_slice(crd_arr, start_val, stop_val,
idx.start, idx.stop, idx.step)
idx = slice(_a, _b, _c)
else:
# slice by single value or ndarray of single values (int, float, times)
usel, _ = _unify_sbv_types(std_sel, crd_arr, tdunit='s', epoch=epoch)
if (isinstance(usel, (int, np.integer, type(None)))
and not isinstance(usel, (np.datetime64, np.timedelta64))):
idx = usel
elif isinstance(usel, np.ndarray):
if isinstance(usel[0, 0], np.integer):
idx = usel.reshape(-1)
else:
idx = np.argmin(np.abs(crd_arr.reshape(-1, 1) - usel), axis=0)
else:
idx = np.argmin(np.abs(crd_arr - usel))
return idx
def _unify_sbv_types(std_val, crd_arr, tdunit='s', epoch=None):
uval = None
tol = None
if std_val is None:
pass
elif (isinstance(std_val, (int, np.integer))
and not isinstance(std_val, (np.datetime64, np.timedelta64))):
uval, tol = std_val, 0
elif (isinstance(std_val, np.ndarray) and isinstance(std_val[0], np.integer)
and not isinstance(std_val[0], (np.datetime64, np.timedelta64))):
uval, tol = std_val.reshape(1, -1), 0
else:
ndarray_slice = isinstance(std_val, np.ndarray)
std_val = np.asarray(std_val)
len_std_val = 1 if std_val.shape == () else len(std_val)
std_val = std_val.reshape(1, len_std_val)
if isinstance(std_val[0, 0], (np.complex, np.complexfloating)):
assert np.all(std_val.real == 0)
std_val = std_val.imag
if crd_arr is None:
crd_arr = np.array([0], dtype=std_val.dtype)
else:
crd_arr = np.asarray(crd_arr)
# ----
if isinstance(crd_arr[0], type(std_val[0, 0])):
uval = std_val
elif isinstance(crd_arr[0], np.timedelta64):
if isinstance(std_val[0, 0], np.floating):
uval = as_timedelta64(std_val, unit=tdunit)
elif isinstance(std_val[0, 0], np.datetime64):
if epoch is None:
raise NotImplementedError("Can't slice-by-location a timedelta64 "
"axis using datetime64 value without "
"epoch")
else:
uval = time_diff(std_val, epoch, most_precise=True)
elif isinstance(crd_arr[0], np.datetime64):
if isinstance(std_val[0, 0], np.floating):
std_val = as_timedelta64(std_val, unit=tdunit)
uval = crd_arr[0] + std_val
elif isinstance(std_val[0, 0], np.timedelta64):
uval = crd_arr[0] + std_val
elif isinstance(crd_arr[0], np.floating):
if isinstance(std_val[0, 0], np.timedelta64):
uval = std_val / as_timedelta64(1, unit=tdunit)
elif isinstance(std_val[0, 0], np.datetime64):
if epoch is None:
raise NotImplementedError("Can't slice-by-location a floating pt. "
"axis using datetime64 value without "
"epoch")
else:
uval = time_diff(std_val, epoch, most_precise=True)
uval = uval / as_timedelta64(1, unit=tdunit)
elif isinstance(std_val[0, 0], np.floating):
uval = std_val.astype(crd_arr.dtype)
else:
raise NotImplementedError("coordinates dtype {0} can not be "
"sliced by value".format(type(crd_arr[0])))
# ----
if uval is None:
tol = None
elif isinstance(uval[0, 0], np.floating):
if len(crd_arr) > 1:
tol = 0.0001 * np.min(np.diff(crd_arr))
else:
tol = 1.0
elif isinstance(uval[0, 0], np.datetime64):
tol = as_timedelta64(0, 's')
elif isinstance(uval[0, 0], np.timedelta64):
tol = as_timedelta64(0, 's')
if uval is not None and not ndarray_slice:
uval = uval[0, 0]
return uval, tol
def _interiorize_slice(arr, start_val, stop_val, start, stop, step,
verify=True):
"""Ensure start_val and stop_val interior to the sliced array
Args:
arr (sequence): sequence being sliced
start_val (None, float): Value used to find start index, or
None to not adjust start
stop_val (None, float): Value used to find stop index, or
None to net adjust stop
start (int): start of slice
stop (int): stop of slice
step (int): step of slice
verify (bool): verify that start_val and stop_val are interior
to the resulting sequence
Returns:
(start, stop, step), adusted sliced start:stop:step indices
"""
step = 1 if step is None else step
if start_val is not None and start is not None:
start_slcval = arr[start]
if step > 0 and start_slcval > start_val:
# print("ADJUSTING start fwd")
if start == 0:
start = None
else:
start -= 1
elif step < 0 and start_slcval < start_val:
# print("ADJUSTING start rev")
if start == -1:
start = None
else:
start += 1
if stop_val is not None and stop is not None:
if step > 0:
stop_slcval = arr[0] if stop == 0 else arr[stop - 1]
if stop_slcval < stop_val:
# print("ADJUSTING stop fwd")
if stop == -1:
stop = None
else:
stop += 1
elif step < 0:
stop_slcval = arr[-1] if stop in (-1, len(arr)) else arr[stop + 1]
if stop_slcval > stop_val:
# print("ADJUSTING stop rev")
if stop == 0:
stop = None
else:
stop -= 1
# for debug, check that interior does what it says
if verify:
subarr = arr[start:stop:step]
ends = [v for v in sorted([subarr[0], subarr[-1]])]
for v in [start_val, stop_val]:
if v is not None:
if v < ends[0] or v > ends[-1]:
raise RuntimeError("Logic issue in interiorize, "
"v: {0} ends: {1}".format(v, ends))
return start, stop, step
#######################################################################
def raw_sel2values(arrs, selection, epoch=None, tdunit='s'):
return sel_list2values(arrs, raw_sel2sel_list(selection),
epoch=epoch, tdunit=tdunit)
[docs]def sel_list2values(arrs, sel_list, epoch=None, tdunit='s'):
"""find the extrema values for a given sel list"""
if arrs is None:
arrs = [None] * len(sel_list)
return [sel2values(arr, sel, epoch=epoch, tdunit=tdunit)
for arr, sel in zip(arrs, sel_list)]
[docs]def sel2values(arr, sel, epoch=None, tdunit='s'):
"""find the extrema values for a given selection
Args:
arr (None, sequence): array that is being selected, if this is
None, then the output will contain np.nan where it can not
infer values.
selection (slice, str): a single selection that could be given
to :py:func:`to_slice`
epoch (datetime64-like): Epoch for to go datetime64 <-> float
tdunit (str): Presumed time unit for floats
Returns:
(start_val, stop_val) as floats
- If arr is None and start/stop are None, then they will become
+/- inf depending on the sign of step.
- If arr is None and start/stop are slice-by-index, they will
become NaN.
"""
ret = None
std_sel = standardize_sel(sel)
if isinstance(std_sel, slice):
if std_sel.start is None or hasattr(std_sel.start, '__index__'):
if arr is None:
if std_sel.start is None and std_sel.step > 0:
ustart = -np.inf
elif std_sel.start is None and std_sel.step < 0:
ustart = np.inf
else:
ustart = np.nan
else:
if std_sel.start is None:
idx = 0 if std_sel.step > 0 else -1
else:
idx = int(std_sel.start)
ustart = arr[idx]
else:
ustart, _ = _unify_sbv_types(std_sel.start, arr, tdunit=tdunit,
epoch=epoch)
if std_sel.stop is None or hasattr(std_sel.stop, '__index__'):
if arr is None:
if std_sel.stop is None and std_sel.step > 0:
ustop = np.inf
elif std_sel.stop is None and std_sel.step < 0:
ustop = -np.inf
else:
ustop = np.nan
else:
if std_sel.stop is None:
idx = -1 if std_sel.step > 0 else 0
else:
idx = int(std_sel.stop)
ustop = arr[idx]
else:
ustop, _ = _unify_sbv_types(std_sel.stop, arr, tdunit=tdunit,
epoch=epoch)
ret = (ustart, ustop)
else:
if std_sel in (None, np.newaxis):
ret = (-np.inf, np.inf)
elif hasattr(std_sel, "__index__"):
if arr is None:
ret = (np.nan, np.nan)
else:
ret = tuple([arr[std_sel]] * 2)
else:
uval, _ = _unify_sbv_types(std_sel, arr, tdunit=tdunit, epoch=epoch)
ret = tuple([uval] * 2)
return ret
selection2values = sel2values
#######################################################################
# FIXME: is this graceful on slice-by-ndarray?
[docs]def make_fwd_slice(shape, slices, reverse=None, cull_second=True):
"""Make sure slices go forward
This function returns two slices equivalent to `slices` such
that the first slice always goes forward. This is necessary because
h5py can't deal with reverse slices such as [::-1].
The optional `reverse` can be used to interpret a dimension as
flipped. This is used if the indices in a slice are based on a
coordinate array that has already been flipped. For instance, the
result is equivalent to `arr[::-1][slices]`, but in a way that can
be handled by h5py. This lets us efficiently load small subsets
of large arrays on disk, which is most useful when the large array
is coming through sshfs.
Note:
The only restriction on slices is that neither start nor stop
can be outide the range [-L, L].
Args:
shape: shape of the array that is to be sliced
slices: a tuple of slices to work with
reverse (optional): list of bools that indicate if the
corresponding value in slices should be ineterpreted as
flipped
cull_second (bool, optional): iff True, remove elements of
the second slice for dimensions that don't exist after
the first slice has completed. This is only here for
a super-hacky case when slicing fields.
Returns:
(first_slice, second_slice)
* first_slice: a forward-only slice that retrieves the
desired elements of an array
* second_slice: a slice that does [::1] or [::-1] as needed
to make the result equivalent to slices. If keep_all,
then this may contain None indicating that this
dimension no longer exists after the first slice.
Examples:
>> a = np.arange(8)
>> first, second = make_fwd_slice(len(a),slice(None, None, -1))
>> (a[::-1] == a[first][second]).all()
True
>> a = np.arange(4*5*6).reshape((4, 5, 6))
>> first, second = make_fwd_slice(a.shape,
>> [slice(None, -1, 1),
>> slice(-1, None, 1),
>> slice(-4, -1, 2)],
>> [True, True, True])
>> a1 = a[::-1, ::-1, ::-1][:-1, -1:, -4:-1:2]
>> a2 = a[first][second]
>> a1 == a2
True
"""
if reverse is None:
reverse = []
if not isinstance(shape, (list, tuple, np.ndarray)):
shape = [shape]
if not isinstance(slices, (list, tuple)):
slices = [slices]
if not isinstance(reverse, (list, tuple)):
reverse = [reverse]
newax_inds = [i for i, x in enumerate(slices) if x == np.newaxis]
shape = list(shape)
for i in newax_inds:
shape.insert(i, 1)
# ya know, lets just go through all the dimensions in shape
# just to be safe and default to an empty slice / no reverse
slices = slices + [slice(None)] * (len(shape) - len(slices))
reverse = reverse + [False] * (len(slices) - len(reverse))
first_slc = [slice(None)] * len(slices)
second_slc = [slice(None, None, 1)] * len(first_slc)
for i, slc, L, rev in izip(count(), slices, shape, reverse):
if isinstance(slc, slice):
step = slc.step if slc.step is not None else 1
start = slc.start if slc.start is not None else 0
stop = slc.stop if slc.stop is not None else L
if start < 0:
start += L
if stop < 0:
stop += L
# sanity check the start/stop since we're gunna be playing
# fast and loose with them
if start < 0 or stop < 0:
raise IndexError("((start = {0}) or (stop = {1})) < 0"
"".format(start, stop))
if start > L or stop > L:
raise IndexError("((start={0}) or (stop={1})) > (L={2})"
"".format(start, stop, L))
# now do the math of flipping the slice if needed, these branches
# change start, stop, and step so they can be used to create a new
# slice below
if rev:
if step < 0:
step = -step
if slc.start is None:
start = L - 1
if slc.stop is None:
start = L - 1 - start
stop = None
else:
start, stop = L - 1 - start, L - 1 - stop
else:
start, stop = L - stop, L - start
start += ((stop - 1 - start) % step)
second_slc[i] = slice(None, None, -1)
elif step < 0:
step = -step
if slc.start is None:
start = L - 1
if slc.stop is None:
start, stop = 0, start + 1
start = ((stop - 1 - start) % step)
else:
start, stop = stop + 1, start + 1
start += ((stop - 1 - start) % step)
second_slc[i] = slice(None, None, -1)
# check that our slice is valid
assert start is None or (start >= 0 and start <= L), \
"start (={0}) is outside range".format(start)
assert start is None or (start >= 0 and start <= L), \
"start (={0}) is outside range".format(start)
assert start is None or stop is None or start == stop == 0 or \
start < stop, "bad slice ordering: {0} !< {1}".format(start, stop)
assert step > 0
slc = slice(start, stop, step)
elif isinstance(slc, (int, np.integer)):
second_slc[i] = None
if rev:
slc = (L - 1) - slc
elif slc == np.newaxis:
second_slc[i] = "NEWAXIS"
first_slc[i] = slc
first_slc = [s for s in first_slc if s is not np.newaxis]
if cull_second:
second_slc = [s for s in second_slc if s is not None]
second_slc = [np.newaxis if s == "NEWAXIS" else s for s in second_slc]
return first_slc, second_slc
#######################################################################
[docs]def all_slices_none(slices):
'''true iff all slices have no effect'''
return all(not isinstance(s, (list, np.ndarray))
and s in (slice(None), slice(None, None, 1))
for s in slices)
def _user_written_stack_frame():
"""get the frame of first stack frame outside Viscid"""
import inspect
import os
stack = inspect.stack()
frame_info = None
for frame_info in stack:
if 'viscid' not in os.path.normpath(frame_info[1]):
break
return frame_info
##
## EOF
##