Source code for dialogy.utils.normalize_utterance

"""
This module was created in response to: https://github.com/Vernacular-ai/dialogy/issues/9
we will ship functions to assist normalization of ASR output, we will refer to these as Utterances.
"""
import itertools
from functools import partial
from typing import Any, Callable, Dict, List

from dialogy import constants as const
from dialogy.types.utterances import Transcript, Utterance


[docs]def dict_get(prop: str, obj: Dict[str, Any]) -> Any: """ Get value of prop within obj. This simple function exists to facilitate a partial function defined :ref:`here<is_utterance>`. :param prop: A property within a :code:`dict`. :type prop: str :param obj: A :code:`dict`. :type obj: Dict[str, Any] :return: Value of a property within a :code:`dict`. :rtype: Any """ return obj[prop]
[docs]def is_list(input_: Any) -> bool: """ Check type of :code:`input` :param input_: Any arbitrary input :type input_: Any :return: True if :code:`input` is a :code:`list` else False :rtype: True """ return isinstance(input_, list)
[docs]def is_each_element( type_: type, input_: List[Any], transform: Callable[[Any], Any] = lambda x: x ) -> bool: """ Check if each element in a list is of a given type. :param type_: Expected :code:`Type` of each element in the :code:`input_` which is a :code:`list`. :type type_: Type :param input_: A :code:`list`. :type input_: List[Any] :param transform: We may apply some transforms to each element before making these checks. This is to check if a certain key in a Dict matches the expected type. In case this is not needed, leave the argument unset and an identity transform is applied. Defaults to lambda x:x. :type transform: Callable[[Any], Any] :return: Checks each element in a list to match :code:`type_`, if any element fails the check, this returns False, else True. :rtype: bool """ return all(isinstance(transform(item), type_) for item in input_)
[docs]def is_utterance(maybe_utterance: Any, key: str = const.TRANSCRIPT) -> bool: """ .. _is_utterance: Check input to be of `List[List[Dict]]`. .. code-block: json [[{"transcript": "hi"}]] .. ipython:: python from dialogy.utils.normalize_utterance import is_utterance # 1. :code:`List[List[Dict[str, str]]]` is_utterance([[{"transcript": "this"}, {"transcript": "works"}]]) # 2. key is configurable is_utterance([[{"text": "this"}, {"text": "works"}]], key="text") # 3. Hope for everything else... you have a mastercard. # Or use this lib, works just fine 🍷. is_utterance([{"transcript": "this"}, {"transcript": "doesn't"}, {"transcript": "work"}]) :param maybe_utterance: Arbitrary input. :type maybe_utterance: Any :param key: The key within which transcription string resides. Defaults to :code:`const.TRANSCRIPT`. :type key: str :return: True if the inputs is :code:`List[List[Dict[str, str]]]`, else False. :rtype: bool """ dict_get_key = partial(dict_get, key) try: return all( is_each_element(str, alternatives, transform=dict_get_key) for alternatives in maybe_utterance ) except KeyError: return False except TypeError: return False
[docs]def is_unsqueezed_utterance(maybe_utterance: Any, key: str = const.TRANSCRIPT) -> bool: """ Check input to be of `List[Dict]`. .. ipython:: python from dialogy.utils.normalize_utterance import is_unsqueezed_utterance # 1. This fails is_unsqueezed_utterance([[{"transcript": "this"}, {"transcript": "works"}]]) # 2. key is configurable is_unsqueezed_utterance([{"text": "this"}, {"text": "works"}], key="text") :param maybe_utterance: Arbitrary type input. :type maybe_utterance: Any :param key: The key within which transcription string resides. :type key: str, Defaults to const.TRANSCRIPT. :return: True, if the input is of type :code:`List[Dict[str, Any]]` else False. :rtype: bool """ dict_get_key = partial(dict_get, key) try: return is_each_element(str, maybe_utterance, transform=dict_get_key) except KeyError: return False except TypeError: return False
[docs]def is_list_of_string(maybe_utterance: Any) -> bool: """ Check input to be of `List[str]`. .. ipython:: python from dialogy.utils.normalize_utterance import is_list_of_string is_list_of_string(["this", "works"]) :param maybe_utterance: Arbitrary input. :type maybe_utterance: Any :return: True if :code:`maybe_utterance` is a :code:`str`. :rtype: bool """ try: return is_each_element(str, maybe_utterance) except TypeError: return False
[docs]def is_string(maybe_utterance: Any) -> bool: """ Check input's type is `str`. :param maybe_utterance: Arbitrary type input. :type maybe_utterance: Any :return: True if :code:`maybe_utterance` is a :code:`str`, else False. :rtype: bool """ return isinstance(maybe_utterance, str)
[docs]def utterance2alternatives( utterances: List[Utterance], key: str = const.TRANSCRIPT ) -> List[Transcript]: """ Convert a list of utterances to a list of alternatives. """ return [ " ".join([alternative[key] for alternative in alternatives]) for alternatives in itertools.product(*utterances) ]
[docs]def normalize(maybe_utterance: Any, key: str = const.TRANSCRIPT) -> List[str]: """ Adapt various non-standard ASR alternative forms. The output will be a list of strings since models will expect that. .. _normalize: .. ipython:: python In [1]: from dialogy.utils.normalize_utterance import normalize In [2]: # A popular case In [3]: normalize([[{"transcript": "this"}, {"transcript": "works"}]]) .. ipython:: python In [3]: # A case with multiple utterances In [4]: normalize([ [{"transcript": "hello hello?", "transcript": "yellow yellow?"}], [{"transcript": "I wanted to check"}], [{"transcript": "if you have space for us?"}] ]) .. ipython:: python In [5]: normalize([{"transcript": "I wanted to know umm hello?"}]) .. ipython:: python In [6]: normalize(["I wanted to know umm hello?"]) .. ipython:: python In [7]: normalize("I wanted to know umm hello?") :param maybe_utterance: Arbitrary input. :type maybe_utterance: Any :param key: A string to be looked into :code:`List[List[Dict[str, str]]]`, :code:`List[Dict[str, str]]` type inputs. :type key: str :return: A flattened list of strings parsed from various formats. :rtype: List[str] :raises: TypeError: If :code:`maybe_utterance` is none of the expected types. """ if maybe_utterance is None or not maybe_utterance: return [] if is_utterance(maybe_utterance): return utterance2alternatives(maybe_utterance) if is_unsqueezed_utterance(maybe_utterance): return [alternative[key] for alternative in maybe_utterance] if is_list(maybe_utterance) and is_list_of_string(maybe_utterance): return [utterance for utterance in maybe_utterance] if is_string(maybe_utterance): return [maybe_utterance] else: raise TypeError( f"The input {maybe_utterance} does not belong to any of the expected types:" " List[List[Dict[str, Any]]], List[Dict[str, Any]], List[str] or str." )
[docs]def get_best_transcript(transcripts: List[str]) -> str: """ Select the best transcript from a list of transcripts. The best transcript is the first transcript gven by ASR (20220803) :param transcripts: List of transcripts :type transcripts: List[str] :return: A string containing the best transcript :rtype: str """ if transcripts: return transcripts[0] return ""