Auto-CS 2.0

https://sourceforge.net/projects/autocs/

Module CuedSpeech.whatkey

Class CuedSpeechCueingRules

Description

Rules data structure for a system to predict Cued Speech keys.

Example
 >>> rules = CuedSpeechCueingRules()
 >>> result = rules.load(FRA_KEYS)
 >>> assert(result is True)
 >>> rules.get_key("e")
 > "t"

Constructor

Create a new instance.

Parameters
  • filename: (str) Name of the file with the rules.
View Source
def __init__(self, filename=None):
    """Create a new instance.

    :param filename: (str) Name of the file with the rules.

    """
    self.__phon = dict()
    self.__shptgt = dict()
    self.__phonmerge = dict()
    if filename is not None:
        self.load(filename)
    else:
        self.reset()

Public functions

reset

Reset the set of rules.

View Source
def reset(self) -> None:
    """Reset the set of rules.

        """
    self.__phon = dict()
    self.__shptgt = dict()
    self.__phonmerge = dict()
    for phone in symbols.all:
        self.__phon[phone] = (CuedSpeechCueingRules.NEUTRAL_CLASS, None)
    self.__phon['vnone'] = (CuedSpeechCueingRules.NEUTRAL_CLASS, 'n')
    self.__phon['cnone'] = (CuedSpeechCueingRules.NEUTRAL_CLASS, '0')
    self.__phon['vnil'] = (CuedSpeechCueingRules.NEUTRAL_CLASS, None)
    self.__phon['cnil'] = (CuedSpeechCueingRules.NEUTRAL_CLASS, None)

load

Load the rules from a file.

Invalidate the already defined set of rules.

Parameters
  • filename: (str) Name of the file with the rules.
Raises
  • sppasIOError: If the file cannot be opened.
  • sppasError: File is not a regular Cued Speech file.
Returns
  • (bool) Rules were appended or not
View Source
def load(self, filename: str) -> bool:
    """Load the rules from a file.

        Invalidate the already defined set of rules.

        :param filename: (str) Name of the file with the rules.
        :raises: sppasIOError: If the file cannot be opened.
        :raises: sppasError: File is not a regular Cued Speech file.
        :return: (bool) Rules were appended or not

        """
    self.reset()
    if os.path.exists(filename) is True and os.path.isfile(filename):
        with open(filename, 'r') as f:
            lines = f.readlines()
            f.close()
    else:
        raise sppasIOError(filename)
    added = False
    for (line_nb, line) in enumerate(lines, 1):
        sp = sppasUnicode(line)
        line = sp.to_strip()
        if line.startswith('#') is True:
            continue
        columns = line.split()
        if len(columns) == 4 and columns[0] == 'PHONMERGE':
            sequence = tuple(columns[1:-1])
            effective_class = columns[-1]
            if len(sequence) == 0:
                raise sppasError(f'Invalid PHONMERGE at line {line_nb}: empty sequence.')
            if effective_class not in CuedSpeechCueingRules.PHON_CLASSES:
                raise sppasError('Invalid PHONMERGE class. One of {:s} was expected. Got {:s} instead.'.format(CuedSpeechCueingRules.PHON_CLASSES, effective_class))
            if sequence in self.__phonmerge:
                raise sppasError(f'Duplicated PHONMERGE at line {line_nb}: {sequence} is already defined.')
            self.__phonmerge[sequence] = effective_class
            added = True
        elif len(columns) == 3:
            p = columns[1]
            if p not in self.__phon:
                self.__phon[p] = (None, None)
            tup = self.__phon[p]
            if columns[0] == 'PHONCLASS':
                if columns[2] not in CuedSpeechCueingRules.PHON_CLASSES:
                    raise sppasError('Invalid PHONCLASS. One of {:s} was expected. Got {:s} instead.'.format(CuedSpeechCueingRules.PHON_CLASSES, columns[2]))
                if columns[1] not in ('cnone', 'vnone'):
                    self.__phon[columns[1]] = (columns[2], tup[1])
                    added = True
            elif columns[0] == 'PHONKEY':
                if columns[1] not in ('cnone', 'vnone'):
                    self.__phon[columns[1]] = (tup[0], columns[2])
                    added = True
            elif columns[0] == 'SHAPETARGET':
                if columns[1] not in self.__shptgt:
                    try:
                        target = int(columns[2])
                        if 0 <= target <= 20:
                            self.__shptgt[columns[1]] = target
                        else:
                            raise sppasError(f'Invalid SHAPETARGET value {target}')
                    except ValueError:
                        raise sppasError(f"SHAPETARGET '{columns[2]}' is not an integer for shape {columns[1]}")
                else:
                    raise sppasError(f'shape {columns[1]} is already defined.')
    return added

get_class

Return the class identifier of the phoneme.

If the phoneme is unknown, the neutral class is returned.

Parameters
  • phoneme: (str) A phoneme
Returns
  • class of the phoneme or neutral class
View Source
def get_class(self, phoneme: str) -> str:
    """Return the class identifier of the phoneme.

        If the phoneme is unknown, the neutral class is returned.

        :param phoneme: (str) A phoneme
        :return: class of the phoneme or neutral class

        """
    tup = self.__phon.get(phoneme, None)
    if tup is None or (tup is not None and tup[0] is None):
        for seq in self.__phonmerge:
            _joined_seq = ''.join(seq)
            if phoneme == _joined_seq:
                return self.__phonmerge[seq]
        return CuedSpeechCueingRules.NEUTRAL_CLASS
    return tup[0]

get_merged_class

Return the effective class for a phoneme sequence.

Parameters
  • sequence: (tuple) Sequence of phones to merge.
Returns
  • (str or None) The effective class, or None.
View Source
def get_merged_class(self, sequence: tuple):
    """Return the effective class for a phoneme sequence.

        :param sequence: (tuple) Sequence of phones to merge.
        :return: (str or None) The effective class, or None.

        """
    if isinstance(sequence, tuple) is False:
        raise TypeError(f'Given sequence must be a tuple. Got {type(sequence)} instead.')
    return self.__phonmerge.get(sequence)

get_vowels_codes

Return the list of key codes of all the positions (vowels).

View Source
def get_vowels_codes(self) -> list:
    """Return the list of key codes of all the positions (vowels).

        """
    vowels_codes = list()
    for phon in self.__phon:
        phon_key = self.get_key(phon)
        if phon_key is not None:
            if self.get_class(phon) == 'V':
                vowels_codes.append(phon_key)
            elif self.get_class(phon) == 'W':
                for key_code in phon_key:
                    vowels_codes.append(key_code)
    return [self.get_neutral_vowel()] + sorted(list(set(vowels_codes)), reverse=True)

get_consonants_codes

Return the list of key codes of all the shapes (consonants).

View Source
def get_consonants_codes(self) -> list:
    """Return the list of key codes of all the shapes (consonants).

        """
    cons_codes = list()
    for phon in self.__phon:
        phon_key = self.get_key(phon)
        if self.get_class(phon) == 'C' and phon_key is not None:
            cons_codes.append(phon_key)
    return [self.get_neutral_consonant()] + sorted(list(set(cons_codes)))

get_key

Return the key identifier of the phoneme.

None is returned if the phoneme is unknown or if it is a break. If the phoneme is known but no key was defined for this phoneme, the "nil" key is returned.

Parameters
  • phoneme: (str) A phoneme or a diphthong
Returns
  • key or tuple(key,key) if diphtong or None if unknown
View Source
def get_key(self, phoneme: str) -> tuple:
    """Return the key identifier of the phoneme.

        None is returned if the phoneme is unknown or if it is a break.
        If the phoneme is known but no key was defined for this phoneme,
        the "nil" key is returned.

        :param phoneme: (str) A phoneme or a diphthong
        :return: key or tuple(key,key) if diphtong or None if unknown

        """
    if self.get_class(phoneme) == 'W':
        return self.get_diphthong_key(phoneme)
    tup = self.__phon.get(phoneme, None)
    if tup is None:
        return None
    key = tup[1]
    if key is None:
        if tup[0] in ('V', 'W'):
            key = self.__phon['vnil'][1]
        elif tup[0] == 'C':
            key = self.__phon['cnil'][1]
    return key

get_diphthong_key

Return the key identifiers of the given diphthong.

Parameters
  • diphthong: (str) A diphthong made of 2 phonemes
Returns
  • tuple(key,key) or None if unknown
View Source
def get_diphthong_key(self, diphthong: str) -> tuple[str, str] | None:
    """Return the key identifiers of the given diphthong.

        :param diphthong: (str) A diphthong made of 2 phonemes
        :return: tuple(key,key) or None if unknown

        """
    if len(diphthong) == 2:
        tup = self.__phon.get(diphthong[0], None)
        if tup is None:
            return None
        key1 = tup[1]
        tup = self.__phon.get(diphthong[1], None)
        if tup is None:
            return None
        key2 = tup[1]
        return (key1, key2)
    return None

get_nil_consonant

Return the key code for a missing consonant.

View Source
def get_nil_consonant(self) -> str:
    """Return the key code for a missing consonant."""
    return self.__phon['cnil'][1]

get_nil_vowel

Return the key code for a missing vowel.

View Source
def get_nil_vowel(self) -> str:
    """Return the key code for a missing vowel."""
    return self.__phon['vnil'][1]

get_neutral_vowel

Return the key code of the neutral position (vowel).

View Source
def get_neutral_vowel(self) -> str:
    """Return the key code of the neutral position (vowel)."""
    return self.__phon['vnone'][1]

get_neutral_consonant

Return the key code of the neutral shape (consonant).

View Source
def get_neutral_consonant(self) -> str:
    """Return the key code of the neutral shape (consonant)."""
    return self.__phon['cnone'][1]

get_shape_target

Return the target index of the given shape.

Parameters
  • shape: (str) Shape code
Returns
  • (int) target index of the given shape or the default target index
View Source
def get_shape_target(self, shape: str) -> int:
    """Return the target index of the given shape.

        :param shape: (str) Shape code
        :return: (int) target index of the given shape or the default target index

        """
    return self.__shptgt.get(shape, CuedSpeechCueingRules.SHAPE_TARGET)

get_phon_target

Return the target index of the given phoneme.

Parameters
  • phoneme: (str) Shape code
Returns
  • (int) target index of the given phoneme or the default target index
View Source
def get_phon_target(self, phoneme: str) -> int:
    """Return the target index of the given phoneme.

        :param phoneme: (str) Shape code
        :return: (int) target index of the given phoneme or the default target index

        """
    _tup = self.__phon.get(phoneme, None)
    if _tup is None:
        return CuedSpeechCueingRules.SHAPE_TARGET
    return self.__shptgt.get(_tup[1], CuedSpeechCueingRules.SHAPE_TARGET)

get_merged_phone

Return the merged phone for a phoneme sequence.

Parameters
  • sequence: (tuple) Sequence of phones to merge.
Returns
  • (str or None) The merged phone, or None.
View Source
def get_merged_phone(self, sequence: tuple):
    """Return the merged phone for a phoneme sequence.

        :param sequence: (tuple) Sequence of phones to merge.
        :return: (str or None) The merged phone, or None.

        """
    if isinstance(sequence, tuple) is False:
        raise TypeError(f'Given sequence must be a tuple. Got {type(sequence)} instead.')
    return self.__phonmerge.get(sequence)

has_phonmerge

Return True if at least one merge rule is defined.

View Source
def has_phonmerge(self) -> bool:
    """Return True if at least one merge rule is defined."""
    return len(self.__phonmerge) > 0

syll_to_key

Return the key codes matching the given syllable.

The given entry can be either of the form: C-V or C- or C or -V or V.

Example
 >>> syll_to_key("-E")
 > ('5', 'c')
 >>> syll_to_key("E")
 > ('5', 'c')
 >>> syll_to_key("p-")
 > ('1', 's')
 >>> syll_to_key("p")
 > ('1', 's')
 >>> syll_to_key("p-E")
 > ('1', 'c')
 >>> syll_to_key("p-aI")
 > (('1', 's'), ('5', 't'))
 >>> syll_to_key("hw-E")
 > ('4', 'c')
Parameters
  • syll: (str) A syllable like "p-a", or "p-" or "-a" or "p-aI" or "hw-E".
Returns
  • (tuple or None) Key codes
Raises
  • sppasCuedRulesValueError: malformed syll
  • sppasCuedRulesMinValueError: not enough phonemes in syll
  • sppasCuedRulesMaxValueError: too many phonemes in syll
View Source
def syll_to_key(self, syll: str) -> tuple:
    """Return the key codes matching the given syllable.

        The given entry can be either of the form:
        C-V or C- or C or -V or V.

        :example:
        >>> syll_to_key("-E")
        ('5', 'c')
        >>> syll_to_key("E")
        ('5', 'c')
        >>> syll_to_key("p-")
        ('1', 's')
        >>> syll_to_key("p")
        ('1', 's')
        >>> syll_to_key("p-E")
        ('1', 'c')
        >>> syll_to_key("p-aI")
        (('1', 's'), ('5', 't'))
        >>> syll_to_key("hw-E")
        ('4', 'c')

        :param syll: (str) A syllable like "p-a", or "p-" or "-a" or "p-aI" or "hw-E".
        :return: (tuple or None) Key codes
        :raises: sppasCuedRulesValueError: malformed syll
        :raises: sppasCuedRulesMinValueError: not enough phonemes in syll
        :raises: sppasCuedRulesMaxValueError: too many phonemes in syll

        """
    phons = self.__syll_to_phons(syll)
    _class_1 = self.get_class(phons[0])
    _class_2 = self.get_class(phons[1])
    if _class_1 not in ('N', 'C') or _class_2 not in ('N', 'V', 'W'):
        raise sppasCuedRulesValueError(syll)
    key_cons = self.get_key(phons[0])
    key_vowel = self.get_key(phons[1])
    if key_cons is None or key_vowel is None:
        raise sppasCuedRulesValueError(syll)
    if self.get_class(phons[1]) == 'W':
        return ((key_cons, key_vowel[0]), (self.get_nil_consonant(), key_vowel[1]))
    return (key_cons, key_vowel)

Protected functions

__syll_to_phons

Return the phonemes matching the given syllable.

Raises
  • sppasCuedRulesValueError: malformed syll
  • sppasCuedRulesMinValueError: not enough phonemes in syll
  • sppasCuedRulesMaxValueError: too many phonemes in syll
Returns
  • (tuple) Tuple with (consonant, vowel)
Parameters
  • syll
View Source
def __syll_to_phons(self, syll: str) -> list:
    """Return the phonemes matching the given syllable.

        :raises: sppasCuedRulesValueError: malformed syll
        :raises: sppasCuedRulesMinValueError: not enough phonemes in syll
        :raises: sppasCuedRulesMaxValueError: too many phonemes in syll
        :return: (tuple) Tuple with (consonant, vowel)

        """
    if len(syll.strip()) == 0 or syll.strip() == '-':
        raise sppasCuedRulesValueError(syll)
    phons = syll.split(separators.phonemes)
    if len(phons) == 0:
        raise sppasCuedRulesMinValueError(syll)
    elif len(phons) == 1:
        if self.get_class(phons[0]) == 'V':
            phons.insert(0, 'cnil')
        elif self.get_class(phons[0]) == 'C':
            phons.append('vnil')
        else:
            phons.insert(0, 'unknown')
    elif len(phons) == 2:
        if len(phons[0]) == 0:
            phons[0] = 'cnil'
        if len(phons[1]) == 0:
            phons[1] = 'vnil'
    else:
        raise sppasCuedRulesMaxValueError(syll)
    return phons