Source code for ollin.movement_analyzers.turn_angle

import numpy as np

from .base import MovementAnalyzer


[docs]class Analyzer(MovementAnalyzer): r"""Turn Angle analyzer. Turn angle refers to the angle formed by two successive steps of an individual motion. Hence if :math:`p(t_0) = (x_0, y_0)`, :math:`p(t_1) = (x_1, y_1)` and :math:`p(t_2) = (x_2, y_2)` are the positions of an individual at times :math:`t_0, t_0` and :math:`t_2` then the turn angle at time :math:`t_1` is defined by: .. math:: d_1 = (x_1 - x_0, y_1 - y_0) d_2 = (x_2 - x_1, y_2 - y_1) ta = \arccos\left(\frac{d_1 \cdot d_2}{||d_1|| \cdot ||d_2||}\right) Here :math:`ta` is turn angle. """ name = 'Turn Angle'
[docs] def analyze(self, movement): """Extract turn angles from movement data. Get the turn angles of all individuals in movement data at every time step. Will result in an array of size [num_individuals, time_steps - 2] so that:: turn_angle = results[i, k] Means that the ``i``-th individual had a turn angle of ``turn_angle`` at the ``k``-th time step. Arguments --------- movement : :py:obj:`.Movement` Movement data to analyze Returns ------- results : :py:obj:`array` Array of shape [num_individuals, time_steps - 1] containing the turn angles of all individuals at every time step. """ data = movement.data times = movement.times dtimes = (times[1:] - times[:-1])[None, :, None] directions = (data[:, 1:, :] - data[:, :-1, :]) / dtimes complex_directions = directions[:, :, 0] + 1j * directions[:, :, 1] turn_angles = np.angle( complex_directions[:, 1:] / complex_directions[:, :-1]) return turn_angles
[docs] def plot( self, ax=None, figsize=(10, 10), num_individual=0, bins=20, width=None, cmap='Reds', alpha=0.8, log=True): """Plot distribution of turning angles. Arguments --------- ax : :py:obj:`matplotlib.axes.Axes`, optional Axes object in which to plot. figsize : tuple or list, optional Size of figure to create if no axes are provided. num_individual : int or tuple or list or array, optional Selection of individuals from which to draw turning angle information. If num_individual='all', all information will be plotted. bins : int, optional Number of bins to use in histogram of turn angle distribution. Defaults to 20. width : float, optional Width of bars in histogram. If none is given, width will be maximum possible before overlap. cmap : str, optional Colormap to use to assign colors to histogram bars. Defaults to 'Reds'. alpha : float, optional Alpha value of plot. Defaults to 0.8. """ import matplotlib.pyplot as plt from matplotlib.cm import get_cmap if ax is None: _, ax = plt.subplots( figsize=figsize, subplot_kw={"polar": True}) if num_individual == 'all': tdata = self.results.ravel() elif isinstance(num_individual, int): tdata = self.results[num_individual, :] else: tdata = self.results[num_individual, :].ravel() range_ = (-np.pi, np.pi) histogram, _ = np.histogram( tdata, bins=bins, range=range_, normed=True) if width is None: width = (range_[1] - range_[0]) / bins theta = np.linspace(range_[0], range_[1], bins, endpoint=False) bars = ax.bar(theta, histogram, width=width, bottom=0.05) mins, maxs = histogram.min(), histogram.max() # Use custom colors and opacity cmap = get_cmap(cmap) for value, pbar in zip(histogram, bars): pbar.set_facecolor( cmap((value - mins + 0.1) / (0.1 + maxs - mins))) pbar.set_alpha(alpha) ticks = np.linspace(0.05, 0.05 + histogram.max(), 4) ax.set_yticks(ticks) ax.set_yticklabels(np.round(ticks - 0.05, 2)) ax.set_title('Turn Angle distribution') return ax