예제 #1
0
파일: utils.py 프로젝트: 1313e/e13Tools
    def do_substitution(target):
        # Check if target has a docstring that can be substituted to
        if target.__doc__:
            # Make a copy of the target docstring to check formatting later
            doc_presub = str(target.__doc__)

            # Try to use %-formatting
            try:
                target.__doc__ = target.__doc__ % (params)
            # If that raises an error, use .format with *args
            except TypeError:
                target.__doc__ = target.__doc__.format(*params)
            # Using **kwargs with % raises no errors if .format is required
            else:
                # Check if formatting was done and use .format if not
                if (target.__doc__ == doc_presub):
                    target.__doc__ = target.__doc__.format(**params)

        # Raise error if target has no docstring
        else:
            raise InputError("Target has no docstring available for "
                             "substitutions!")

        # Return the target definition
        return (target)
예제 #2
0
파일: utils.py 프로젝트: 1313e/e13Tools
def get_outer_frame(func):
    """
    Checks whether or not the calling function contains an outer frame
    corresponding to `func` and returns it if so. If this frame cannot be
    found, returns *None* instead.

    Parameters
    ----------
    func : function
        The function or method whose frame must be located in the outer frames.

    Returns
    -------
    outer_frame : frame or None
        The requested outer frame if it was found, or *None* if it was not.

    """

    # If func is a function, obtain its name and module name
    if isfunction(func):
        name = func.__name__
        module_name = func.__module__
    # Else, if func is a method, obtain its name and class object
    elif ismethod(func):
        name = func.__name__
        class_obj = func.__self__.__class__
    # Else, raise error
    else:
        raise InputError("Input argument 'func' must be a callable function or"
                         " method!")

    # Obtain the caller's frame
    caller_frame = currentframe().f_back

    # Loop over all outer frames
    for frame_info in getouterframes(caller_frame):
        # Check if frame has the correct name
        if (frame_info.function == name):
            # If func is a function, return if module name is also correct
            if (isfunction(func)
                    and frame_info.frame.f_globals['__name__'] == module_name):
                return (frame_info.frame)

            # Else, return frame if class is also correct
            elif (frame_info.frame.f_locals['self'].__class__ is class_obj):
                return (frame_info.frame)
    else:
        return (None)
예제 #3
0
파일: utils.py 프로젝트: 1313e/e13Tools
def docstring_substitute(*args, **kwargs):
    """
    Custom decorator that allows either given positional arguments `args` or
    keyword arguments `kwargs` to be substituted into the docstring of the
    target function/class.

    Both `%` and `.format()` string formatting styles are supported. Keep in
    mind that this decorator will always attempt to do %-formatting first, and
    only uses `.format()` if the first fails.

    """

    # Check if solely args or kwargs were provided
    if len(args) and len(kwargs):
        raise InputError("Either only positional or keyword arguments are "
                         "allowed!")
    else:
        params = args or kwargs

    # This function performs the docstring substitution on a given definition
    def do_substitution(target):
        # Check if target has a docstring that can be substituted to
        if target.__doc__:
            # Make a copy of the target docstring to check formatting later
            doc_presub = str(target.__doc__)

            # Try to use %-formatting
            try:
                target.__doc__ = target.__doc__ % (params)
            # If that raises an error, use .format with *args
            except TypeError:
                target.__doc__ = target.__doc__.format(*params)
            # Using **kwargs with % raises no errors if .format is required
            else:
                # Check if formatting was done and use .format if not
                if (target.__doc__ == doc_presub):
                    target.__doc__ = target.__doc__.format(**params)

        # Raise error if target has no docstring
        else:
            raise InputError("Target has no docstring available for "
                             "substitutions!")

        # Return the target definition
        return (target)

    # Return decorator function
    return (do_substitution)
예제 #4
0
파일: utils.py 프로젝트: 1313e/e13Tools
def check_instance(instance, cls):
    """
    Checks if provided `instance` has been initialized from a proper `cls`
    (sub)class. Raises a :class:`~TypeError` if `instance` is not an instance
    of `cls`.

    Parameters
    ----------
    instance : object
        Class instance that needs to be checked.
    cls : class
        The class which `instance` needs to be properly initialized from.

    Returns
    -------
    result : bool
        Bool indicating whether or not the provided `instance` was initialized
        from a proper `cls` (sub)class.

    """

    # Check if cls is a class
    if not isclass(cls):
        raise InputError("Input argument 'cls' must be a class!")

    # Check if instance was initialized from a cls (sub)class
    if not isinstance(instance, cls):
        raise TypeError("Input argument 'instance' must be an instance of the "
                        "%s.%s class!" % (cls.__module__, cls.__name__))

    # Retrieve a list of all cls attributes
    class_attrs = dir(cls)

    # Check if all cls attributes can be called in instance
    for attr in class_attrs:
        if not hasattr(instance, attr):
            return (False)
    else:
        return (True)
예제 #5
0
파일: numpy.py 프로젝트: 1313e/e13Tools
def sort2D(array, axis=-1, order=None):
    """
    Sorts a 2D `array` over a given `axis` in the specified `order`. This
    function is different from NumPy's :func:`~numpy.sort` function in that it
    sorts over a given axis rather than along it, and the order can be given as
    integers rather than field strings.

    Parameters
    ----------
    array : 2D array_like
        Input array that requires sorting.

    Optional
    --------
    axis : int. Default: -1
        Axis over which to sort the elements. Default is to sort all elements
        over the last axis. A negative value counts from the last to the first
        axis.
    order : int, 1D array_like of int or None. Default: None
        The order in which the vectors in the given `axis` need to be sorted.
        Negative values count from the last to the first vector.
        If *None*, all vectors in the given `axis` are sorted individually.

    Returns
    -------
    array_sort : 2D :obj:`~numpy.ndarray` object
        Input `array` with its `axis` sorted in the specified `order`.

    Examples
    --------
    Sorting the column elements of a given 2D array with no order specified:

        >>> array = np.array([[0, 5, 1], [7, 4, 9], [3, 13, 6], [0, 1, 8]])
        >>> array
        array([[ 0,  5,  1],
               [ 7,  4,  9],
               [ 3, 13,  6],
               [ 0,  1,  8]])
        >>> sort2D(array)
        array([[ 0,  1,  1],
               [ 0,  4,  6],
               [ 3,  5,  8],
               [ 7, 13,  9]])


    Sorting the same array in only the first column:

        >>> sort2D(array, order=0)
        array([[ 0,  5,  1],
               [ 0,  1,  8],
               [ 3, 13,  6],
               [ 7,  4,  9]])


    Sorting all three columns in order:

        >>> sort2D(array, order=(0, 1, 2))
        array([[ 0,  1,  8],
               [ 0,  5,  1],
               [ 3, 13,  6],
               [ 7,  4,  9]])


    Sorting all three columns in a different order:

        >>> sort2D(array, order=(0, 2, 1))
        array([[ 0,  5,  1],
               [ 0,  1,  8],
               [ 3, 13,  6],
               [ 7,  4,  9]])

    """

    # Make sure that input array is a numpy array
    array = np.array(array)

    # Check if array is indeed 2D
    if (array.ndim != 2):
        raise ShapeError("Input argument 'array' must be two-dimensional!")
    else:
        # Obtain the number of vectors along the given axis
        try:
            n_vec = array.shape[axis]
        except Exception as error:
            raise InputError("Input argument 'axis' is invalid (%s)!" %
                             (error))

    # Move the given axis to be the first axis
    array = np.moveaxis(array, axis, 0)

    # If order is given, transform it into an array
    if order is not None:
        order = np.array(order, ndmin=1)

    # Check what order is given and act accordingly
    if order is None:
        array.sort(axis=-1)
    elif not (((-n_vec <= order) * (order < n_vec)).all()):
        raise ValueError("Input argument 'order' contains values that are "
                         "out of bounds!")
    else:
        for i in reversed(order):
            array = array[:, np.argsort(array[i], kind='mergesort')]

    # Return the resulting array back after transforming its axes back
    return (np.moveaxis(array, 0, axis))
예제 #6
0
파일: numpy.py 프로젝트: 1313e/e13Tools
def rot90(array, axes=(0, 1), rot_axis='center', n_rot=1):
    """
    Rotates the given `array` by 90 degrees around the point `rot_axis` in the
    given `axes`. This function is different from NumPy's :func:`~numpy.rot90`
    function in that every column (2nd axis) defines a different dimension
    instead of every individual axis.

    Parameters
    ----------
    array : 2D array_like
        Array with shape [`n_pts`, `n_dim`] with `n_pts` the number of points
        and `n_dim` the number of dimensions. Requires: `n_dim` > 1.

    Optional
    --------
    axes : 1D array_like with 2 ints. Default: (0, 1)
        Array containing the axes defining the rotation plane. Rotation is from
        the first axis towards the second. Can be omitted if `rot_axis` has
        length `n_dim`.
    rot_axis : 1D array_like of length 2/`n_dim` or 'center'. Default: 'center'
        If 'center', the rotation axis is chosen in the center of the minimum
        and maximum values found in the given `axes`.
        If 1D array of length 2, the rotation axis is chosen around the given
        values in the given `axes`.
        If 1D array of length `n_dim`, the rotation axis is chosen around the
        first two non-zero values.
    n_rot : int. Default: 1
        Number of times to rotate `array` by 90 degrees.

    Returns
    -------
    array_rot : 2D :obj:`~numpy.ndarray` object
        Array with shape [`n_pts`, `n_dim`] that has been rotated by 90 degrees
        `n_rot` times.

    Examples
    --------
    Using an array with just two dimensions:

        >>> array = np.array([[0.75, 0], [0.25, 1], [1, 0.75], [0, 0.25]])
        >>> rot90(array)
        array([[ 1.  ,  0.75],
               [ 0.  ,  0.25],
               [ 0.25,  1.  ],
               [ 0.75,  0.  ]])


    Using the same array, but rotating it around a different point:

        >>> array = np.array([[0.75, 0], [0.25, 1], [1, 0.75], [0, 0.25]])
        >>> rot90(array, rot_axis=[0.2, 0.7])
        array([[ 0.9 ,  1.25],
               [-0.1 ,  0.75],
               [ 0.15,  1.5 ],
               [ 0.65,  0.5 ]])

    """

    # Make sure that array is a numpy array
    array = np.asarray(array)

    # Check if array is indeed two-dimensional and obtain the lengths
    if (array.ndim != 2):
        raise ShapeError("Input argument 'array' must be two-dimensional!")
    else:
        n_pts, n_dim = array.shape

    # Check axes
    axes = np.asarray(axes)
    if (axes.ndim == 1 and axes.shape[0] == 2 and (axes < n_dim).all()):
        pass
    else:
        raise InputError("Input argument 'axes' has invalid shape or values!")

    # Check what rot_axis is and act accordingly
    if (rot_axis == 'center'):
        rot_axis = np.zeros(2)
        rot_axis[0] =\
            abs(np.max(array[:, axes[0]])+np.min(array[:, axes[0]]))/2
        rot_axis[1] =\
            abs(np.max(array[:, axes[1]])+np.min(array[:, axes[1]]))/2
    elif (isinstance(rot_axis, str)):
        raise ValueError("Input argument 'rot_axis' can only have 'center' as"
                         " a string value!")
    else:
        rot_axis = np.asarray(rot_axis)
        if (rot_axis.ndim == 1 and rot_axis.shape[0] == 2):
            pass
        elif (rot_axis.ndim == 1 and rot_axis.shape[0] == n_dim):
            axes = []
            for i in range(n_dim):
                if (rot_axis[i] != 0):
                    axes.append(i)
                if (len(axes) == 2):
                    break
            else:
                raise ValueError("Input argument 'rot_axis' does not have two "
                                 "non-zero values!")
            rot_axis = rot_axis[axes]
        else:
            raise ShapeError("Input argument 'rot_axis' has invalid shape!")

    # Calculate the rotated matrix
    array_rot = array.copy()
    if (n_rot % 4 == 0):
        return (array_rot)
    elif (n_rot % 4 == 1):
        array_rot[:, axes[0]] = rot_axis[0] + rot_axis[1] - array[:, axes[1]]
        array_rot[:, axes[1]] = rot_axis[1] - rot_axis[0] + array[:, axes[0]]
    elif (n_rot % 4 == 2):
        array_rot[:, axes[0]] = 2 * rot_axis[0] - array[:, axes[0]]
        array_rot[:, axes[1]] = 2 * rot_axis[1] - array[:, axes[1]]
    elif (n_rot % 4 == 3):
        array_rot[:, axes[0]] = rot_axis[0] - rot_axis[1] + array[:, axes[1]]
        array_rot[:, axes[1]] = rot_axis[1] + rot_axis[0] - array[:, axes[0]]
    else:
        raise InputError("Input argument 'n_rot' is invalid!")

    # Return it
    return (array_rot)
예제 #7
0
파일: numpy.py 프로젝트: 1313e/e13Tools
def isin(array1, array2, axis=0, assume_unique=False, invert=False):
    """
    Checks over the provided `axis` which elements of given `array1` are also
    in given `array2` and returns it.

    This is an nD-version of NumPy's :func:`~numpy.isin` function.

    Parameters
    ----------
    array1 : array_like
        Input array.
    array2 : array_like
        Comparison array with same shape as `array1` except in given `axis`.

    Optional
    --------
    axis : int or None. Default: 0
        Axis over which elements must be checked in both arrays. A negative
        value counts from the last to the first axis.
        If *None*, both arrays are compared element-wise (this is the
        functionality of :func:`~numpy.isin`).
    assume_unique : bool. Default: False
        Whether to assume that the elements in both arrays are unique, which
        can speed up the calculation.
    invert : bool. Default: False
        Whether to invert the returned boolean values. If *True*, the values in
        `bool_array` are as if calculating ``array1 not in array2``.

    Returns
    -------
    bool_array : :obj:`~numpy.ndarray` object of bool
        Bool array containing the elements found in `array1` that are in
        `array2` over given `axis`.

    Example
    -------
    >>> array1 = np.array([[1, 2], [1, 3], [2, 1]])
    >>> array2 = np.array([[1, 2], [1, 3]])
    >>> isin(array1, array2)
    array([True, True, False])

    """

    # Check if axis is None
    if axis is None:
        # If so, use NumPy's isin function
        return (np.isin(array1, array2, assume_unique, invert))

    # Make sure that given arrays are NumPy arrays
    array1 = np.asarray(array1)
    array2 = np.asarray(array2)

    # Make sure that 'axis' is the first axis of both arrays
    try:
        array1 = np.moveaxis(array1, axis, 0)
        array2 = np.moveaxis(array2, axis, 0)
    except Exception as error:
        raise InputError("Input argument 'axis' is invalid (%s)!" % (error))

    # Convert arrays to lists
    list1 = array1.tolist()
    list2 = array2.tolist()

    # Determine what values in list1 are in list2
    bool_list = [element in list2 for element in list1]

    # Convert bool_list to bool_array
    bool_array = np.array(bool_list, dtype=bool)

    # Return it
    return (~bool_array if invert else bool_array)
예제 #8
0
파일: numpy.py 프로젝트: 1313e/e13Tools
def diff(array1, array2=None, axis=0, flatten=True):
    """
    Calculates the pair-wise differences between inputs `array1` and `array2`
    over the given `axis`.

    Parameters
    ----------
    array1 : array_like
        One of the inputs used to calculate the pair-wise differences.

    Optional
    --------
    array2 : array_like or None. Default: None
        The other input used to calculate the pair-wise differences.
        If *None*, `array2` is equal to `array1`.
        If not *None*, the length of all axes except `axis` must be equal for
        both arrays.
    axis : int. Default: 0
        Over which axis to calculate the pair-wise differences. Default is over
        the first axis. A negative value counts from the last to the first
        axis.
    flatten : bool. Default: True
        If `array2` is *None*, whether or not to calculate all pair-wise
        differences.
        If *True*, a flattened array containing all above-diagonal pair-wise
        differences is returned. This is useful if only off-diagonal terms are
        required and the sign is not important.
        If *False*, an array with all pair-wise differences is returned.

    Returns
    -------
    diff_array : :obj:`~numpy.ndarray` object
        Depending on the input parameters, an array with n dimensions
        containing the pair-wise differences between `array1` and `array2` over
        the given `axis`.

    Examples
    --------
    Using two matrices returns the pair-wise differences in row-vectors:

        >>> mat1 = np.array([[1, 2, 3], [4, 5, 6]])
        >>> mat2 = np.array([[4, 5, 6], [7, 8, 9]])
        >>> diff(mat1, mat2)
        array([[[-3., -3., -3.],
                [-6., -6., -6.]],
        <BLANKLINE>
               [[ 0.,  0.,  0.],
                [-3., -3., -3.]]])


    Setting `axis` to 1 returns the pair-wise differences in column-vectors:

        >>> mat1 = np.array([[1, 2, 3], [4, 5, 6]])
        >>> mat2 = np.array([[4, 5, 6], [7, 8, 9]])
        >>> diff(mat1, mat2, axis=1)
        array([[[-3., -3.],
                [-4., -4.],
                [-5., -5.]],
        <BLANKLINE>
               [[-2., -2.],
                [-3., -3.],
                [-4., -4.]],
        <BLANKLINE>
               [[-1., -1.],
                [-2., -2.],
                [-3., -3.]]])


    Only using a single matrix returns the pair-wise differences in row-vectors
    in that matrix (either flattened or not):

        >>> mat = np.array([[1, 2, 3], [4, 5, 6]])
        >>> diff(mat, flatten=True)
        array([[-3., -3., -3.]])
        >>> diff(mat, flatten=False)
        array([[[ 0.,  0.,  0.],
                [-3., -3., -3.]],
        <BLANKLINE>
               [[ 3.,  3.,  3.],
                [ 0.,  0.,  0.]]])


    Using a matrix and a vector returns the pair-wise differences in
    row-vectors:

        >>> mat = np.array([[1, 2, 3], [4, 5, 6]])
        >>> vec = np.array([7, 8, 9])
        >>> diff(mat, vec)
        array([[-6, -6, -6],
               [-3, -3, -3]])


    Using two vectors returns the pair-wise differences in scalars:

        >>> vec1 = np.array([1, 2, 3])
        >>> vec2 = np.array([4, 5, 6])
        >>> diff(vec1, vec2)
        array([[-3., -4., -5.],
               [-2., -3., -4.],
               [-1., -2., -3.]])

    """

    # If array2 is not provided, both arrays are the same
    if array2 is None:
        # Make sure that input is a numpy array
        array1 = np.asarray(array1)

        # Check if a scalar has been provided and act accordingly
        if (array1.ndim == 0):
            return (0)

        # Swap axes in array to put the given axis as the first axis
        try:
            array1 = np.moveaxis(array1, axis, 0)
        except Exception as error:
            raise InputError("Input argument 'axis' is invalid (%s)!" %
                             (error))
        else:
            # Obtain the dimensionality and axis-length
            n_dim = array1.ndim
            len_axis = array1.shape[0]

        # If only unique pair-wise differences are requested
        if flatten:
            # Obtain the shape of the resulting array and initialize it
            n_diff = len_axis * (len_axis - 1) // 2
            if (n_dim == 1):
                diff_shape = [n_diff]
            else:
                diff_shape = np.concatenate([[n_diff], array1.shape[1:n_dim]])
            diff_array = np.zeros(diff_shape)

            # Initialize empty variable holding the distance in index of last i
            dist = 0

            # Fill array
            for i in range(len_axis):
                diff_array[dist:dist + len_axis - i -
                           1] = array1[i] - array1[i + 1:]
                dist += len_axis - i - 1

            # Return it
            return (diff_array)

        # If all difference are requested
        else:
            # Obtain the shape of the resulting array and initialize it
            diff_shape = np.concatenate([[len_axis], array1.shape])
            diff_array = np.zeros(diff_shape)

            # Fill array
            for i in range(len_axis):
                diff_array[i] = array1[i] - array1

            # Return it
            return (diff_array)

    # If array2 is provided, both arrays are different
    else:
        # Make sure that inputs are numpy arrays
        array1 = np.asarray(array1)
        array2 = np.asarray(array2)

        # Get number of dimensions
        n_dim1 = array1.ndim
        n_dim2 = array2.ndim

        # Check if both arrays are scalars and act accordingly
        if (n_dim1 == n_dim2 == 0):
            return (array1 - array2)

        # If both arrays have the same number of dimensions
        if (n_dim1 == n_dim2):
            # Swap axes in arrays to put the given axis as the first axis
            try:
                array1 = np.moveaxis(array1, axis, 0)
                array2 = np.moveaxis(array2, axis, 0)
            except Exception as error:
                raise InputError("Input argument 'axis' is invalid (%s)!" %
                                 (error))
            else:
                # Obtain axis-length
                len_axis1 = array1.shape[0]

            # Check if the length of all other axes are the same
            if (array1.shape[1:] != array2.shape[1:]):
                raise ShapeError("Input arguments 'array1' and 'array2' do not"
                                 " have the same axes lengths: %s != %s" %
                                 (array1.shape[1:], array2.shape[1:]))

            # Obtain the shape of the resulting array and initialize it
            diff_shape = np.concatenate([[len_axis1], array2.shape])
            diff_array = np.zeros(diff_shape)

            # Fill array
            for i in range(len_axis1):
                diff_array[i] = array1[i] - array2

            # Return it
            return (diff_array)

        # If the arrays have different number of dimensions
        else:
            # If second array is bigger than first, swap them
            if (n_dim1 < n_dim2):
                # Swap arrays
                temp_array = array1
                array1 = array2
                array2 = temp_array

                # Swap ndims
                temp_ndim = n_dim1
                n_dim1 = n_dim2
                n_dim2 = temp_ndim

                # Save that arrays were swapped
                sign = -1
            else:
                sign = 1

            # Swap axes in the bigger array to put the given axis as first axis
            try:
                array1 = np.moveaxis(array1, axis, 0)
            except Exception as error:
                raise InputError("Input argument 'axis' is invalid (%s)!" %
                                 (error))

            # Check if the length of all other axes are the same
            if (array1.shape[1:n_dim1] != array2.shape):
                args = ((array1.shape[1:n_dim1], array2.shape) if
                        (sign == 1) else
                        (array2.shape, array1.shape[1:n_dim1]))
                raise ShapeError("Input arguments 'array1' and 'array2' do"
                                 " not have the same axes lengths: %s != "
                                 "%s" % args)
            else:
                # Return difference array
                return (sign * (array1 - array2))
예제 #9
0
파일: pyplot.py 프로젝트: 1313e/e13Tools
def draw_textline(text,
                  *,
                  x=None,
                  y=None,
                  pos='start top',
                  ax=None,
                  line_kwargs={},
                  text_kwargs={}):
    """
    Draws a line on the axis `ax` in a :obj:`~matplotlib.figure.Figure`
    instance and prints `text` on top.

    Parameters
    ----------
    text : str
        Text to be printed on the line.
    x : scalar or None
        If scalar, text/line x-coordinate.
        If *None*, line covers complete x-axis.
        Either `x` or `y` needs to be *None*.
    y : scalar or None
        If scalar, text/line y-coordinate.
        If *None*, line covers complete y-axis.
        Either `x` or `y` needs to be *None*.

    Optional
    --------
    pos : {'start', 'end'}{'top', 'bottom'}. Default: 'start top'
        If 'start', prints the text at the start of the drawn line.
        If 'end', prints the text at the end of the drawn line.
        If 'top', prints the text above the drawn line.
        If 'bottom', prints the text below the drawn line.
        Arguments must be given as a single string.
    ax : :obj:`~matplotlib.axes.Axes` object or None. Default: None
        If :obj:`~matplotlib.axes.Axes` object, draws line in specified
        :obj:`~matplotlib.figure.Figure` instance.
        If *None*, draws line in current :obj:`~matplotlib.figure.Figure`
        instance.
    line_kwargs : dict of :class:`~matplotlib.lines.Line2D` properties.\
        Default: {}
        The keyword arguments used for drawing the line.
    text_kwargs : dict of :class:`~matplotlib.text.Text` properties.\
        Default: {}
        The keyword arguments used for drawing the text.

    """

    # If no AxesSubplot object is provided, make one
    if ax is None:
        ax = plt.gca()

    # Convert pos to lowercase
    pos = pos.lower()

    # Set default line_kwargs and text_kwargs
    default_line_kwargs = {'linestyle': '-', 'color': 'k'}
    default_text_kwargs = {'color': 'k', 'fontsize': 14}

    # Combine given kwargs with default ones
    default_line_kwargs.update(line_kwargs)
    default_text_kwargs.update(text_kwargs)
    line_kwargs = default_line_kwargs
    text_kwargs = default_text_kwargs

    # Check if certain keyword arguments are present in text_fmt
    text_keys = list(text_kwargs.keys())
    for key in text_keys:
        if key in ('va', 'ha', 'verticalalignment', 'horizontalalignment',
                   'rotation', 'transform', 'x', 'y', 's'):
            text_kwargs.pop(key)

    # Set line specific variables
    if x is None and y is not None:
        ax.axhline(y, **line_kwargs)
    elif x is not None and y is None:
        ax.axvline(x, **line_kwargs)
    else:
        raise InputError("Either of input arguments 'x' and 'y' needs to be "
                         "*None*!")

    # Gather case specific text properties
    if ('start') in pos and ('top') in pos:
        ha = 'left' if x is None else 'right'
        va = 'bottom'
        other_axis = 0
    elif ('start') in pos and ('bottom') in pos:
        ha = 'left'
        va = 'top' if x is None else 'bottom'
        other_axis = 0
    elif ('end') in pos and ('top') in pos:
        ha = 'right'
        va = 'bottom' if x is None else 'top'
        other_axis = 1
    elif ('end') in pos and ('bottom') in pos:
        ha = 'right' if x is None else 'left'
        va = 'top'
        other_axis = 1
    else:
        raise ValueError("Input argument 'pos' is invalid!")

    # Set proper axes and rotation
    if x is None:
        x = other_axis
        rotation = 0
        transform = transforms.blended_transform_factory(
            ax.transAxes, ax.transData)
    else:
        y = other_axis
        rotation = 90
        transform = transforms.blended_transform_factory(
            ax.transData, ax.transAxes)

    # Draw text
    ax.text(x,
            y,
            text,
            rotation=rotation,
            ha=ha,
            va=va,
            transform=transform,
            **text_kwargs)