ba4be087d5
Create PR to main with cherry-pick from release / cherry-pick (push) Failing after 0s
CICD NeMo / pre-flight (push) Failing after 0s
CICD NeMo / configure (push) Has been skipped
Build, validate, and release Neural Modules / pre-flight (push) Failing after 1s
CICD NeMo / code-linting (push) Has been skipped
Build, validate, and release Neural Modules / release (push) Has been skipped
Build, validate, and release Neural Modules / release-summary (push) Has been cancelled
CICD NeMo / cicd-test-container-build (push) Has been cancelled
CICD NeMo / cicd-import-tests (push) Has been cancelled
CICD NeMo / L0_Setup_Test_Data_And_Models (push) Has been cancelled
CICD NeMo / cicd-main-unit-tests (push) Has been cancelled
CICD NeMo / cicd-main-speech (push) Has been cancelled
CICD NeMo / Nemo_CICD_Test (push) Has been cancelled
CICD NeMo / Coverage (e2e) (push) Has been cancelled
CICD NeMo / Coverage (unit-test) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
CICD NeMo / cicd-wait-in-queue (push) Has been cancelled
142 lines
6.2 KiB
Python
142 lines
6.2 KiB
Python
# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from lhotse import SupervisionSegment
|
|
|
|
from nemo.collections.asr.parts.utils.vad_utils import (
|
|
align_labels_to_frames,
|
|
convert_labels_to_speech_segments,
|
|
frame_vad_construct_supervisions_per_file,
|
|
get_frame_labels,
|
|
get_nonspeech_segments,
|
|
load_speech_overlap_segments_from_rttm,
|
|
load_speech_segments_from_rttm,
|
|
read_rttm_as_supervisions,
|
|
)
|
|
|
|
|
|
def get_simple_rttm_without_overlap(rttm_file="test1.rttm"):
|
|
line = "SPEAKER <NA> 1 0 2 <NA> <NA> speech <NA> <NA>\n"
|
|
speech_segments = [[0.0, 2.0]]
|
|
with open(rttm_file, "w") as f:
|
|
f.write(line)
|
|
return rttm_file, speech_segments
|
|
|
|
|
|
def get_simple_rttm_with_overlap(rttm_file="test2.rttm"):
|
|
speech_segments = [[0.0, 3.0]]
|
|
overlap_segments = [[1.0, 2.0]]
|
|
with open(rttm_file, "w") as f:
|
|
f.write("SPEAKER <NA> 1 0 2 <NA> <NA> speech <NA> <NA>\n")
|
|
f.write("SPEAKER <NA> 1 1 2 <NA> <NA> speech <NA> <NA>\n")
|
|
return rttm_file, speech_segments, overlap_segments
|
|
|
|
|
|
def get_simple_rttm_with_silence(rttm_file="test3.rttm"):
|
|
line = "SPEAKER <NA> 1 1 2 <NA> <NA> speech <NA> <NA>\n"
|
|
speech_segments = [[1.0, 2.0]]
|
|
silence_segments = [[0.0, 1.0]]
|
|
with open(rttm_file, "w") as f:
|
|
f.write(line)
|
|
return rttm_file, speech_segments, silence_segments
|
|
|
|
|
|
class TestVADUtils:
|
|
@pytest.mark.parametrize(["logits_len", "labels_len"], [(20, 10), (20, 11), (20, 9), (10, 21), (10, 19)])
|
|
@pytest.mark.unit
|
|
def test_align_label_logits(self, logits_len, labels_len):
|
|
logits = np.arange(logits_len).tolist()
|
|
labels = np.arange(labels_len).tolist()
|
|
labels_new = align_labels_to_frames(probs=logits, labels=labels)
|
|
|
|
assert len(labels_new) == len(logits)
|
|
|
|
@pytest.mark.unit
|
|
def test_load_speech_segments_from_rttm(self, test_data_dir):
|
|
rttm_file, speech_segments = get_simple_rttm_without_overlap(test_data_dir + "/test1.rttm")
|
|
speech_segments_new = load_speech_segments_from_rttm(rttm_file)
|
|
assert speech_segments_new == speech_segments
|
|
|
|
@pytest.mark.unit
|
|
def test_load_speech_overlap_segments_from_rttm(self, test_data_dir):
|
|
rttm_file, speech_segments, overlap_segments = get_simple_rttm_with_overlap(test_data_dir + "/test2.rttm")
|
|
speech_segments_new, overlap_segments_new = load_speech_overlap_segments_from_rttm(rttm_file)
|
|
assert speech_segments_new == speech_segments
|
|
assert overlap_segments_new == overlap_segments
|
|
|
|
@pytest.mark.unit
|
|
def test_get_nonspeech_segments(self, test_data_dir):
|
|
rttm_file, speech_segments, silence_segments = get_simple_rttm_with_silence(test_data_dir + "/test3.rttm")
|
|
speech_segments_new = load_speech_segments_from_rttm(rttm_file)
|
|
silence_segments_new = get_nonspeech_segments(speech_segments_new)
|
|
assert silence_segments_new == silence_segments
|
|
|
|
@pytest.mark.unit
|
|
def test_get_frame_labels(self, test_data_dir):
|
|
rttm_file, speech_segments = get_simple_rttm_without_overlap(test_data_dir + "/test4.rttm")
|
|
speech_segments_new = load_speech_segments_from_rttm(rttm_file)
|
|
frame_labels = get_frame_labels(speech_segments_new, 0.02, 0.0, 3.0, as_str=False)
|
|
assert frame_labels[0] == 1
|
|
assert len(frame_labels) == 150
|
|
|
|
@pytest.mark.unit
|
|
def test_convert_labels_to_speech_segments(self, test_data_dir):
|
|
rttm_file, speech_segments = get_simple_rttm_without_overlap(test_data_dir + "/test5.rttm")
|
|
speech_segments_new = load_speech_segments_from_rttm(rttm_file)
|
|
frame_labels = get_frame_labels(speech_segments_new, 0.02, 0.0, 3.0, as_str=False)
|
|
speech_segments_new = convert_labels_to_speech_segments(frame_labels, 0.02)
|
|
assert speech_segments_new == speech_segments
|
|
|
|
@pytest.mark.unit
|
|
def test_read_rttm_as_supervisions(self, test_data_dir):
|
|
rttm_file, speech_segments = get_simple_rttm_without_overlap(test_data_dir + "/test6.rttm")
|
|
annotation = read_rttm_as_supervisions(rttm_file)
|
|
assert _annotation_equals(annotation, [(0.0, 2.0, 'speech')])
|
|
|
|
@pytest.mark.unit
|
|
def test_frame_vad_construct_supervisions_per_file(self, test_data_dir):
|
|
rttm_file, speech_segments = get_simple_rttm_without_overlap(test_data_dir + "/test7.rttm")
|
|
# test for rttm input
|
|
ref, hyp = frame_vad_construct_supervisions_per_file(rttm_file, rttm_file)
|
|
expected = [(0.0, 2.0, 'speech')]
|
|
assert _annotation_equals(ref, expected)
|
|
assert _annotation_equals(hyp, expected)
|
|
|
|
# test for list input
|
|
speech_segments = load_speech_segments_from_rttm(rttm_file)
|
|
frame_labels = get_frame_labels(speech_segments, 0.02, 0.0, 3.0, as_str=False)
|
|
speech_segments_new = convert_labels_to_speech_segments(frame_labels, 0.02)
|
|
assert speech_segments_new == speech_segments
|
|
ref, hyp = frame_vad_construct_supervisions_per_file(frame_labels, frame_labels, 0.02)
|
|
assert _annotation_equals(ref, expected)
|
|
assert _annotation_equals(hyp, expected)
|
|
|
|
|
|
def _annotation_equals(annotation, expected_segments, *, atol=1e-6):
|
|
"""Compare a list of :class:`lhotse.SupervisionSegment` to expected ``(start, end, speaker)`` tuples."""
|
|
assert isinstance(annotation, list)
|
|
assert all(isinstance(s, SupervisionSegment) for s in annotation)
|
|
if len(annotation) != len(expected_segments):
|
|
return False
|
|
for seg, (exp_start, exp_end, exp_spk) in zip(annotation, expected_segments):
|
|
if abs(float(seg.start) - exp_start) > atol:
|
|
return False
|
|
if abs(float(seg.end) - exp_end) > atol:
|
|
return False
|
|
if seg.speaker != exp_spk:
|
|
return False
|
|
return True
|