CoolFace
Modelpublic

RASMUS/Finnish-ASR-Canary-v2

sourceHugging Facemitupdated 7mo agoView on Hugging Face
0likes1.2kdownloads
multispeaker_data_analysis.py289 linesDownload Raw Back to speaker_tasks
1# Copyright (c) 2023, NVIDIA CORPORATION.  All rights reserved.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#     http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14 15import argparse16import multiprocessing17import shutil18from collections import OrderedDict19from pathlib import Path20from pprint import pprint21from typing import Dict22 23import matplotlib.pyplot as plt24import numpy as np25import seaborn as sns26import sox27from scipy.stats import expon28from tqdm import tqdm29 30from nemo.collections.asr.parts.utils.vad_utils import (31    get_nonspeech_segments,32    load_speech_overlap_segments_from_rttm,33    plot_sample_from_rttm,34)35 36"""37This script analyzes multi-speaker speech dataset and generates statistics.38The input directory </path/to/rttm_and_wav_directory> is required to contain the following files:39    - rttm files (*.rttm)40    - wav files (*.wav)41 42Usage:43    python <NEMO_ROOT>/scripts/speaker_tasks/multispeaker_data_analysis.py \44        </path/to/rttm_and_wav_directory> \45        --session_dur 20 \46        --silence_mean 0.2 \47        --silence_var 100 \48        --overlap_mean 0.15 \49        --overlap_var 50 \50        --num_workers 8 \51        --num_samples 10 \52        --output_dir <path/to/output_directory>53"""54 55 56def process_sample(sess_dict: Dict) -> Dict:57    """58    Process each synthetic sample59 60    Args:61        sess_dict (dict): dictionary containing the following keys62            rttm_file (str): path to the rttm file63            session_dur (float): duration of the session (specified by argument)64            precise (bool): whether to measure the precise duration of the session using sox65 66    Returns:67        results (dict): dictionary containing the following keys68            session_dur (float): duration of the session69            silence_len_list (list): list of silence durations of each silence occurrence70            silence_dur (float): total silence duration in a session71            silence_ratio (float): ratio of silence duration to session duration72            overlap_len_list (list): list of overlap durations of each overlap occurrence73            overlap_dur (float): total overlap duration74            overlap_ratio (float): ratio of overlap duration to speech (non-silence) duration75    """76 77    rttm_file = sess_dict["rttm_file"]78    session_dur = sess_dict["session_dur"]79    precise = sess_dict["precise"]80    if precise or session_dur is None:81        wav_file = rttm_file.parent / Path(rttm_file.stem + ".wav")82        session_dur = sox.file_info.duration(str(wav_file))83 84    speech_seg, overlap_seg = load_speech_overlap_segments_from_rttm(rttm_file)85    speech_dur = sum([sess_dict[1] - sess_dict[0] for sess_dict in speech_seg])86 87    silence_seg = get_nonspeech_segments(speech_seg, session_dur)88    silence_len_list = [sess_dict[1] - sess_dict[0] for sess_dict in silence_seg]89    silence_dur = max(0, session_dur - speech_dur)90    silence_ratio = silence_dur / session_dur91 92    overlap_len_list = [sess_dict[1] - sess_dict[0] for sess_dict in overlap_seg]93    overlap_dur = sum(overlap_len_list) if len(overlap_len_list) else 094    overlap_ratio = overlap_dur / speech_dur95 96    results = {97        "session_dur": session_dur,98        "silence_len_list": silence_len_list,99        "silence_dur": silence_dur,100        "silence_ratio": silence_ratio,101        "overlap_len_list": overlap_len_list,102        "overlap_dur": overlap_dur,103        "overlap_ratio": overlap_ratio,104    }105 106    return results107 108 109def run_multispeaker_data_analysis(110    input_dir,111    session_dur=None,112    silence_mean=None,113    silence_var=None,114    overlap_mean=None,115    overlap_var=None,116    precise=False,117    save_path=None,118    num_workers=1,119) -> Dict:120    rttm_list = list(Path(input_dir).glob("*.rttm"))121    """122    Analyze the multispeaker data and plot the distribution of silence and overlap durations.123 124    Args:125        input_dir (str): path to the directory containing the rttm files126        session_dur (float): duration of the session (specified by argument)127        silence_mean (float): mean of the silence duration distribution128        silence_var (float): variance of the silence duration distribution129        overlap_mean (float): mean of the overlap duration distribution130        overlap_var (float): variance of the overlap duration distribution131        precise (bool): whether to measure the precise duration of the session using sox132        save_path (str): path to save the plots133 134    Returns:135        stats (dict): dictionary containing the statistics of the analyzed data136    """137 138    print(f"Found {len(rttm_list)} files to be processed")139    if len(rttm_list) == 0:140        raise ValueError(f"No rttm files found in {input_dir}")141 142    silence_duration = 0.0143    total_duration = 0.0144    overlap_duration = 0.0145 146    silence_ratio_all = []147    overlap_ratio_all = []148    silence_length_all = []149    overlap_length_all = []150 151    queue = []152    for rttm_file in tqdm(rttm_list):153        queue.append(154            {"rttm_file": rttm_file, "session_dur": session_dur, "precise": precise,}155        )156 157    if num_workers <= 1:158        results = [process_sample(sess_dict) for sess_dict in tqdm(queue)]159    else:160        with multiprocessing.Pool(processes=num_workers) as p:161            results = list(tqdm(p.imap(process_sample, queue), total=len(queue), desc='Processing', leave=True,))162 163    for item in results:164        total_duration += item["session_dur"]165        silence_duration += item["silence_dur"]166        overlap_duration += item["overlap_dur"]167 168        silence_length_all += item["silence_len_list"]169        overlap_length_all += item["overlap_len_list"]170 171        silence_ratio_all.append(item["silence_ratio"])172        overlap_ratio_all.append(item["overlap_ratio"])173 174    actual_silence_mean = silence_duration / total_duration175    actual_silence_var = np.var(silence_ratio_all)176    actual_overlap_mean = overlap_duration / (total_duration - silence_duration)177    actual_overlap_var = np.var(overlap_ratio_all)178 179    stats = OrderedDict()180    stats["total duration (hours)"] = f"{total_duration / 3600:.2f}"181    stats["number of sessions"] = len(rttm_list)182    stats["average session duration (seconds)"] = f"{total_duration / len(rttm_list):.2f}"183    stats["actual silence ratio mean/var"] = f"{actual_silence_mean:.4f}/{actual_silence_var:.4f}"184    stats["actual overlap ratio mean/var"] = f"{actual_overlap_mean:.4f}/{actual_overlap_var:.4f}"185    stats["expected silence ratio mean/var"] = f"{silence_mean}/{silence_var}"186    stats["expected overlap ratio mean/var"] = f"{overlap_mean}/{overlap_var}"187    stats["save_path"] = save_path188 189    print("-----------------------------------------------")190    print("                    Results                    ")191    print("-----------------------------------------------")192    for k, v in stats.items():193        print(k, ": ", v)194    print("-----------------------------------------------")195 196    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(14, 14))197    fig.suptitle(198        f"Average session={total_duration/len(rttm_list):.2f} seconds, num sessions={len(rttm_list)}, total={total_duration/3600:.2f} hours"199    )200    sns.histplot(silence_ratio_all, ax=ax1)201    ax1.set_xlabel("Silence ratio in a session")202    ax1.set_title(203        f"Target silence mean={silence_mean}, var={silence_var}. \nActual silence ratio={actual_silence_mean:.4f}, var={actual_silence_var:.4f}"204    )205 206    _, scale = expon.fit(silence_length_all, floc=0)207    sns.histplot(silence_length_all, ax=ax2)208    ax2.set_xlabel("Per-silence length in seconds")209    ax2.set_title(f"Per-silence length histogram, \nfitted exponential distribution with mean={scale:.4f}")210 211    sns.histplot(overlap_ratio_all, ax=ax3)212    ax3.set_title(213        f"Target overlap mean={overlap_mean}, var={overlap_var}. \nActual ratio={actual_overlap_mean:.4f}, var={actual_overlap_var:.4f}"214    )215    ax3.set_xlabel("Overlap ratio in a session")216    _, scale2 = expon.fit(overlap_length_all, floc=0)217    sns.histplot(overlap_length_all, ax=ax4)218    ax4.set_title(f"Per overlap length histogram, \nfitted exponential distribution with mean={scale2:.4f}")219    ax4.set_xlabel("Duration in seconds")220 221    if save_path:222        fig.savefig(save_path)223        print(f"Figure saved at: {save_path}")224 225    return stats226 227 228def visualize_multispeaker_data(input_dir: str, output_dir: str, num_samples: int = 10) -> None:229    """230    Visualize a set of randomly sampled data in the input directory231 232    Args:233        input_dir (str): Path to the input directory234        output_dir (str): Path to the output directory235        num_samples (int): Number of samples to visualize236    """237    rttm_list = list(Path(input_dir).glob("*.rttm"))238    idx_list = np.random.permutation(len(rttm_list))[:num_samples]239    print(f"Visualizing {num_samples} random samples")240    for idx in idx_list:241        rttm_file = rttm_list[idx]242        audio_file = rttm_file.parent / Path(rttm_file.stem + ".wav")243        output_file = Path(output_dir) / Path(rttm_file.stem + ".png")244        plot_sample_from_rttm(audio_file=audio_file, rttm_file=rttm_file, save_path=str(output_file), show=False)245    print(f"Sample plots saved at: {output_dir}")246 247 248if __name__ == "__main__":249    parser = argparse.ArgumentParser()250    parser.add_argument("input_dir", default="", help="Input directory")251    parser.add_argument("-sd", "--session_dur", default=None, type=float, help="Duration per session in seconds")252    parser.add_argument("-sm", "--silence_mean", default=None, type=float, help="Expected silence ratio mean")253    parser.add_argument("-sv", "--silence_var", default=None, type=float, help="Expected silence ratio variance")254    parser.add_argument("-om", "--overlap_mean", default=None, type=float, help="Expected overlap ratio mean")255    parser.add_argument("-ov", "--overlap_var", default=None, type=float, help="Expected overlap ratio variance")256    parser.add_argument("-w", "--num_workers", default=1, type=int, help="Number of CPU workers to use")257    parser.add_argument("-s", "--num_samples", default=10, type=int, help="Number of random samples to plot")258    parser.add_argument("-o", "--output_dir", default="analysis/", type=str, help="Directory for saving output figure")259    parser.add_argument(260        "--precise", action="store_true", help="Set to get precise duration, with significant time cost"261    )262    args = parser.parse_args()263 264    print("Running with params:")265    pprint(vars(args))266 267    output_dir = Path(args.output_dir)268    if output_dir.exists():269        print(f"Removing existing output directory: {args.output_dir}")270        shutil.rmtree(str(output_dir))271    output_dir.mkdir(parents=True)272 273    run_multispeaker_data_analysis(274        input_dir=args.input_dir,275        session_dur=args.session_dur,276        silence_mean=args.silence_mean,277        silence_var=args.silence_var,278        overlap_mean=args.overlap_mean,279        overlap_var=args.overlap_var,280        precise=args.precise,281        save_path=str(Path(args.output_dir, "statistics.png")),282        num_workers=args.num_workers,283    )284 285    visualize_multispeaker_data(input_dir=args.input_dir, output_dir=args.output_dir, num_samples=args.num_samples)286 287    print("The multispeaker data analysis has been completed.")288    print(f"Please check the output directory: \n{args.output_dir}")289