Auto-CS 2.0

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

Module CuedSpeech.whatkey

Class sppasWhatKeyPredictor

Description

Cued Speech keys automatic generator from a sequence of phonemes.

Constructor

Instantiate a CS generator.

Parameters
  • cue_rules: (CuedSpeechKeys) Rules to convert phonemes to keys
View Source
def __init__(self, cue_rules: CuedSpeechKeys=CuedSpeechKeys()):
    """Instantiate a CS generator.

    :param cue_rules: (CuedSpeechKeys) Rules to convert phonemes to keys

    """
    self.__cued = None
    self.set_cue_rules(cue_rules)

Public functions

set_cue_rules

Set new rules.

Parameters
  • cue_rules: (CuedSpeechKeys) Rules and codes for vowel positions and hand shapes
View Source
def set_cue_rules(self, cue_rules: CuedSpeechKeys) -> None:
    """Set new rules.

        :param cue_rules: (CuedSpeechKeys) Rules and codes for vowel positions and hand shapes

        """
    if isinstance(cue_rules, CuedSpeechKeys) is False:
        raise sppasTypeError('cue_rules', 'CuedSpeechKeys')
    self.__cued = cue_rules

phons_to_segments

Convert time-aligned phonemes into CS segments.

PhonAlign: | b | O~ | Z | u | R | CS-PhonSegments: | b O~ | Z u | R |

Parameters
  • phonemes: (sppasTier) time-aligned phonemes tier
Returns
  • (sppasTier) Phonemes grouped into key segments
View Source
def phons_to_segments(self, phonemes: sppasTier) -> sppasTier:
    """Convert time-aligned phonemes into CS segments.

        PhonAlign:            |  b   | O~ |  Z |  u |  R   |
        CS-PhonSegments:      |  b O~     |  Z u    |  R   |

        :param phonemes: (sppasTier) time-aligned phonemes tier
        :return: (sppasTier) Phonemes grouped into key segments

        """
    if isinstance(phonemes, sppasTier) is False:
        raise sppasTypeError('phons', 'sppasTier')
    segments_tier = sppasTier('CS-PhonSegments')
    segments_tier.set_meta('cued_speech_segments_of_tier', phonemes.get_name())
    intervals = sppasWhatKeyPredictor._phon_to_intervals(phonemes)
    for interval in intervals:
        start_phon_idx = phonemes.lindex(interval.get_lowest_localization())
        if start_phon_idx == -1:
            start_phon_idx = phonemes.mindex(interval.get_lowest_localization(), bound=-1)
        end_phon_idx = phonemes.rindex(interval.get_highest_localization())
        if end_phon_idx == -1:
            end_phon_idx = phonemes.mindex(interval.get_highest_localization(), bound=1)
        if start_phon_idx != -1 and end_phon_idx != -1:
            self.__gen_key_segments(phonemes, start_phon_idx, end_phon_idx, segments_tier)
        else:
            logging.warning(info(1224, 'annotations').format(interval))
    return segments_tier

segments_to_keys

Create tiers with the CS denomination and the class of each phoneme.

CS-PhonSegments: | b O~ | Z u | R | # | CS-PhonStructs: | C V | C V | C | | CS-Keys: | 4 m | 1 c | 3 n | 0 n | CS-KeysClass: | C V | C V | C N | N N |

Parameters
  • segments: (sppasTier) A tier in which key segments
  • start_point: (sppasPoint)
  • end_point: (sppasPoint)
Returns
  • (sppasTier, sppasTier, sppasTier) CS-Keys CS-KeysClass CS-PhonStructs
View Source
def segments_to_keys(self, segments: sppasTier, start_point: sppasPoint | None=None, end_point: sppasPoint=None) -> tuple:
    """Create tiers with the CS denomination and the class of each phoneme.

        CS-PhonSegments:      |  b O~     |  Z u    |   R   |   #   |
        CS-PhonStructs:       |  C V      |  C V    |   C   |       |
        CS-Keys:              |  4 m      |  1 c    |  3 n  |  0 n  |
        CS-KeysClass:         |  C V      |  C V    |  C N  |  N N  |

        :param segments: (sppasTier) A tier in which key segments
        :param start_point: (sppasPoint)
        :param end_point: (sppasPoint)
        :returns: (sppasTier, sppasTier, sppasTier) CS-Keys CS-KeysClass CS-PhonStructs

        """
    key_tier = sppasTier('CS-Keys')
    key_tier.set_meta('cued_speech_key_of_tier', segments.get_name())
    class_tier = sppasTier('CS-KeysClass')
    class_tier.set_meta('cued_speech_phonclass_of_tier', segments.get_name())
    struct_tier = sppasTier('CS-PhonStructs')
    struct_tier.set_meta('cued_speech_struct_of_tier', segments.get_name())
    for ann in segments:
        phons = [label.copy() for label in ann.get_labels()]
        if len(phons) == 0:
            raise ValueError('A CS key should contain at least one phoneme.Got {:d} instead.'.format(len(phons)))
        if len(phons) > 2:
            raise ValueError('A CS key should contain at max two phonemes.Got {:d} instead.'.format(len(phons)))
        if len(phons) == 1:
            content = phons[0].get_best().get_content()
            if self.__cued.get_class(content) == 'V':
                phons.insert(0, sppasLabel(sppasTag('cnil')))
            elif self.__cued.get_class(content) == 'C':
                phons.append(sppasLabel(sppasTag('vnil')))
            else:
                phons.insert(0, sppasLabel(sppasTag(symbols.unk)))
        consonant = phons[0].get_best().get_content()
        consonant_class = self.__cued.get_class(consonant)
        vowel = phons[1].get_best().get_content()
        vowel_class = self.__cued.get_class(vowel)
        labels = self.__create_labels(self.__cued.get_key(consonant), self.__cued.get_key(vowel), phons[0].get_key(), phons[1].get_key())
        a1 = ann.copy()
        a1.set_labels(labels)
        key_tier.append(a1)
        labels = self.__create_labels(consonant_class, vowel_class, phons[0].get_key(), phons[1].get_key())
        a2 = ann.copy()
        a2.set_labels(labels)
        class_tier.append(a2)
        labels = self.__create_labels(consonant_class if consonant_class != 'N' else None, vowel_class if vowel_class != 'N' else None, phons[0].get_key(), phons[1].get_key())
        a3 = ann.copy()
        a3.set_labels(labels)
        struct_tier.append(a3)
    self.__fill_key_segments(key_tier, class_tier, start_point, end_point)
    return (key_tier, class_tier, struct_tier)

Private functions

_phon_to_intervals

Create a tier with only the intervals to be syllabified.

We could use symbols.phone only, but for backward compatibility, the symbols used in previous versions of SPPAS are added here.

Parameters
  • phonemes: (sppasTier)
Returns
  • a tier with consecutive filled intervals.
View Source
@staticmethod
def _phon_to_intervals(phonemes: sppasTier) -> sppasTier:
    """Create a tier with only the intervals to be syllabified.

        We could use symbols.phone only, but for backward compatibility,
        the symbols used in previous versions of SPPAS are added here.

        :param phonemes: (sppasTier)
        :return: a tier with consecutive filled intervals.

        """
    stop = list(symbols.phone.keys())
    stop.append('#')
    stop.append('@@')
    stop.append('+')
    stop.append('gb')
    stop.append('lg')
    return phonemes.export_to_intervals(stop)

Protected functions

__gen_key_segments

Perform the key generation of a sequence of phonemes.

Parameters
  • tier_palign: (sppasTier) Time-aligned phonemes
  • from_p: (int) index of the first phoneme to be syllabified
  • to_p: (int) index of the last phoneme to be syllabified
  • tierkeysegs: (sppasTier)
View Source
def __gen_key_segments(self, tier_palign, from_p, to_p, tier_key_segs):
    """Perform the key generation of a sequence of phonemes.

        :param tier_palign: (sppasTier) Time-aligned phonemes
        :param from_p: (int) index of the first phoneme to be syllabified
        :param to_p: (int) index of the last phoneme to be syllabified
        :param tier_key_segs: (sppasTier)

        """
    phons = list()
    for ann in tier_palign[from_p:to_p + 1]:
        tag = ann.get_best_tag()
        phons.append(tag.get_typed_content())
    syll_keys = self.__cued.syllabify(phons)
    for (i, syll) in enumerate(syll_keys):
        (start_idx, end_idx) = syll
        a1 = tier_palign[start_idx + from_p].get_lowest_localization().copy()
        a3 = tier_palign[end_idx + from_p].get_highest_localization().copy()
        location = sppasLocation(sppasInterval(a1, a3))
        labels = list()
        for ann in tier_palign[from_p + start_idx:from_p + end_idx + 1]:
            tag = ann.get_best_tag()
            label = sppasLabel(tag.copy())
            label.set_key(key=ann.get_id())
            labels.append(label)
        tier_key_segs.create_annotation(location, labels)
    return tier_key_segs

__fill_key_segments

Fill the gaps with the neutral shape and neutral position.

Parameters
  • tier_keys: (sppasTier)
  • tier_class: (sppasTier)
View Source
def __fill_key_segments(self, tier_keys, tier_class, start_point, end_point):
    """Fill the gaps with the neutral shape and neutral position.

        :param tier_keys: (sppasTier)
        :param tier_class: (sppasTier)

        """
    if len(tier_keys) == 0:
        return
    cn = self.__cued.get_neutral_consonant()
    vn = self.__cued.get_neutral_vowel()
    prev = None
    prev_shape_key = None
    prev_pos_key = None
    prev_shape_class = None
    prev_pos_class = None
    current_pos_key = None
    current_shape_key = None
    if start_point is not None and tier_keys[0].get_lowest_localization() > start_point:
        interval = sppasInterval(start_point.copy(), tier_keys[0].get_lowest_localization())
        labels = [sppasLabel(sppasTag(cn)), sppasLabel(sppasTag(vn))]
        annotation = sppasAnnotation(sppasLocation(interval), labels)
        tier_keys.add(annotation)
        prev_shape_key = cn
        prev_pos_key = vn
        labels = [sppasLabel(sppasTag(self.__cued.NEUTRAL_CLASS)), sppasLabel(sppasTag(self.__cued.NEUTRAL_CLASS))]
        annotation = sppasAnnotation(sppasLocation(interval.copy()), labels)
        tier_class.add(annotation)
        prev_shape_class = self.__cued.NEUTRAL_CLASS
        prev_pos_class = self.__cued.NEUTRAL_CLASS
    for (a, ac) in zip(tier_keys, tier_class):
        fill_hole = False
        if prev is not None and prev.get_highest_localization() < a.get_lowest_localization():
            interval = sppasInterval(prev.get_highest_localization(), a.get_lowest_localization())
            duration = interval.duration().get_value()
            if prev_shape_key == 's' and duration > sppasWhatKeyPredictor.NEUTRAL_SHAPE_THRESHOLD:
                current_shape_key = cn
                current_shape_class = self.__cued.NEUTRAL_CLASS
                fill_hole = True
            else:
                current_shape_key = prev_shape_key
                current_shape_class = prev_shape_class
            if prev_pos_key is not None and duration > sppasWhatKeyPredictor.NEUTRAL_POSITION_THRESHOLD:
                current_pos_key = vn
                current_pos_class = self.__cued.NEUTRAL_CLASS
                fill_hole = True
                current_shape_key = cn
                current_shape_class = self.__cued.NEUTRAL_CLASS
            else:
                current_pos_key = prev_pos_key
                current_pos_class = prev_pos_class
            if fill_hole is True:
                labels = [sppasLabel(sppasTag(current_shape_key)), sppasLabel(sppasTag(current_pos_key))]
                annotation = sppasAnnotation(sppasLocation(interval), labels)
                tier_keys.add(annotation)
                labels = [sppasLabel(sppasTag(current_shape_class)), sppasLabel(sppasTag(current_pos_class))]
                annotation = sppasAnnotation(sppasLocation(interval), labels)
                tier_class.add(annotation)
                prev = annotation
                prev_shape_key = current_shape_key
                prev_shape_class = current_shape_class
                prev_pos_key = current_pos_key
                prev_pos_class = current_pos_class
        if fill_hole is False:
            prev = a
            prev_shape_key = a.get_labels()[0].get_best().get_content()
            prev_shape_class = ac.get_labels()[0].get_best().get_content()
            prev_pos_key = a.get_labels()[1].get_best().get_content()
            prev_pos_class = ac.get_labels()[1].get_best().get_content()
    if end_point is not None and tier_keys[-1].get_highest_localization() < end_point:
        interval = sppasInterval(tier_keys[-1].get_highest_localization(), end_point.copy())
        labels = [sppasLabel(sppasTag(cn)), sppasLabel(sppasTag(vn))]
        annotation = sppasAnnotation(sppasLocation(interval), labels)
        tier_keys.add(annotation)
        labels = [sppasLabel(sppasTag(self.__cued.NEUTRAL_CLASS)), sppasLabel(sppasTag(self.__cued.NEUTRAL_CLASS))]
        annotation = sppasAnnotation(sppasLocation(interval.copy()), labels)
        tier_class.add(annotation)

__create_labels

View Source
@staticmethod
def __create_labels(consonant, vowel, ann_key_c, ann_key_v):
    tag_c = None
    if consonant is not None:
        tag_c = sppasTag(consonant)
    tag_v = None
    if vowel is not None:
        tag_v = sppasTag(vowel)
    cs_c = sppasLabel(tag_c)
    cs_c.set_key(ann_key_c)
    cs_v = sppasLabel(tag_v)
    cs_v.set_key(ann_key_v)
    return [cs_c, cs_v]