| |
| |
|
|
| |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| """ |
| ############# |
| Visualization |
| ############# |
| |
| :class:`pyannote.core.Segment`, :class:`pyannote.core.Timeline`, |
| :class:`pyannote.core.Annotation` and :class:`pyannote.core.SlidingWindowFeature` |
| instances can be directly visualized in notebooks. |
| |
| You will however need to install ``pytannote.core``'s additional dependencies |
| for notebook representations (namely, matplotlib): |
| |
| |
| .. code-block:: bash |
| |
| pip install pyannote.core[notebook] |
| |
| |
| Segments |
| -------- |
| |
| .. code-block:: ipython |
| |
| In [1]: from pyannote.core import Segment |
| |
| In [2]: segment = Segment(start=5, end=15) |
| ....: segment |
| |
| .. plot:: pyplots/segment.py |
| |
| |
| Timelines |
| --------- |
| |
| .. code-block:: ipython |
| |
| In [25]: from pyannote.core import Timeline, Segment |
| |
| In [26]: timeline = Timeline() |
| ....: timeline.add(Segment(1, 5)) |
| ....: timeline.add(Segment(6, 8)) |
| ....: timeline.add(Segment(12, 18)) |
| ....: timeline.add(Segment(7, 20)) |
| ....: timeline |
| |
| .. plot:: pyplots/timeline.py |
| |
| |
| Annotations |
| ----------- |
| |
| |
| .. code-block:: ipython |
| |
| In [1]: from pyannote.core import Annotation, Segment |
| |
| In [6]: annotation = Annotation() |
| ...: annotation[Segment(1, 5)] = 'Carol' |
| ...: annotation[Segment(6, 8)] = 'Bob' |
| ...: annotation[Segment(12, 18)] = 'Carol' |
| ...: annotation[Segment(7, 20)] = 'Alice' |
| ...: annotation |
| |
| .. plot:: pyplots/annotation.py |
| |
| """ |
| from typing import Iterable, Dict, Optional |
|
|
| from .utils.types import Label, LabelStyle, Resource |
|
|
| |
| |
| |
| |
| import numpy as np |
| from itertools import cycle, product, groupby |
| from .segment import Segment, SlidingWindow |
| from .timeline import Timeline |
| from .annotation import Annotation |
| from .feature import SlidingWindowFeature |
|
|
| try: |
| import matplotlib |
| except ImportError: |
| MATPLOTLIB_IS_AVAILABLE = False |
| else: |
| MATPLOTLIB_IS_AVAILABLE = True |
|
|
| MATPLOTLIB_WARNING = ( |
| "Couldn't import matplotlib to render the vizualization " |
| "for object {klass}. To enable, install the required dependencies " |
| "with 'pip install pyannore.core[notebook]'" |
| ) |
|
|
|
|
| class Notebook: |
| def __init__(self): |
| self.reset() |
|
|
| def reset(self): |
| from matplotlib.cm import get_cmap |
|
|
| linewidth = [3, 1] |
| linestyle = ["solid", "dashed", "dotted"] |
|
|
| cm = get_cmap("Set1") |
| colors = [cm(1.0 * i / 8) for i in range(9)] |
|
|
| self._style_generator = cycle(product(linestyle, linewidth, colors)) |
| self._style: Dict[Optional[Label], LabelStyle] = { |
| None: ("solid", 1, (0.0, 0.0, 0.0)) |
| } |
| del self.crop |
| del self.width |
|
|
| @property |
| def crop(self): |
| """The crop property.""" |
| return self._crop |
|
|
| @crop.setter |
| def crop(self, segment: Segment): |
| self._crop = segment |
|
|
| @crop.deleter |
| def crop(self): |
| self._crop = None |
|
|
| @property |
| def width(self): |
| """The width property""" |
| return self._width |
|
|
| @width.setter |
| def width(self, value: int): |
| self._width = value |
|
|
| @width.deleter |
| def width(self): |
| self._width = 20 |
|
|
| def __getitem__(self, label: Label) -> LabelStyle: |
| """Get line style for a given label""" |
| if label not in self._style: |
| self._style[label] = next(self._style_generator) |
| return self._style[label] |
|
|
| def setup(self, ax=None, ylim=(0, 1), yaxis=False, time=True): |
| import matplotlib.pyplot as plt |
|
|
| if ax is None: |
| ax = plt.gca() |
| ax.set_xlim(self.crop) |
| if time: |
| ax.set_xlabel("Time") |
| else: |
| ax.set_xticklabels([]) |
| ax.set_ylim(ylim) |
| ax.axes.get_yaxis().set_visible(yaxis) |
| return ax |
|
|
| def draw_segment(self, ax, segment: Segment, y, label=None, boundaries=True): |
|
|
| |
| if not segment: |
| return |
|
|
| linestyle, linewidth, color = self[label] |
|
|
| |
| ax.hlines( |
| y, |
| segment.start, |
| segment.end, |
| color, |
| linewidth=linewidth, |
| linestyle=linestyle, |
| label=label, |
| ) |
| if boundaries: |
| ax.vlines( |
| segment.start, y + 0.05, y - 0.05, color, linewidth=1, linestyle="solid" |
| ) |
| ax.vlines( |
| segment.end, y + 0.05, y - 0.05, color, linewidth=1, linestyle="solid" |
| ) |
|
|
| if label is None: |
| return |
|
|
| def get_y(self, segments: Iterable[Segment]) -> np.ndarray: |
| """ |
| |
| Parameters |
| ---------- |
| segments : Iterable |
| `Segment` iterable (sorted) |
| |
| Returns |
| ------- |
| y : np.array |
| y coordinates of each segment |
| |
| """ |
|
|
| |
| |
| |
| up_to = [-np.inf] |
|
|
| |
| y = [] |
|
|
| for segment in segments: |
| |
| found = False |
| |
| for i, u in enumerate(up_to): |
| |
| |
| if segment.start >= u: |
| found = True |
| y.append(i) |
| up_to[i] = segment.end |
| break |
| |
| if not found: |
| y.append(len(up_to)) |
| up_to.append(segment.end) |
|
|
| |
| y = 1.0 - 1.0 / (len(up_to) + 1) * (1 + np.array(y)) |
|
|
| return y |
|
|
| def __call__(self, resource: Resource, time: bool = True, legend: bool = True): |
|
|
| if isinstance(resource, Segment): |
| self.plot_segment(resource, time=time) |
|
|
| elif isinstance(resource, Timeline): |
| self.plot_timeline(resource, time=time) |
|
|
| elif isinstance(resource, Annotation): |
| self.plot_annotation(resource, time=time, legend=legend) |
|
|
| elif isinstance(resource, SlidingWindowFeature): |
| self.plot_feature(resource, time=time) |
|
|
| def plot_segment(self, segment, ax=None, time=True): |
|
|
| if not self.crop: |
| self.crop = segment |
|
|
| ax = self.setup(ax=ax, time=time) |
| self.draw_segment(ax, segment, 0.5) |
|
|
| def plot_timeline(self, timeline: Timeline, ax=None, time=True): |
|
|
| if not self.crop and timeline: |
| self.crop = timeline.extent() |
|
|
| cropped = timeline.crop(self.crop, mode="loose") |
|
|
| ax = self.setup(ax=ax, time=time) |
|
|
| for segment, y in zip(cropped, self.get_y(cropped)): |
| self.draw_segment(ax, segment, y) |
|
|
| |
|
|
| def plot_annotation(self, annotation: Annotation, ax=None, time=True, legend=True): |
|
|
| if not self.crop: |
| self.crop = annotation.get_timeline(copy=False).extent() |
|
|
| cropped = annotation.crop(self.crop, mode="intersection") |
| labels = cropped.labels() |
| segments = [s for s, _ in cropped.itertracks()] |
|
|
| ax = self.setup(ax=ax, time=time) |
|
|
| for (segment, track, label), y in zip( |
| cropped.itertracks(yield_label=True), self.get_y(segments) |
| ): |
| self.draw_segment(ax, segment, y, label=label) |
|
|
| if legend: |
| H, L = ax.get_legend_handles_labels() |
|
|
| |
| if not H: |
| return |
|
|
| |
| |
| HL = groupby( |
| sorted(zip(H, L), key=lambda h_l: h_l[1]), key=lambda h_l: h_l[1] |
| ) |
| H, L = zip(*list((next(h_l)[0], l) for l, h_l in HL)) |
| ax.legend( |
| H, |
| L, |
| bbox_to_anchor=(0, 1), |
| loc=3, |
| ncol=5, |
| borderaxespad=0.0, |
| frameon=False, |
| ) |
|
|
| def plot_feature( |
| self, feature: SlidingWindowFeature, ax=None, time=True, ylim=None |
| ): |
|
|
| if not self.crop: |
| self.crop = feature.getExtent() |
|
|
| window = feature.sliding_window |
| n, dimension = feature.data.shape |
| ((start, stop),) = window.crop(self.crop, mode="loose", return_ranges=True) |
| xlim = (window[start].middle, window[stop].middle) |
|
|
| start = max(0, start) |
| stop = min(stop, n) |
| t = window[0].middle + window.step * np.arange(start, stop) |
| data = feature[start:stop] |
|
|
| if ylim is None: |
| m = np.nanmin(data) |
| M = np.nanmax(data) |
| ylim = (m - 0.1 * (M - m), M + 0.1 * (M - m)) |
|
|
| ax = self.setup(ax=ax, yaxis=False, ylim=ylim, time=time) |
| ax.plot(t, data) |
| ax.set_xlim(xlim) |
|
|
|
|
| notebook = Notebook() |
|
|
| def repr_segment(segment: Segment): |
| """Get `png` data for `segment`""" |
| import matplotlib.pyplot as plt |
|
|
| figsize = plt.rcParams["figure.figsize"] |
| plt.rcParams["figure.figsize"] = (notebook.width, 1) |
| fig, ax = plt.subplots() |
| notebook.plot_segment(segment, ax=ax) |
| |
| plt.savefig('./output') |
| plt.close(fig) |
| plt.rcParams["figure.figsize"] = figsize |
| return |
|
|
|
|
| def repr_timeline(timeline: Timeline): |
| """Get `png` data for `timeline`""" |
| import matplotlib.pyplot as plt |
| breakpoint() |
| figsize = plt.rcParams["figure.figsize"] |
| plt.rcParams["figure.figsize"] = (notebook.width, 1) |
| fig, ax = plt.subplots() |
| notebook.plot_timeline(timeline, ax=ax) |
| |
| plt.savefig('./output') |
| plt.cla(fig) |
| plt.rcParams["figure.figsize"] = figsize |
| return |
|
|
|
|
| def repr_annotation(annotation: Annotation): |
| """Get `png` data for `annotation`""" |
| import matplotlib.pyplot as plt |
| |
| figsize = plt.rcParams["figure.figsize"] |
| plt.rcParams["figure.figsize"] = (notebook.width, 2) |
| fig, ax = plt.subplots() |
| notebook.plot_annotation(annotation, ax=ax) |
| |
| plt.savefig('./output') |
| plt.close(fig) |
| plt.rcParams["figure.figsize"] = figsize |
| return |
|
|
|
|
| def repr_feature(feature: SlidingWindowFeature): |
| """Get `png` data for `feature`""" |
| import matplotlib.pyplot as plt |
|
|
| figsize = plt.rcParams["figure.figsize"] |
|
|
| if feature.data.ndim == 2: |
|
|
| plt.rcParams["figure.figsize"] = (notebook.width, 2) |
| fig, ax = plt.subplots() |
| notebook.plot_feature(feature, ax=ax) |
| |
| plt.savefig('./output') |
| plt.close(fig) |
|
|
| elif feature.data.ndim == 3: |
|
|
| num_chunks = len(feature) |
|
|
| if notebook.crop is None: |
| notebook.crop = Segment( |
| start=feature.sliding_window.start, |
| end=feature.sliding_window[num_chunks - 1].end, |
| ) |
| else: |
| feature = feature.crop(notebook.crop, mode="loose", return_data=False) |
|
|
| num_overlap = ( |
| round(feature.sliding_window.duration // feature.sliding_window.step) + 1 |
| ) |
|
|
| num_overlap = min(num_chunks, num_overlap) |
|
|
| plt.rcParams["figure.figsize"] = (notebook.width, 1.5 * num_overlap) |
|
|
| fig, axes = plt.subplots(nrows=num_overlap, ncols=1,) |
| mini, maxi = np.nanmin(feature.data), np.nanmax(feature.data) |
| ylim = (mini - 0.2 * (maxi - mini), maxi + 0.2 * (maxi - mini)) |
| for c, (window, data) in enumerate(feature): |
| ax = axes[c % num_overlap] |
| step = duration = window.duration / len(data) |
| frames = SlidingWindow(start=window.start, step=step, duration=duration) |
| window_feature = SlidingWindowFeature(data, frames, labels=feature.labels) |
| notebook.plot_feature( |
| window_feature, |
| ax=ax, |
| time=c % num_overlap == (num_overlap - 1), |
| ylim=ylim, |
| ) |
| ax.set_prop_cycle(None) |
| |
| plt.savefig('./output') |
| plt.close(fig) |
|
|
| plt.rcParams["figure.figsize"] = figsize |
| return |
|
|