#!/usr/bin/env python # encoding: utf-8 # The MIT License (MIT) # Copyright (c) 2012-2019 CNRS # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # AUTHORS # Hervé BREDIN - http://herve.niderb.fr import warnings from typing import Optional, Tuple, Union from pyannote_audio_utils.core import Timeline, Segment, Annotation class UEMSupportMixin: """Provides 'uemify' method with optional (à la NIST) collar""" def extrude(self, uem: Timeline, reference: Annotation, collar: float = 0.0, skip_overlap: bool = False) -> Timeline: """Extrude reference boundary collars from uem reference |----| |--------------| |-------------| uem |---------------------| |-------------------------------| extruded |--| |--| |---| |-----| |-| |-----| |-----------| |-----| Parameters ---------- uem : Timeline Evaluation map. reference : Annotation Reference annotation. collar : float, optional When provided, set the duration of collars centered around reference segment boundaries that are extruded from both reference and hypothesis. Defaults to 0. (i.e. no collar). skip_overlap : bool, optional Set to True to not evaluate overlap regions. Defaults to False (i.e. keep overlap regions). Returns ------- extruded_uem : Timeline """ if collar == 0. and not skip_overlap: return uem collars, overlap_regions = [], [] # build list of collars if needed if collar > 0.: # iterate over all segments in reference for segment in reference.itersegments(): # add collar centered on start time t = segment.start collars.append(Segment(t - .5 * collar, t + .5 * collar)) # add collar centered on end time t = segment.end collars.append(Segment(t - .5 * collar, t + .5 * collar)) # build list of overlap regions if needed if skip_overlap: # iterate over pair of intersecting segments for (segment1, track1), (segment2, track2) in reference.co_iter(reference): if segment1 == segment2 and track1 == track2: continue # add their intersection overlap_regions.append(segment1 & segment2) segments = collars + overlap_regions return Timeline(segments=segments).support().gaps(support=uem) def common_timeline(self, reference: Annotation, hypothesis: Annotation) \ -> Timeline: """Return timeline common to both reference and hypothesis reference |--------| |------------| |---------| |----| hypothesis |--------------| |------| |----------------| timeline |--|-----|----|---|-|------| |-|---------|----| |----| Parameters ---------- reference : Annotation hypothesis : Annotation Returns ------- timeline : Timeline """ timeline = reference.get_timeline(copy=True) timeline.update(hypothesis.get_timeline(copy=False)) return timeline.segmentation() def project(self, annotation: Annotation, timeline: Timeline) -> Annotation: """Project annotation onto timeline segments reference |__A__| |__B__| |____C____| timeline |---|---|---| |---| projection |_A_|_A_|_C_| |_B_| |_C_| Parameters ---------- annotation : Annotation timeline : Timeline Returns ------- projection : Annotation """ projection = annotation.empty() timeline_ = annotation.get_timeline(copy=False) for segment_, segment in timeline_.co_iter(timeline): for track_ in annotation.get_tracks(segment_): track = projection.new_track(segment, candidate=track_) projection[segment, track] = annotation[segment_, track_] return projection def uemify(self, reference: Annotation, hypothesis: Annotation, uem: Optional[Timeline] = None, collar: float = 0., skip_overlap: bool = False, returns_uem: bool = False, returns_timeline: bool = False) \ -> Union[ Tuple[Annotation, Annotation], Tuple[Annotation, Annotation, Timeline], Tuple[Annotation, Annotation, Timeline, Timeline], ]: """Crop 'reference' and 'hypothesis' to 'uem' support Parameters ---------- reference, hypothesis : Annotation Reference and hypothesis annotations. uem : Timeline, optional Evaluation map. collar : float, optional When provided, set the duration of collars centered around reference segment boundaries that are extruded from both reference and hypothesis. Defaults to 0. (i.e. no collar). skip_overlap : bool, optional Set to True to not evaluate overlap regions. Defaults to False (i.e. keep overlap regions). returns_uem : bool, optional Set to True to return extruded uem as well. Defaults to False (i.e. only return reference and hypothesis) returns_timeline : bool, optional Set to True to oversegment reference and hypothesis so that they share the same internal timeline. Returns ------- reference, hypothesis : Annotation Extruded reference and hypothesis annotations uem : Timeline Extruded uem (returned only when 'returns_uem' is True) timeline : Timeline: Common timeline (returned only when 'returns_timeline' is True) """ # when uem is not provided, use the union of reference and hypothesis # extents -- and warn the user about that. if uem is None: r_extent = reference.get_timeline().extent() h_extent = hypothesis.get_timeline().extent() extent = r_extent | h_extent uem = Timeline(segments=[extent] if extent else [], uri=reference.uri) warnings.warn( "'uem' was approximated by the union of 'reference' " "and 'hypothesis' extents.") # extrude collars (and overlap regions) from uem uem = self.extrude(uem, reference, collar=collar, skip_overlap=skip_overlap) # extrude regions outside of uem reference = reference.crop(uem, mode='intersection') hypothesis = hypothesis.crop(uem, mode='intersection') # project reference and hypothesis on common timeline if returns_timeline: timeline = self.common_timeline(reference, hypothesis) reference = self.project(reference, timeline) hypothesis = self.project(hypothesis, timeline) result = (reference, hypothesis) if returns_uem: result += (uem,) if returns_timeline: result += (timeline,) return result