# Source code for ccdproc.utils.slices

```# Licensed under a 3-clause BSD style license - see LICENSE.rst

"""
Define utility functions and classes for ccdproc
"""

__all__ = ["slice_from_string"]

[docs]def slice_from_string(string, fits_convention=False):
"""
Convert a string to a tuple of slices.

Parameters
----------

string : str
A string that can be converted to a slice.

fits_convention : bool, optional
If True, assume the input string follows the FITS convention for
indexing: the indexing is one-based (not zero-based) and the first
axis is that which changes most rapidly as the index increases.

Returns
-------

slice_tuple : tuple of slice objects
A tuple able to be used to index a numpy.array

Notes
-----

The ``string`` argument can be anything that would work as a valid way to
slice an array in Numpy. It must be enclosed in matching brackets; all
spaces are stripped from the string before processing.

Examples
--------

>>> import numpy as np
>>> arr1d = np.arange(5)
>>> a_slice = slice_from_string('[2:5]')
>>> arr1d[a_slice]
array([2, 3, 4])
>>> a_slice = slice_from_string('[ : : -2] ')
>>> arr1d[a_slice]
array([4, 2, 0])
>>> arr2d = np.array([arr1d, arr1d + 5, arr1d + 10])
>>> arr2d
array([[ 0,  1,  2,  3,  4],
[ 5,  6,  7,  8,  9],
[10, 11, 12, 13, 14]])
>>> a_slice = slice_from_string('[1:-1, 0:4:2]')
>>> arr2d[a_slice]
array([[5, 7]])
>>> a_slice = slice_from_string('[0:2,0:3]')
>>> arr2d[a_slice]
array([[0, 1, 2],
[5, 6, 7]])
"""
no_space = string.replace(' ', '')

if not no_space:
return ()

if not (no_space.startswith('[') and no_space.endswith(']')):
raise ValueError('Slice string must be enclosed in square brackets.')

no_space = no_space.strip('[]')
if fits_convention:
# Special cases first
# Flip dimension, with step
no_space = no_space.replace('-*:', '::-')
# Flip dimension
no_space = no_space.replace('-*', '::-1')
# Normal wildcard
no_space = no_space.replace('*', ':')
string_slices = no_space.split(',')
slices = []
for string_slice in string_slices:
slice_args = [int(arg) if arg else None
for arg in string_slice.split(':')]
a_slice = slice(*slice_args)
slices.append(a_slice)

if fits_convention:
slices = _defitsify_slice(slices)

return tuple(slices)

def _defitsify_slice(slices):
"""
Convert a FITS-style slice specification into a python slice.

This means two things:
+ Subtract 1 from starting index because in the FITS
specification arrays are one-based.
+ Do **not** subtract 1 from the ending index because the python
convention for a slice is for the last value to be one less than the
stop value. In other words, this subtraction is already built into
python.
+ Reverse the order of the slices, because the FITS specification dictates
that the first axis is the one along which the index varies most rapidly
(aka FORTRAN order).
"""

python_slice = []
for a_slice in slices[::-1]:
new_start = a_slice.start - 1 if a_slice.start is not None else None
if new_start is not None and new_start < 0:
raise ValueError("Smallest permissible FITS index is 1")
if a_slice.stop is not None and a_slice.stop < 0:
raise ValueError("Negative final index not allowed for FITS slice")
new_slice = slice(new_start, a_slice.stop, a_slice.step)
if (a_slice.start is not None and a_slice.stop is not None and
a_slice.start > a_slice.stop):
# FITS use a positive step index when dimension are inverted
new_step = -1 if a_slice.step is None else -a_slice.step
# Special case to prevent -1 as slice stop value
new_stop = None if a_slice.stop == 1 else a_slice.stop-2
new_slice = slice(new_start, new_stop, new_step)
python_slice.append(new_slice)

return python_slice
```