跳轉到

Utilities

kudio.util.tools

Small generic helpers: timing decorators and list utilities.

Classes:

Name Description
Timer

Decorator: run func in a daemon thread, optionally joining for at most

Functions:

Name Description
timer

Decorator: log the wall-clock run time of func.

find_duplicate

Return the Counter dict of file_list, logging duplicated entries.

Timer

Timer(time_delay: float)

Decorator: run func in a daemon thread, optionally joining for at most time_delay seconds.

>>> @Timer(time_delay=3)
... def job(): ...
>>> thread = job()   # returns the started thread
Source code in kudio/util/tools.py
def __init__(self, time_delay: float):
    self.time_delay = time_delay

timer

timer(func: Callable) -> Callable

Decorator: log the wall-clock run time of func.

Source code in kudio/util/tools.py
def timer(func: Callable) -> Callable:
    """Decorator: log the wall-clock run time of *func*."""

    @wraps(func)
    def wrapper(*args, **kwargs):
        before = time.time()
        result = func(*args, **kwargs)
        log.info("%s(): %.7f s", func.__name__, time.time() - before)
        return result

    return wrapper

find_duplicate

find_duplicate(
    file_list: Iterable[Hashable],
) -> Dict[Hashable, int]

Return the Counter dict of file_list, logging duplicated entries.

Source code in kudio/util/tools.py
def find_duplicate(file_list: Iterable[Hashable]) -> Dict[Hashable, int]:
    """Return the ``Counter`` dict of *file_list*, logging duplicated entries."""
    counts = dict(Counter(file_list))
    duplicates = {key: value for key, value in counts.items() if value > 1}
    if duplicates:
        log.info("Duplicates: %s", duplicates)
    return counts

kudio.util.parallel

Parallel batch processing helpers.

Functions:

Name Description
map_waves

Apply fn to every item, optionally across workers processes.

map_waves

map_waves(
    fn: Callable,
    items: Iterable,
    workers: int = 1,
    desc: Optional[str] = None,
    chunksize: int = 1,
) -> List

Apply fn to every item, optionally across workers processes.

With workers <= 1 runs sequentially (easier to debug); otherwise uses a multiprocessing pool. fn must be top-level (picklable) for workers > 1.

Source code in kudio/util/parallel.py
def map_waves(fn: Callable, items: Iterable, workers: int = 1,
              desc: Optional[str] = None, chunksize: int = 1) -> List:
    """Apply *fn* to every item, optionally across ``workers`` processes.

    With ``workers <= 1`` runs sequentially (easier to debug); otherwise uses a
    multiprocessing pool. *fn* must be top-level (picklable) for ``workers > 1``.
    """
    items = list(items)
    if workers and workers > 1 and len(items) > 1:
        with Pool(workers) as pool:
            return list(tqdm(pool.imap(fn, items, chunksize=chunksize),
                             total=len(items), desc=desc or "map_waves"))
    return [fn(x) for x in tqdm(items, desc=desc or "map_waves")]

kudio.util.colors

ANSI-colored console helpers used by the CLI tools and the GUI.

Colors

ANSI escape sequences (SGR codes).

kudio.util.conv

Data conversion/export helpers.

Functions:

Name Description
data2xlsx

Write a dict or DataFrame to a sheet of an .xlsx file.

lowercase

The strings in lst, lower-cased. Anything that is not a string is

uppercase

The strings in lst, upper-cased. Anything that is not a string is

data2xlsx

data2xlsx(
    data: Union[dict, DataFrame],
    xlsx_dir: Union[str, Path],
    sheet_name: str,
    index: bool = False,
    fit_column: bool = True,
) -> Optional[str]

Write a dict or DataFrame to a sheet of an .xlsx file.

If the file already exists, the sheet is replaced; other sheets are kept. With fit_column=True, column widths are auto-sized to the content.

Source code in kudio/util/conv.py
def data2xlsx(data: Union[dict, pd.DataFrame], xlsx_dir: Union[str, Path],
              sheet_name: str, index: bool = False,
              fit_column: bool = True) -> Optional[str]:
    """Write a dict or DataFrame to a sheet of an ``.xlsx`` file.

    If the file already exists, the sheet is replaced; other sheets are kept.
    With ``fit_column=True``, column widths are auto-sized to the content.
    """
    pd = _pandas()
    if isinstance(data, dict):
        try:
            data_frame = pd.DataFrame.from_dict(data, orient='columns')
        except Exception as e:
            log.error("data2xlsx: bad data: %s", e)
            return f'[data2xlsx] data error: {e}'
    elif isinstance(data, pd.DataFrame):
        data_frame = data
    else:
        return '[data2xlsx] check the data type'

    xlsx_dir = Path(xlsx_dir)
    xlsx_dir.parent.mkdir(parents=True, exist_ok=True)

    # one writer for both cases: a new file used to skip fit_column entirely
    append = xlsx_dir.is_file()
    with pd.ExcelWriter(xlsx_dir, engine='openpyxl', mode='a' if append else 'w',
                        **({'if_sheet_exists': 'replace'} if append else {})) as writer:
        data_frame.to_excel(writer, sheet_name=sheet_name, index=index)
        if fit_column:
            from openpyxl.utils import get_column_letter
            worksheet = writer.book[sheet_name]
            for column_cells in worksheet.columns:
                length = max(len(str(cell.value)) for cell in column_cells)
                worksheet.column_dimensions[
                    get_column_letter(column_cells[0].column)].width = length
    return None

lowercase

lowercase(lst: Iterable) -> List[str]

The strings in lst, lower-cased. Anything that is not a string is left out.

Source code in kudio/util/conv.py
def lowercase(lst: Iterable) -> List[str]:
    """The strings in *lst*, lower-cased. Anything that is not a string is
    left out."""
    return [_.lower() for _ in lst if isinstance(_, str)]

uppercase

uppercase(lst: Iterable) -> List[str]

The strings in lst, upper-cased. Anything that is not a string is left out.

Source code in kudio/util/conv.py
def uppercase(lst: Iterable) -> List[str]:
    """The strings in *lst*, upper-cased. Anything that is not a string is
    left out."""
    return [_.upper() for _ in lst if isinstance(_, str)]

kudio.util.visual

Plotting helpers: waveforms, spectrograms, training histories.

Classes:

Name Description
Common

Base figure wrapper with save/show/close helpers.

WaveVisualizer

Plot waveform + spectrogram side by side for one or more files.

Functions:

Name Description
history_plot

Plot every history curve whose key contains metrics (Keras History).

Common

Common(fig_dir: str = 'kd_figs', is_save: bool = False)

Base figure wrapper with save/show/close helpers.

Methods:

Name Description
plot_stft_w_2mfe

Waveform + two mel spectrograms (full band vs. sub_mfe_fq).

Source code in kudio/util/visual.py
def __init__(self, fig_dir: str = 'kd_figs', is_save: bool = False):
    self.is_save = is_save
    if self.is_save:
        fig_dir = Path(fig_dir).absolute()
        fig_dir.mkdir(parents=True, exist_ok=True)

    self.plt = plt
    self.fig = self.plt.figure(figsize=(7, 5))
    self.plt.subplots_adjust(wspace=0.3, hspace=0.3)

plot_stft_w_2mfe staticmethod

plot_stft_w_2mfe(
    f: Union[str, Path],
    n_mels: int = 40,
    sub_mfe_fq: int = 48000,
) -> None

Waveform + two mel spectrograms (full band vs. sub_mfe_fq).

Source code in kudio/util/visual.py
@staticmethod
def plot_stft_w_2mfe(f: Union[str, Path], n_mels: int = 40,
                     sub_mfe_fq: int = 48000) -> None:
    """Waveform + two mel spectrograms (full band vs. ``sub_mfe_fq``)."""
    f = Path(f)
    y, sr = librosa.load(str(f), sr=None, mono=True)

    plt.figure(figsize=(12, 10), dpi=200)

    plt.subplot(311)
    plt.title(f.stem)
    librosa.display.waveshow(y, sr=sr)

    plt.subplot(312)
    plt.title(f'Mel Spectrogram {sr}Hz n:{n_mels}')
    mel_spect = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=n_mels)
    librosa.display.specshow(librosa.power_to_db(mel_spect, ref=np.max),
                             sr=sr, x_axis='time', y_axis='mel', fmax=sr // 2)

    plt.subplot(313)
    plt.title(f'Mel Spectrogram {sub_mfe_fq}Hz n:{n_mels}')
    mel_spect = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=n_mels,
                                               fmax=sub_mfe_fq)
    librosa.display.specshow(librosa.power_to_db(mel_spect, ref=np.max),
                             sr=sr, x_axis='time', y_axis='mel', fmax=sub_mfe_fq)

    plt.tight_layout()
    plt.show()
    plt.close()

WaveVisualizer

WaveVisualizer(
    fig_size: Tuple[float, float] = (7, 5),
    dpi: int = 200,
    subplots: Optional[int] = None,
)

Bases: Common

Plot waveform + spectrogram side by side for one or more files.

Source code in kudio/util/visual.py
def __init__(self, fig_size: Tuple[float, float] = (7, 5), dpi: int = 200,
             subplots: Optional[int] = None):
    super().__init__()
    self.plt = plt
    self.row_len = None
    self.wave_length = 0
    if subplots is not None:
        self.row_len = subplots
        self.fig, self.ax = plt.subplots(self.row_len, 2, figsize=(11, 8))
    else:
        self.fig = self.plt.figure(figsize=fig_size, dpi=dpi)
    self.plt.subplots_adjust(wspace=0.3, hspace=0.3)

history_plot

history_plot(
    history: Any,
    metrics: str = "loss",
    save_fig: Optional[str] = None,
) -> None

Plot every history curve whose key contains metrics (Keras History).

Source code in kudio/util/visual.py
def history_plot(history: Any, metrics: str = 'loss',
                 save_fig: Optional[str] = None) -> None:
    """Plot every history curve whose key contains *metrics* (Keras History)."""
    for_legend = []
    color = ['r', 'g', 'b', 'c', 'm', 'y', 'k', 'b--', 'r:', 'c:', 'm:', 'k:', 'g--', 'y--']
    for index, key in enumerate(history.history.keys()):
        if metrics in key:
            plt.plot(history.history[key], color[index % len(color)], label=key)
            for_legend.append(key)
    plt.legend(for_legend, loc='upper left', fontsize='small')
    plt.title(metrics)
    plt.xlabel('Epoch')
    plt.ylabel(metrics)
    plt.grid(True)
    if save_fig is not None:
        save_fig = Path(save_fig)
        save_fig.parent.mkdir(parents=True, exist_ok=True)
        if save_fig.suffix != '.png':
            save_fig = save_fig.with_suffix('.png')
        plt.savefig(save_fig.parent / f"{metrics}_{save_fig.name}", dpi=150)
    plt.show()
    plt.close()

hist_plt

hist_plt(hist, fig_dir=None, show: bool = False)

Plot Keras accuracy/loss curves side by side.

Source code in kudio/util/visual.py
def hist_plt(hist, fig_dir=None, show: bool = False):
    """Plot Keras accuracy/loss curves side by side."""
    plt.figure(figsize=(10, 5))
    plt.subplot(1, 2, 1)
    plt.plot(hist.history['accuracy'])
    plt.plot(hist.history['val_accuracy'])
    plt.title('Model Accuracy')
    plt.ylabel('Accuracy')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Validation'], loc='upper left')

    plt.subplot(1, 2, 2)
    plt.plot(hist.history['loss'])
    plt.plot(hist.history['val_loss'])
    plt.title('Model Loss')
    plt.ylabel('Loss')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Validation'], loc='upper left')

    plt.tight_layout()
    if fig_dir is not None:
        plt.savefig(str(fig_dir), dpi=150)
    if show:
        plt.show()
        plt.close()

basic_analyze

basic_analyze(
    file, show: bool = False, save_dir=None
) -> None

Waveform / STFT / zero-crossing-rate overview of a file.

Source code in kudio/util/visual.py
def basic_analyze(file, show: bool = False, save_dir=None) -> None:
    """Waveform / STFT / zero-crossing-rate overview of a file."""
    x, sr = file_load(file)

    plt.figure(figsize=(10, 8), dpi=200)
    plt.subplot(311)
    plt.title(Path(file).stem)
    librosa.display.waveshow(x, sr=sr)

    X = librosa.stft(x)
    Xdb = librosa.amplitude_to_db(abs(X))
    plt.subplot(312)
    plt.title('STFT')
    librosa.display.specshow(Xdb, sr=sr, x_axis='time', y_axis='hz', cmap='jet')

    plt.subplot(313)
    plt.title('ZCR')
    zcrs = librosa.feature.zero_crossing_rate(x + 0.0001)
    time2 = np.linspace(0, int(len(x) / sr), len(zcrs[0]))
    plt.plot(time2, zcrs[0])
    plt.axis([0, int(len(x) / sr), 0, max(zcrs[0])])

    plt.tight_layout()

    if save_dir is not None:
        plt.savefig(str(save_dir))
    if show:
        plt.show()
    plt.close()

plot_4_features

plot_4_features(
    file, show: bool = False, save_dir=None, dpi: int = 200
) -> None

Waveform + spectral centroid/rolloff/ZCR and power spectral density.

Source code in kudio/util/visual.py
def plot_4_features(file, show: bool = False, save_dir=None, dpi: int = 200) -> None:
    """Waveform + spectral centroid/rolloff/ZCR and power spectral density."""
    x, sr = file_load(file)

    fig, ax = plt.subplots(2, 1, figsize=(10, 10), dpi=dpi)

    librosa.display.waveshow(x, sr=sr, alpha=0.4, ax=ax[0], label='waveform')
    plot_spectral_centroid(ax[0], x, sr, color='r', label='spectral_centroid')
    plot_spectral_rolloff(ax[0], x, sr, color='b', label='spectral_rolloff')
    plot_zcr(ax[0], x, sr, color='g', label='zcr')
    plot_power_spectral_density(ax[1], x, sr)

    ax[0].set_xlim(0, int(len(x) / sr))
    ax[0].grid(which='major', axis='both')
    ax[0].set_title('spectral centroid & spectral rolloff & zcr')

    plt.tight_layout()
    if save_dir is not None:
        plt.savefig(save_dir)
    if show:
        plt.show()
    plt.close()

plot_3_features

plot_3_features(
    file, show: bool = False, save_dir=None, dpi: int = 200
) -> None

Waveform + spectral centroid/rolloff/ZCR.

Source code in kudio/util/visual.py
def plot_3_features(file, show: bool = False, save_dir=None, dpi: int = 200) -> None:
    """Waveform + spectral centroid/rolloff/ZCR."""
    x, sr = file_load(file)

    fig, ax = plt.subplots(figsize=(7, 5), dpi=dpi)

    librosa.display.waveshow(x, sr=sr, alpha=0.4, ax=ax, label='waveform')
    plot_spectral_centroid(ax, x, sr, color='r', label='spectral_centroid')
    plot_spectral_rolloff(ax, x, sr, color='b', label='spectral_rolloff')
    plot_zcr(ax, x, sr, color='g', label='zcr')

    ax.set_xlim(0, int(len(x) / sr))
    ax.grid(which='major', axis='both')
    ax.set_title('spectral centroid & spectral rolloff & zcr')

    plt.tight_layout()
    if save_dir is not None:
        plt.savefig(save_dir)
    if show:
        plt.show()
    plt.close()

plot_PSD

plot_PSD(
    file, show: bool = False, save_dir=None, dpi: int = 200
) -> None

Power spectral density of a file.

Source code in kudio/util/visual.py
def plot_PSD(file, show: bool = False, save_dir=None, dpi: int = 200) -> None:
    """Power spectral density of a file."""
    x, sr = file_load(file)

    fig, ax = plt.subplots(figsize=(7, 5), dpi=dpi)
    plot_power_spectral_density(ax, x, sr)

    plt.tight_layout()
    if save_dir is not None:
        plt.savefig(save_dir)
    if show:
        plt.show()
    plt.close()

normalize

normalize(x, axis: int = 0)

Min-max scale to [0, 1] along axis.

Source code in kudio/util/visual.py
def normalize(x, axis: int = 0):
    """Min-max scale to [0, 1] along *axis*."""
    x = np.asarray(x, dtype=float)
    x_min = x.min(axis=axis, keepdims=True)
    x_range = x.max(axis=axis, keepdims=True) - x_min
    x_range[x_range == 0] = 1.0
    return (x - x_min) / x_range