Cued Speech keys automatic generator from a sequence of phonemes.
Module CuedSpeech.whatkey
Class sppasWhatKeyPredictor
Description
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]
