In [1]:
%pylab inline


Populating the interactive namespace from numpy and matplotlib

Annotation (pyannote.core.annotation.Annotation)


In [2]:
from pyannote.core import Annotation

Annotation instances are used to describe sets of annotated temporal fragments.
For instance, one can use an Annotation to store the result of speaker identification approach applied on an episode of The Big Bang Theory TV series.


In [3]:
annotation = Annotation(
    uri='TheBigBangTheory.Season01.Episode01', 
    modality='speaker'
)

For instance, to represent a dialogue between Penny and Leonard, we could do the following:


In [4]:
from pyannote.core import Segment
annotation[Segment(3, 5)] = 'Penny'
annotation[Segment(5.5, 7)] = 'Leonard'
annotation[Segment(8, 10)] = 'Penny'
annotation


Out[4]:

Now, suppose (super-annoying) Sheldon speaks over Penny's second speech turn.


In [5]:
annotation[Segment(8, 10)] = 'Sheldon'
annotation


Out[5]:

Damn, Sheldon! That's not what we wanted! We did not want to replace Penny by Sheldon. Instead, we wanted to show that both characters are speaking at the very same time.
To achieve this, we need to create a new track for the same segment.


In [6]:
segment = Segment(8, 10)
annotation[segment, annotation.new_track(segment)] = 'Penny'
annotation


Out[6]:

That's much better! A track cannot be identified by a segment alone: it is uniquely identified by a 2-tuple (segment, track_identifier).

In fact, the new_track method is simply an helper function to help you generate new track identifiers for a given segment. And the previous lines are in fact shortcuts for the following:


In [7]:
annotation = Annotation(
    uri='TheBigBangTheory.Season01.Episode01', 
    modality='speaker'
)
annotation[Segment(3, 5), '_'] = 'Penny'
annotation[Segment(5.5, 7), '_',] = 'Leonard'
annotation[Segment(8, 10), '_'] = 'Penny'
annotation[Segment(8, 10), 'anything'] = 'Sheldon'
annotation


Out[7]:

_ is the default track identifier (in case you are sure that each segment has only one associated track). Generally, you should avoid using the shortcut annotation[segment] and always add a track identifier of your choosing.

Iterating over segments, tracks and/or labels


In [8]:
for segment in annotation.itersegments():
    print segment


[3.000 --> 5.000]
[5.500 --> 7.000]
[8.000 --> 10.000]

In [9]:
for segment, track in annotation.itertracks():
    print segment, track


[3.000 --> 5.000] _
[5.500 --> 7.000] _
[8.000 --> 10.000] _
[8.000 --> 10.000] anything

In [10]:
for segment, track, label in annotation.itertracks(label=True):
    print segment, track, label


[3.000 --> 5.000] _ Penny
[5.500 --> 7.000] _ Leonard
[8.000 --> 10.000] _ Penny
[8.000 --> 10.000] anything Sheldon

Dealing with segments

One can use the get_timeline method to obtain a timeline made of all segments (not tracks!) in the annotation


In [11]:
annotation.get_timeline()


Out[11]:

Dealing with tracks

Get all tracks for a given segment


In [12]:
annotation.get_tracks(Segment(8, 10))


Out[12]:
{'_', 'anything'}

Check whether a given track exists


In [13]:
annotation.has_track(Segment(8, 10), '---')


Out[13]:
False

One can re-initialize tracks as incrementing integer using retrack method. This also makes sure that no two tracks have the same identifier.


In [14]:
for segment, track in annotation.retrack().itertracks():
    print segment, track


[3.000 --> 5.000] 0
[5.500 --> 7.000] 1
[8.000 --> 10.000] 2
[8.000 --> 10.000] 3

Dealing with labels

Get (sorted) list of unique labels


In [15]:
annotation.labels()


Out[15]:
['Leonard', 'Penny', 'Sheldon']

List of labels for a given segment. Read the help message for more options.


In [16]:
annotation.get_labels(Segment(8, 10))


Out[16]:
{'Penny', 'Sheldon'}

One may want to be more precise on the identiy of each speaker and replace character names by actor names. We can use translate to do just that:


In [17]:
mapping = {'Penny': 'Kaley Cuoco', 'Sheldon': 'Jim Parsons', 'Leonard': 'Johnny Galecki'}
annotation.translate(mapping)


Out[17]:

In [18]:
# this also works
annotation % mapping


Out[18]:

Cropping and subsetting

The crop method works the same way it does for timeline instances.


In [19]:
annotation


Out[19]:

In [20]:
segment = Segment(6, 9)
segment


Out[20]:

In [21]:
annotation.crop(segment)


Out[21]:

The subset method allows to extract annotations based on their labels.


In [22]:
male_characters = set(['Sheldon', 'Leonard'])
annotation.subset(male_characters)


Out[22]:

In [23]:
# all but male characters
annotation.subset(male_characters, invert=True)


Out[23]:

Analyzing annotations


In [24]:
# timeline made of Penny's segments
annotation.label_timeline('Penny')


Out[24]:

In [25]:
# total Penny's speech duration
print 'Penny spoke during %f seconds' % annotation.label_duration('Penny')


Penny spoke during 4.000000 seconds

In [26]:
# label chart based on their duration
annotation.chart()


Out[26]:
[('Penny', 4), ('Sheldon', 2), ('Leonard', 1.5)]

In [27]:
# who is the biggest speaker?
annotation.argmax()


Out[27]:
'Penny'

Other operations

co_iter works the same way as for timeline except it iterates over overlapping (segment, track) tuples.

Need help?

You can always try the following...
Who knows? It might give you the information you are looking for!


In [28]:
help(Annotation)


Help on class Annotation in module pyannote.core.annotation:

class Annotation(__builtin__.object)
 |  Annotation
 |  
 |  Parameters
 |  ----------
 |  uri : string, optional
 |      uniform resource identifier of annotated document
 |  modality : string, optional
 |      name of annotated modality
 |  
 |  Methods defined here:
 |  
 |  __bool__(self)
 |  
 |  __contains__(self, included)
 |      Inclusion
 |      
 |      Use expression 'segment in annotation' or 'timeline in annotation'
 |      
 |      Parameters
 |      ----------
 |      included : `Segment` or `Timeline`
 |      
 |      Returns
 |      -------
 |      contains : bool
 |          True if every segment in `included` exists in annotation
 |          False otherwise
 |  
 |  __delitem__(self, key)
 |  
 |  __eq__(self, other)
 |  
 |  __getitem__(self, key)
 |      # label = annotation[segment, track]
 |  
 |  __init__(self, uri=None, modality=None)
 |  
 |  __len__(self)
 |      Number of segments
 |  
 |  __mod__(self, translation)
 |  
 |  __ne__(self, other)
 |  
 |  __nonzero__(self)
 |  
 |  __setitem__(self, key, label)
 |      # annotation[segment, track] = label
 |  
 |  __str__(self)
 |      Human-friendly representation
 |  
 |  anonymize_labels(self, generator=u'string')
 |      Anonmyize labels
 |      
 |      Create a new annotation where labels are anonymized.
 |      
 |      Parameters
 |      ----------
 |      generator : {'string', 'int', iterator}, optional
 |      
 |      Returns
 |      -------
 |      anonymized : :class:`Annotation`
 |          New annotation with anonymized labels.
 |  
 |  anonymize_tracks(self, generator=u'string')
 |      Anonymize tracks
 |      
 |      Create a new annotation where each track has a unique label.
 |      
 |      Parameters
 |      ----------
 |      generator : {'string', 'int', iterator}, optional
 |          Default to 'string'.
 |      
 |      Returns
 |      -------
 |      anonymized : `Annotation`
 |          New annotation with anonymized tracks.
 |  
 |  argmax(self, segment=None, known_first=False)
 |      Get most frequent label
 |      
 |      
 |      Parameters
 |      ----------
 |      segment : Segment, optional
 |          Section of annotation where to look for the most frequent label.
 |          Defaults to whole annotation extent.
 |      known_first: bool, optional
 |          If True, artificially reduces the duration of intersection of
 |          `Unknown` labels so that 'known' labels are returned first.
 |      
 |      Returns
 |      -------
 |      label : any existing label or None
 |          Label with longest intersection
 |      
 |      Examples
 |      --------
 |      
 |          >>> annotation = Annotation(modality='speaker')
 |          >>> annotation[Segment(0, 10), 'speaker1'] = 'Alice'
 |          >>> annotation[Segment(8, 20), 'speaker1'] = 'Bob'
 |          >>> print "%s is such a talker!" % annotation.argmax()
 |          Bob is such a talker!
 |          >>> segment = Segment(22, 23)
 |          >>> if not annotation.argmax(segment):
 |          ...    print "No label intersecting %s" % segment
 |          No label intersection [22 --> 23]
 |  
 |  chart(self, percent=False)
 |      Label chart based on their duration
 |      
 |      Parameters
 |      ----------
 |      percent : bool, optional
 |          Return total duration percentage (rather than raw duration)
 |      
 |      Returns
 |      -------
 |      chart : (label, duration) iterable
 |          Sorted from longest to shortest.
 |  
 |  co_iter(self, other)
 |      Parameters
 |      ----------
 |      other : Annotation
 |      
 |      Generates
 |      ---------
 |      (segment, track), (other_segment, other_track)
 |  
 |  copy(self)
 |  
 |  crop(self, other, mode=u'intersection')
 |      Crop annotation
 |      
 |      Parameters
 |      ----------
 |      other : `Segment` or `Timeline`
 |      
 |      mode : {'strict', 'loose', 'intersection'}
 |          In 'strict' mode, only segments fully included in focus coverage
 |          are kept. In 'loose' mode, any intersecting segment is kept
 |          unchanged. In 'intersection' mode, only intersecting segments are
 |          kept and replaced by their actual intersection with the focus.
 |      
 |      Returns
 |      -------
 |      cropped : Annotation
 |      
 |      Remarks
 |      -------
 |      In 'intersection' mode, the best is done to keep the track names
 |      unchanged. However, in some cases where two original segments are
 |      cropped into the same resulting segments, conflicting track names are
 |      modified to make sure no track is lost.
 |  
 |  empty(self)
 |  
 |  for_json(self)
 |  
 |  get_labels(self, segment, unknown=True, unique=True)
 |      Local set of labels
 |      
 |      Parameters
 |      ----------
 |      segment : Segment
 |          Segments to get label from.
 |      unknown : bool, optional
 |          When False, do not return Unknown instances
 |          When True, return any label (even Unknown instances)
 |      unique : bool, optional
 |          When False, return the list of (possibly repeated) labels.
 |          When True (default), return the set of labels
 |      Returns
 |      -------
 |      labels : set
 |          Set of labels for `segment` if it exists, empty set otherwise.
 |      
 |      Examples
 |      --------
 |      
 |          >>> annotation = Annotation()
 |          >>> segment = Segment(0, 2)
 |          >>> annotation[segment, 'speaker1'] = 'Bernard'
 |          >>> annotation[segment, 'speaker2'] = 'John'
 |          >>> print sorted(annotation.get_labels(segment))
 |          set(['Bernard', 'John'])
 |          >>> print annotation.get_labels(Segment(1, 2))
 |          set([])
 |  
 |  get_timeline(self)
 |      Get timeline made of annotated segments
 |  
 |  get_track_by_name(self, track)
 |      Get all tracks with given name
 |      
 |      Parameters
 |      ----------
 |      track : any valid track name
 |          Requested name track
 |      
 |      Returns
 |      -------
 |      tracks : list
 |          List of (segment, track) tuples
 |  
 |  get_tracks(self, segment)
 |      Set of tracks for query segment
 |      
 |      Parameters
 |      ----------
 |      segment : `Segment`
 |          Query segment
 |      
 |      Returns
 |      -------
 |      tracks : set
 |          Set of tracks for query segment
 |  
 |  has_track(self, segment, track)
 |      Check whether a given track exists
 |      
 |      Parameters
 |      ----------
 |      segment : `Segment`
 |          Query segment
 |      track :
 |          Query track
 |      
 |      Returns
 |      -------
 |      exists : bool
 |          True if track exists for segment
 |  
 |  itersegments(self)
 |      Segment iterator
 |  
 |  itertracks(self, label=False)
 |  
 |  label_coverage(self, label)
 |      Parameters
 |      ----------
 |      label :
 |      
 |      Returns
 |      -------
 |  
 |  label_duration(self, label)
 |  
 |  label_timeline(self, label)
 |      Get timeline for a given label
 |      
 |      Parameters
 |      ----------
 |      label :
 |      
 |      Returns
 |      -------
 |      timeline : :class:`Timeline`
 |          Timeline made of all segments annotated with `label`
 |  
 |  labels(self, unknown=True)
 |      List of labels
 |      
 |      Parameters
 |      ----------
 |      unknown : bool, optional
 |          When False, do not return Unknown instances
 |          When True, return any label (even Unknown instances)
 |      
 |      Returns
 |      -------
 |      labels : list
 |          Sorted list of labels
 |  
 |  new_track(self, segment, candidate=None, prefix=None)
 |      Track name generator
 |      
 |      Parameters
 |      ----------
 |      segment : Segment
 |      prefix : str, optional
 |      candidate : any valid track name
 |      
 |      Returns
 |      -------
 |      track : str
 |          New track name
 |  
 |  retrack(self)
 |  
 |  smooth(self, collar=0.0)
 |      Smooth annotation
 |      
 |      Create new annotation where contiguous tracks with same label are
 |      merged into one longer track.
 |      
 |      Parameters
 |      ----------
 |      collar : float
 |          If collar is positive, also merge tracks separated by less than
 |          collar duration.
 |      
 |      Returns
 |      -------
 |      annotation : Annotation
 |          New annotation where contiguous tracks with same label are merged
 |          into one long track.
 |      
 |      Remarks
 |      -------
 |          Track names are lost in the process.
 |  
 |  subset(self, labels, invert=False)
 |      Annotation subset
 |      
 |      Extract annotation subset based on labels
 |      
 |      Parameters
 |      ----------
 |      labels : iterable
 |          Label iterable.
 |      invert : bool, optional
 |          If invert is True, extract all but requested `labels`
 |      
 |      Returns
 |      -------
 |      subset : `Annotation`
 |          Annotation subset.
 |  
 |  translate(self, translation)
 |      Translate labels
 |      
 |      Parameters
 |      ----------
 |      translation: dict
 |          Label translation.
 |          Labels with no associated translation are kept unchanged.
 |      
 |      Returns
 |      -------
 |      translated : :class:`Annotation`
 |          New annotation with translated labels.
 |  
 |  update(self, annotation)
 |      Update existing annotations or create new ones
 |      
 |      Parameters
 |      ----------
 |      annotation : Annotation
 |          Updated (or new) annotations
 |      
 |      Returns
 |      -------
 |      self : Annotation
 |          Updated annotations.
 |  
 |  ----------------------------------------------------------------------
 |  Class methods defined here:
 |  
 |  from_df(cls, df, uri=None, modality=None) from __builtin__.type
 |      Parameters
 |      ----------
 |      df : DataFrame
 |          Must contain the following columns: 'segment', 'track' and 'label'
 |      uri : str, optional
 |          Resource identifier
 |      modality : str, optional
 |          Modality
 |      
 |      Returns
 |      -------
 |  
 |  from_json(cls, data) from __builtin__.type
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  uri
 |      Resource identifier