Source code for tffpy.create_subregions

# -*- coding: utf-8 -*-
# ######### COPYRIGHT #########
# Credits
# #######
#
# Copyright(c) 2020-2020
# ----------------------
#
# * Laboratoire d'Informatique et Systèmes <http://www.lis-lab.fr/>
# * Université d'Aix-Marseille <http://www.univ-amu.fr/>
# * Centre National de la Recherche Scientifique <http://www.cnrs.fr/>
# * Université de Toulon <http://www.univ-tln.fr/>
#
# Contributors
# ------------
#
# * `Valentin Emiya <mailto:valentin.emiya@lis-lab.fr>`_
# * `Ama Marina Krémé <mailto:ama-marina.kreme@lis-lab.fr>`_
#
# This package has been created thanks to the joint work with Florent Jaillet
# and Ronan Hamon on other packages.
#
# Description
# -----------
#
# Time frequency fading using Gabor multipliers
#
# Version
# -------
#
# * tffpy version = 0.1.4
#
# Licence
# -------
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# ######### COPYRIGHT #########
"""

.. moduleauthor:: Valentin Emiya
"""
# TODO check if eigs(, 1) can be replaced by Halko to run faster
from pathlib import Path
import warnings
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import label
from scipy.sparse.linalg import eigs

from tffpy.utils import plot_mask
from tffpy.tf_tools import GaborMultiplier


[docs]def create_subregions(mask_bool, dgt_params, signal_params, tol, fig_dir=None, return_norms=False): """ Create sub-regions from boolean mask and tolerance on sub-region distance. See Algorithm 3 *Finding sub-regions for TFF-P* in the reference paper. Parameters ---------- mask_bool : nd-array Time-frequency boolean mask dgt_params : dict DGT parameters signal_params : dict Signal parameters tol : float Tolerance on sub-region distance (spectral norm of the composition of the Gabor multipliers related to two candidate sub-regions). fig_dir : Path If not None, folder where figures are stored. If None, figures are not plotted. return_norms : bool If True, the final distance matrix is returned as a second output. Returns ------- mask_labeled : nd-array Time-frequency mask with one positive integer for each sub-region and zeros outside sub-regions. pq_norms : nd-array Matrix of distances between sub-regions. """ mask_labeled, n_labels = label(mask_bool) pq_norms = _get_pq_norms(mask=mask_labeled, dgt_params=dgt_params, signal_params=signal_params) if fig_dir is not None: fig_dir = Path(fig_dir) fig_dir.mkdir(parents=True, exist_ok=True) plt.figure() plot_mask(mask=mask_labeled, hop=dgt_params['hop'], n_bins=dgt_params['n_bins'], fs=signal_params['fs']) plt.set_cmap('nipy_spectral') plt.title('Initial subregions') plt.savefig(fig_dir / 'initial_subregions.pdf') # from matplotlib.colors import LogNorm plt.figure() with warnings.catch_warnings(): warnings.simplefilter("ignore") plt.imshow(np.log10(pq_norms+pq_norms.T), origin='lower') plt.ylabel('Sub-region index') plt.xlabel('Sub-region index') plt.colorbar() plt.set_cmap('viridis') plt.title('Initial norms of Gabor multiplier composition') plt.savefig(fig_dir / 'initial_norms.pdf') n_labels_max = n_labels while pq_norms.max() > tol: # Merge each pair (p, q), q < p, such that pq_norms[p, q] > tol to_be_updated = [False] * n_labels while pq_norms.max() > tol: i_p, i_q = np.unravel_index(np.argmax(pq_norms, axis=None), pq_norms.shape) mask_labeled, pq_norms = _merge_subregions(mask=mask_labeled, pq_norms=pq_norms, i_p=i_p, i_q=i_q) to_be_updated[i_q] = True to_be_updated[i_p] = to_be_updated[-1] to_be_updated = to_be_updated[:-1] n_labels -= 1 for i_p in range(n_labels): if to_be_updated[i_p]: _update_pq_norms(mask=mask_labeled, pq_norms=pq_norms, i_p=i_p, dgt_params=dgt_params, signal_params=signal_params) # print('Merge sub-region p={}'.format(i_p)) if fig_dir is not None: plt.figure() plot_mask(mask=mask_labeled, hop=dgt_params['hop'], n_bins=dgt_params['n_bins'], fs=signal_params['fs']) plt.title('subregions') plt.set_cmap('nipy_spectral') plt.savefig(fig_dir / 'subregions_i{}.pdf' .format(n_labels_max-n_labels)) plt.figure() with warnings.catch_warnings(): warnings.simplefilter("ignore") plt.imshow(np.log10(pq_norms+pq_norms.T), origin='lower') plt.ylabel('Sub-region index') plt.xlabel('Sub-region index') plt.colorbar() plt.set_cmap('viridis') plt.title('norms of Gabor multiplier composition') plt.savefig(fig_dir / 'norms__i{}.pdf' .format(n_labels_max-n_labels)) if fig_dir is not None: plt.figure() plot_mask(mask=mask_labeled, hop=dgt_params['hop'], n_bins=dgt_params['n_bins'], fs=signal_params['fs']) plt.title('Final subregions') plt.set_cmap('nipy_spectral') plt.savefig(fig_dir / 'final_subregions.pdf') plt.figure() with warnings.catch_warnings(): warnings.simplefilter("ignore") plt.imshow(np.log10(pq_norms+pq_norms.T), origin='lower') plt.ylabel('Sub-region index') plt.xlabel('Sub-region index') plt.colorbar() plt.set_cmap('viridis') plt.title('Final norms of Gabor multiplier composition') plt.savefig(fig_dir / 'final_norms.pdf') if return_norms: return mask_labeled, pq_norms else: return mask_labeled
def _get_pq_norms(mask, dgt_params, signal_params): """ Compute distance matrix between sub-regions. Parameters ---------- mask : nd-array Time-frequency mask with one positive integer for each sub-region and zeros outside sub-regions. dgt_params : dict DGT parameters signal_params : dict Signal parameters Returns ------- pq_norms : nd-array Matrix of distances between sub-regions. """ n_labels = np.unique(mask).size - 1 pq_norms = np.zeros((n_labels, n_labels)) for i_p in range(n_labels): for i_q in range(i_p): gabmul_p = GaborMultiplier(mask=(mask == i_p + 1), dgt_params=dgt_params, signal_params=signal_params) gabmul_q = GaborMultiplier(mask=(mask == i_q + 1), dgt_params=dgt_params, signal_params=signal_params) gabmul_pq = gabmul_p @ gabmul_q pq_norms[i_p, i_q] = \ np.real(eigs(A=gabmul_pq, k=1, return_eigenvectors=False)[0]) return pq_norms def _update_pq_norms(mask, pq_norms, i_p, dgt_params, signal_params): """ Update (in-place) distance between one particular sub-region and all sub-regions in distance matrix. Parameters ---------- mask : nd-array Time-frequency mask with one positive integer for each sub-region and zeros outside sub-regions. pq_norms : nd-array Matrix of distances between sub-regions, updated in-place. i_p : int Index of sub-region to be updated dgt_params : dict DGT parameters signal_params : dict Signal parameters """ n_labels = pq_norms.shape[0] gabmul_p = GaborMultiplier(mask=(mask == i_p + 1), dgt_params=dgt_params, signal_params=signal_params) for i_q in range(n_labels): if i_p == i_q: continue gabmul_q = GaborMultiplier(mask=(mask == i_q + 1), dgt_params=dgt_params, signal_params=signal_params) gabmul_pq = gabmul_p @ gabmul_q gabmul_pq_norm = \ np.real(eigs(A=gabmul_pq, k=1, return_eigenvectors=False)[0]) if i_q < i_p: pq_norms[i_p, i_q] = gabmul_pq_norm else: pq_norms[i_q, i_p] = gabmul_pq_norm def _merge_subregions(mask, pq_norms, i_p, i_q): """ Merge two sub-regions indexed by `i_p` and `i_q` In the time-frequency mask, the label of the region indexed by `i_p` will be replace by the label of the region indexed by `i_q` and index `i_p` will be used to relabel the region with highest label. In the distance matrix, rows and columns will be moved consequently. The distance between the new, merged sub-region and all other sub-regions is not updated; it can be done by calling :py:func:`_update_pq_norms`. Parameters ---------- mask : nd-array Time-frequency mask with one positive integer for each sub-region and zeros outside sub-regions. pq_norms : nd-array Matrix of distances between sub-regions. i_p : int Index of sub-region that will be removed after merging. i_q : int Index of sub-region that will receive the result. Returns ------- mask : nd-array Updated time-frequency mask with one positive integer for each sub-region and zeros outside sub-regions. pq_norms : nd-array Updated distance matrix (except for distance with the new sub-region). """ p = i_p + 1 q = i_q + 1 n_labels = pq_norms.shape[0] mask[mask == p] = q mask[mask == n_labels] = p pq_norms[i_p, :i_p - 1] = pq_norms[-1, :i_p - 1] pq_norms[i_p:, i_p] = pq_norms[-1, i_p:] pq_norms = pq_norms[:-1, :-1] return mask, pq_norms