def resize_interval(interval, width, anchor='center'):
    """Resize the Interval. Returns new Interval instance with correct length.

    Arguments:
        interval: pybedtools.Interval object or an object containing `start` and `end` attributes
        width: desired width of the output interval
        anchor (str): which part of the sequence should be anchored. Choices: 'start', 'center', or 'end'
    """
    interval = deepcopy(interval)

    if anchor == "start":
        interval.end = interval.start + width
    elif anchor == "end":
        interval.start = interval.end - width
    elif anchor == "center":
        center = int((interval.start + interval.end) / 2)
        half_len = int(width / 2)
        interval.start = center - half_len
        interval.end = center + half_len + width % 2
    else:
        raise Exception(
            "Interval resizing anchor point can only be 'start', 'end' or 'center'")

    return interval def tokenize(seq, alphabet=DNA, neutral_alphabet=["N"]):
    """Convert sequence to integers

    # Arguments
       seq: Sequence to encode
       alphabet: Alphabet to use
       neutral_alphabet: Neutral alphabet -> assign those values to -1

    # Returns
       List of length `len(seq)` with integers from `-1` to `len(alphabet) - 1`
    """
    # Req: all alphabets have the same length
    if isinstance(neutral_alphabet, str):
        neutral_alphabet = [neutral_alphabet]

    nchar = len(alphabet[0])
    for l in (*alphabet,  *neutral_alphabet):
        assert len(l) == nchar
    assert len(seq) % nchar == 0  # since we are using striding

    alphabet_dict = _get_alphabet_dict(alphabet)
    for l in neutral_alphabet:
        alphabet_dict[l] = -1
    # current performance bottleneck
    return np.array([alphabet_dict[seq[(i * nchar):((i + 1) * nchar)]] for i in range(len(seq) // nchar)]) def token2one_hot(tokens, alphabet_size=4, neutral_value=.25, dtype=None):
    """
    Note: everything out of the alphabet is transformed into `np.zeros(alphabet_size)`
    """
    arr = np.zeros((len(tokens), alphabet_size), dtype=dtype)

    tokens_range = np.arange(len(tokens), dtype=int)
    arr[tokens_range[tokens >= 0], tokens[tokens >= 0]] = 1
    if neutral_value != 0:
        arr[tokens_range[tokens < 0], :] = neutral_value
    return arr def one_hot(seq, alphabet=DNA, neutral_alphabet=['N'], neutral_value=.25, dtype=None):
    if not isinstance(seq, str):
        raise ValueError("seq needs to be a string")
    return token2one_hot(tokenize(seq, alphabet, neutral_alphabet), len(alphabet), neutral_value, dtype=dtype) def one_hot_dna(seq: str,
                alphabet: list = DNA,
                neutral_alphabet: str = 'N',
                neutral_value: Any = 0.25,
                dtype=np.float32) -> np.ndarray:
    """One-hot encode sequence."""
    if not isinstance(seq, str):
        raise ValueError("sequence needs to be a string")
    def to_uint8(string):
        return np.frombuffer(string.encode('ascii'), dtype=np.uint8)
    
    hash_table = np.zeros((np.iinfo(np.uint8).max, len(alphabet)), dtype=dtype)
    hash_table[to_uint8(''.join(alphabet))] = np.eye(len(alphabet), dtype=dtype)
    hash_table[to_uint8(''.join(neutral_alphabet))] = neutral_value
    hash_table = hash_table.astype(dtype)
    return hash_table[to_uint8(seq)] def pad(seq, length, value="N", anchor="center"):
    seq_len = len(seq)
    assert length >= seq_len
    if anchor == "end":
        n_left = length - seq_len
        n_right = 0
    elif anchor == "start":
        n_right = length - seq_len
        n_left = 0
    elif anchor == "center":
        n_left = (length - seq_len) // 2 + (length - seq_len) % 2
        n_right = (length - seq_len) // 2
    else:
        raise ValueError("anchor can be of: end, start or center")

    # normalize for the length
    n_left = n_left // len(value)
    n_right = n_right // len(value)

    return value * n_left + seq + value * n_right def trim(seq, length, anchor="center"):
    seq_len = len(seq)

    assert length <= seq_len
    if anchor == "end":
        return seq[-length:]
    elif anchor == "start":
        return seq[0:length]
    elif anchor == "center":
        dl = seq_len - length
        n_left = dl // 2 + dl % 2
        n_right = seq_len - dl // 2
        return seq[n_left:n_right]
    else:
        raise ValueError("anchor can be of: end, start or center") def fixed_len(seq, length, anchor="center", value="N"):
    """Pad and/or trim a list of sequences to have common length. Procedure:

        1. Pad the sequence with N's or any other string or list element (`value`)
        2. Subset the sequence

    # Note
        See also: https://keras.io/preprocessing/sequence/
        Aplicable also for lists of characters

    # Arguments
        sequence_vec: list of chars or lists
            List of sequences that can have various lengths
        value: Neutral element to pad the sequence with. Can be `str` or `list`.
        length: int or None; Final lenght of sequences.
             If None, length is set to the longest sequence length.
        anchor: character; 'start', 'end' or 'center'
            To which end to anchor the sequences when triming/padding. See examples bellow.

    # Returns
        List of sequences of the same class as sequence_vec

    # Example

        ```python
            >>> sequence = 'CTTACTCAGA'
            >>> pad_sequence(sequence, 10, anchor="start", value="N")
            'CTTACTCAGA'
            >>> pad_sequence(sequence, 10, anchor="end", value="N")
            'CTTACTCAGA'
            >>> pad_sequences(sequence, 4, anchor="center", value="N")
            'ACTC'

            >>> sequence = 'TCTTTA'
            >>> pad_sequence(sequence, 10, anchor="start", value="N")
            'TCTTTANNNN'
            >>> pad_sequence(sequence, 10, anchor="end", value="N")
            'NNNNTCTTTA'
            >>> pad_sequences(sequence, 4, anchor="center", value="N")
            'CTTT'
        ```
    """
    # neutral element type checking
    assert isinstance(value, list) or isinstance(value, str)
    assert isinstance(value, type(seq))
    assert isinstance(length, int)

    # pad and subset
    if len(seq) < length:
        return pad(seq, length, value=value, anchor=anchor)
    elif len(seq) > length:
        return trim(seq, length, anchor=anchor)
    else:
        return seq class ResizeInterval(object):
    """Resize the interval
    """

    def __init__(self, width, anchor='center'):
        self.width = width
        self.anchor = anchor

    def __call__(self, interval):
        return F.resize_interval(interval, self.width, self.anchor) DNA = ("A", "C", "G", "T")
import pytest
import numpy as np
    import pybedtools
    import pybedtools
from kipoiseq.transforms.functional import resize_interval, tokenize, token2one_hot, one_hot, one_hot_dna, pad, trim, fixed_len
from kipoiseq.transforms.transforms import ResizeInterval
from kipoiseq.utils import DNA


def test_tokenize():
    assert np.all(tokenize("ACGTTA", DNA, neutral_alphabet="N")
                  == [0, 1, 2, 3, 3, 0])
    assert np.all(
        tokenize("ACGTGATGA", ["ACG", "TGA"], neutral_alphabet="NNN") == [0, 1, 1])
    assert np.all(
        tokenize("ACGTGATGA", ["ACG"], neutral_alphabet="TGA") == [0, -1, -1])
    with pytest.raises(Exception):
        tokenize("ACGTGATGA", ["ACG"], neutral_alphabet="NNN")


def test_token2one_hot():
    assert np.array_equal(token2one_hot(np.array([0, 1, -1]), 2), np.array([[1, 0],
                                                                            [0, 1],
                                                                            [0.25, .25]]))


def test_tokenize_one_hot():
    assert one_hot("ACG", DNA, "N").shape == (3, 4)

    et = tokenize("ACG", DNA, "N")
    assert et.shape == (3,)
    assert np.array_equal(et, np.array([0, 1, 2]))

    et = tokenize("TGTN", DNA, "N")
    assert np.array_equal(et, np.array([3, 2, 3, -1]))  # N mapped to -1


def test_one_hot():

    seq = "ACGTTTATNT"
    assert len(seq) == 10

    assert one_hot_dna(seq).shape == (10, 4)
    assert one_hot(seq).shape == (10, 4)

    assert np.all(one_hot_dna(seq) == one_hot(seq))

    assert one_hot(pad(seq, 20)).shape == (20, 4)

    assert one_hot(fixed_len(seq, 20)).shape == (20, 4)
    assert one_hot(fixed_len(seq, 5)).shape == (5, 4)
    assert trim(seq, 5) == 'TTTAT'
    assert trim(seq, 5, 'start') == 'ACGTT'
    assert trim(seq, 5, 'end') == 'TATNT'
    with pytest.raises(Exception):
        assert pad(seq, 5, 'end') == 'TATNT'

    assert np.all(one_hot(seq)[0] == np.array([1, 0, 0, 0]))
    assert np.all(one_hot(seq)[1] == np.array([0, 1, 0, 0]))
    assert np.all(one_hot(seq)[2] == np.array([0, 0, 1, 0]))
    assert np.all(one_hot(seq)[3] == np.array([0, 0, 0, 1]))
    assert np.all(one_hot(seq)[4] == np.array([0, 0, 0, 1]))
    assert np.all(one_hot(seq)[-1] == np.array([0, 0, 0, 1]))
    assert np.all(one_hot(seq)[-2] == np.array([0.25, 0.25, 0.25, 0.25]))

    with pytest.raises(ValueError):
        one_hot(['A', 'C'])

    with pytest.raises(ValueError):
        one_hot_dna(['A', 'C'])


def test_fixed_len():
    seq = "ACGTTTATNT"
    assert len(fixed_len(seq, 20, value="N", anchor="end")) is 20
    assert fixed_len([1, 2, 3, 4], 6, value=[0],
                     anchor="end") == [0, 0, 1, 2, 3, 4]
    assert fixed_len([1, 2, 3, 4], 2, value=[0], anchor="end") == [3, 4]

    # expect error
    assert fixed_len(seq, length=3, value="NNN", anchor="end") == "TNT"


def test_pad_sequences():
    seq = 'CTTACTCAGA'

    assert fixed_len(seq, 4, anchor="center", value="N") == 'ACTC'