# Interval object

Intervals can carry particular special meaning in the analysis of time-series. For example, a typical case, is when two time-series are recorded simultaneously. One is recorded from measurement of some continuous physilogical variable, such as fMRI BOLD (and is represented by a an object of type UniformTimeSeries). The other is a series of discrete events occurring concurrently (and can be represented by a EventSeries or by a NonUniformTimeSeries). For example, button presses by the subject, or trials of different kinds. If we want to analyze the progression of the physiological time-series, locked to particular kinds of events in the event-series, we would need a notion of an interval of time surrounding those events.

In order to do that, we propose implementing a `TimeInterval` object.

## Attributes

A `TimeInterval` object can be thought of as a `Slice` for time-series objects and therefore should at least implement all attributes and methods of the slice object (sub-classing of slice doesn’t seem to be possible, see Implementation using a slice object).

In particular, an object of class `TimeInterval`, `TI`, has the attributes/functions:

- `TI.t_start`: the start time of the interval.

- `TI.t_stop`: the end time of the interval.

- `TI.duration`: the duration of the interval.

Obviously, knowledge of two of these, should allow calculation of the third. Therefore, this should be implemented in the object with a `setattr_on_read()` decoration and the object should inherit `ResetMixin`. Initialization of the object would verify that enough information exists and that the information provided is consistent, in the same manner that is already implemented in `UniformTimeSeries`.

- `TI.t_step`: originally, we thought that this could be abused to represent a time offset, relative to the attributes `t_start` and `t_stop`. That is, it can tell us where relative to these two time-points some interesting even, which this interval surrounds, or this interval is close to, occurs. This can be used in order to interpret how time-series access is done using the `TimeInterval` object. See Time-series access. This attribute can be implemented as an optional input on initialization, such that it defaults to be equal to `t_start`.

- `TI.indices(len)()`: this method returns a tuple of time points that can be used for slicing. Originally, this is meant to produce a list of indices of length len that can be directly used to obtain a slice of the same length. However, when we use a `TimeInterval` for slicing, we don’t know yet, how long the sliced object will be (it depends on the sampling interval of the sliced object). If we just use len=0, the indices method just returns a 3-tuple that still contains all necessary information and can be used for slicing:

    >>>TI.indices(0)
    (TI.t_start TI.t_stop, TI.t_step)

## Initialization

There are various different ways to initialize a `TimeInterval`:

- With two time points t_start and t_stop, both of `TimeArray`:

    TimeInterval(t_start=t1,t_stop=t2)

- With a time point `t_start` (a `TimeArray`) and a duration (a `TimeArray`):

    TimeInterval(t_start=t1,duration=t_duration)

- With an optional third argument `t_step` (a `TimeArray`) indicating a time offset of a time point \(t_0=t_{start}-t_{step}\) relative to which the time inside the interval should be interpreted. The relevance of this third argument will become clearer when the time interval is used to slice into a time-series object (see Time-series access). Briefly - the returned object would be a time-series object with the `t0` attribute set to be the \(t_0\) described above. If not provided, this would default to be equal to `t_start`:

    TimeInterval(t_start=t1, t_stop=t2, t_step=delta_t)

or

    TimeInterval (t_start=t1,duration=delta_t1, t_step=delta_t2)

Finally, we would like allow setting the interval with floating point values, which will be interpreted as time points `t_start` and `t_stop`. This convention would be convenient, but requires that the initialization of the object will know what the units are. In order to make this possible, the interval (similar to the current implementation of the time-series object will have an attribute `t_unit`, which would default to ‘s’. The initialization will then cast the values provided into the appropriate `TimeArray` objects.

## Implementation using a slice object

Sub-classing of the slice object doesn’t seem to be possible:

    >>> class myslice(slice):
    ...     pass
    ...
    ------------------------------------------------------------
    Traceback (most recent call last):
      File "<ipython console>", line 1, in <module>
    TypeError: Error when calling the metaclass bases
        type 'slice' is not an acceptable base type

However, it seems that a (scalar) `TimeInterval` can be implemented using a slice object, provided the time points `t_start` and `t_stop` and the time offset `t_step` implement an __index__ method:

    >>> s = slice('t_start','t_stop','t_step')
    >>> s.start
    't_start'
    >>> s.stop
    't_stop'
    >>> s.step
    't_step'
    >>> s.indices(1)
    ------------------------------------------------------------
    Traceback (most recent call last):
      File "<ipython console>", line 1, in <module>
    TypeError: slice indices must be integers or None or have an __index__ method

Alternatively, the `TimeInterval` can be implemented as an original object with the default constructor as similar as possible to the constructor of the slice object, so that we can use slice-like operations, but still maintain slice algebra and such.

In addition to the possibility of algebraic operations, there are other reasons to have the `TimeInterval` be an original class that holds a slice object that can be returned by the method `TI.asslice()`.

## Interval arrays

In addition to scalar `TimeInterval` objects, it also makes sense to define arrays of `TimeInterval` objects. These arrays can be implemented as `np.ndarray`, with an `object` dtype.