speechmetryflow 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. speechmetryflow/__init__.py +25 -0
  2. speechmetryflow/_version.py +34 -0
  3. speechmetryflow/cli.py +131 -0
  4. speechmetryflow/lexical/__init__.py +17 -0
  5. speechmetryflow/lexical/assets/__init__.py +103 -0
  6. speechmetryflow/lexical/assets/concreteness.tsv +39955 -0
  7. speechmetryflow/lexical/assets/familiarity_imageability.tsv +5554 -0
  8. speechmetryflow/lexical/assets/frequency.tsv +74289 -0
  9. speechmetryflow/lexical/assets/valence.tsv +13916 -0
  10. speechmetryflow/lexical/database.py +143 -0
  11. speechmetryflow/lexical/part_of_speech.py +172 -0
  12. speechmetryflow/lexical/utils.py +119 -0
  13. speechmetryflow/pragmatic/__init__.py +60 -0
  14. speechmetryflow/pragmatic/assets.py +62 -0
  15. speechmetryflow/pragmatic/database.py +113 -0
  16. speechmetryflow/semantic/__init__.py +23 -0
  17. speechmetryflow/semantic/assets.py +285 -0
  18. speechmetryflow/semantic/icu.py +33 -0
  19. speechmetryflow/semantic/idea_density.py +78 -0
  20. speechmetryflow/speech_production/__init__.py +62 -0
  21. speechmetryflow/speech_production/assets/__init__.py +58 -0
  22. speechmetryflow/speech_production/assets/words_fr.json +1 -0
  23. speechmetryflow/speech_production/fluency.py +42 -0
  24. speechmetryflow/speech_production/fragment.py +76 -0
  25. speechmetryflow/syntactic/__init__.py +119 -0
  26. speechmetryflow/syntactic/assets.py +40 -0
  27. speechmetryflow/syntactic/dependency.py +47 -0
  28. speechmetryflow/syntactic/sentences.py +189 -0
  29. speechmetryflow/ucsf_disfluency/__init__.py +23 -0
  30. speechmetryflow/utils.py +53 -0
  31. speechmetryflow-0.2.2.dist-info/METADATA +764 -0
  32. speechmetryflow-0.2.2.dist-info/RECORD +35 -0
  33. speechmetryflow-0.2.2.dist-info/WHEEL +4 -0
  34. speechmetryflow-0.2.2.dist-info/entry_points.txt +3 -0
  35. speechmetryflow-0.2.2.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,42 @@
1
+ from speechmetryflow.speech_production.assets import filled_pauses
2
+
3
+
4
+ def count_silent_pauses(text, lang=None, pause_marker=None):
5
+ """
6
+ Counts the total number of occurrences of "<pause_marker>" in the text, according to the specified language.
7
+
8
+ Args:
9
+ text (str): The text to be analysed.
10
+ lang (str): The language of the text ('en' or 'fr').
11
+ pause_marker (str): Overwrite the specific language pause_marker
12
+
13
+ Returns:
14
+ int: The number of occurrences of silent pauses.
15
+ """
16
+ if pause_marker is None:
17
+ pause_markers = {"en": "[break]", "fr": "[pause]"}
18
+ pause_marker = pause_markers.get(lang)
19
+
20
+ if pause_marker is None:
21
+ return
22
+
23
+ return text.count(pause_marker)
24
+
25
+
26
+ def count_filled_pauses(text, lang):
27
+ """
28
+ Counts the total number of occurrences of specified words in the text, according to language.
29
+
30
+ Args:
31
+ text (str): The text to be analysed.
32
+ language (str): The language of the text ('en' or 'fr').
33
+
34
+ Returns:
35
+ int: The number of occurrences of filled pauses.
36
+ """
37
+ pauses = filled_pauses.get(lang)
38
+
39
+ if pauses is None:
40
+ return
41
+
42
+ return sum(text.count(pause) for pause in pauses)
@@ -0,0 +1,76 @@
1
+ """"""
2
+
3
+ import importlib_resources
4
+ import json
5
+
6
+ from speechmetryflow.speech_production.assets import target_words
7
+
8
+
9
+ class Fragment:
10
+ def __init__(self, lang, task):
11
+ self.lang = lang
12
+ self.task = task
13
+ self.valid_words = self._load_valid_words()
14
+ self.target_words = target_words.get(lang)
15
+
16
+ def _load_valid_words(self):
17
+ if self.lang == "en":
18
+ from nltk.corpus import words
19
+
20
+ return set(words.words())
21
+
22
+ elif self.lang == "fr":
23
+ resources = importlib_resources.files(__name__) / "assets"
24
+ with (resources / "words_fr.json").open("r", encoding="utf-8") as file:
25
+ return set(json.load(file))
26
+
27
+ else:
28
+ print(
29
+ f'language "{self.lang}" not currently recognized for fragment counting'
30
+ )
31
+ return None
32
+
33
+ def counter(self, tokens, verbose=False):
34
+ """
35
+ Identifies and counts word fragments in a list of tokens
36
+
37
+ Args:
38
+ tokens (list): The list of tokens
39
+ verbose (bool): If true, displays fragments found.
40
+
41
+ Returns:
42
+ int: The number of word fragments
43
+ """
44
+ if self.valid_words is None:
45
+ return
46
+
47
+ # Fragments identification
48
+ fragments = [token for token in tokens if token.lower() not in self.valid_words]
49
+
50
+ if verbose:
51
+ print(fragments)
52
+
53
+ return len(fragments)
54
+
55
+ def context(self, tokens):
56
+ """
57
+ Identifies and counts word fragments in a list of tokens
58
+
59
+ Args:
60
+ tokens (list): The list of tokens
61
+ verbose (bool): If true, displays fragments found.
62
+
63
+ Returns:
64
+ int: The number of word fragments
65
+ """
66
+ if self.task != "cookie":
67
+ return
68
+
69
+ if self.target_words is None:
70
+ return
71
+
72
+ context_counter = 0
73
+ # Browse two consecutives tokens
74
+ for comb in zip(tokens, tokens[1:]):
75
+ context_counter += comb in self.target_words
76
+ return context_counter
@@ -0,0 +1,119 @@
1
+ from typing import Dict, Union
2
+ import spacy
3
+
4
+
5
+ from speechmetryflow.syntactic.dependency import Dependency
6
+ from speechmetryflow.syntactic.sentences import (
7
+ Sentences,
8
+ nominal_sentences_stats,
9
+ clauses_per_sentence,
10
+ )
11
+
12
+
13
+ def count_verb_tenses(doc: spacy.tokens.Doc, lang: str) -> Dict[str, float | None]:
14
+ """
15
+ Count the number and proportion of verbs in different tenses within a document.
16
+
17
+ This function analyzes verb tenses for English texts based on part-of-speech
18
+ tags provided by SpaCy. For non-English languages, it returns `None` values
19
+ for all tense-related fields.
20
+
21
+ The following English verb tags are used:
22
+ - Present tense: VBP, VBZ, VBG
23
+ - Past tense: VBD, VBN
24
+
25
+ Parameters
26
+ ----------
27
+ doc : spacy.tokens.Doc
28
+ A SpaCy Doc object representing the processed text.
29
+ lang : str
30
+ The ISO language code of the document (e.g., 'en' for English).
31
+
32
+ Returns
33
+ -------
34
+ dict
35
+ A dictionary containing the following keys:
36
+ - "n_present_verb": int or None
37
+ Number of verbs in the present tense.
38
+ - "prop_present_verb": float or None
39
+ Proportion of present-tense verbs relative to document length.
40
+ - "n_past_verb": int or None
41
+ Number of verbs in the past tense.
42
+ - "prop_past_verb": float or None
43
+ Proportion of past-tense verbs relative to document length.
44
+ """
45
+ if lang != "en":
46
+ empty_results = {}
47
+ for tense in ["present", "past"]:
48
+ empty_results[f"n_{tense}_verb"] = None
49
+ empty_results[f"prop_{tense}_verb"] = None
50
+ return empty_results
51
+
52
+ verbs = [token for token in doc if token.pos_ == "VERB"]
53
+
54
+ tenses = {
55
+ "present": 0,
56
+ "past": 0,
57
+ }
58
+
59
+ for verb in verbs:
60
+ if verb.tag_ in [
61
+ "VBP",
62
+ "VBZ",
63
+ "VBG",
64
+ ]: # Typical tags for the present tense in English
65
+ tenses["present"] += 1
66
+ elif verb.tag_ in ["VBD", "VBN"]: # Typical tags for the past tense in English
67
+ tenses["past"] += 1
68
+ # Note: the future tense in English is often marked by auxiliary verbs and does not have a specific tag.
69
+
70
+ results = {}
71
+ for tense, count in tenses.items():
72
+ results[f"n_{tense}_verb"] = count
73
+ results[f"prop_{tense}_verb"] = count / len(doc)
74
+
75
+ return results
76
+
77
+
78
+ def nouns_with_determiners_proportion(doc: spacy.tokens.Doc) -> Union[float, None]:
79
+ """
80
+ Compute the proportion of nouns in a document that have determiners.
81
+
82
+ The function identifies all tokens with the part-of-speech tag "NOUN"
83
+ and checks how many of them have at least one dependent token with
84
+ the dependency label "det" (determiner). It then returns the ratio
85
+ of such nouns to the total number of nouns in the document.
86
+
87
+ Parameters
88
+ ----------
89
+ doc : spacy.tokens.Doc
90
+ A SpaCy Doc object representing the processed text.
91
+
92
+ Returns
93
+ -------
94
+ float or None
95
+ The proportion of nouns that have determiners.
96
+ Returns None if the document contains no nouns.
97
+ """
98
+ nouns = [token for token in doc if token.pos_ == "NOUN"]
99
+ n_nouns = len(nouns)
100
+
101
+ determiners = 0
102
+ for noun in nouns:
103
+ if any(child.dep_ == "det" for child in noun.children):
104
+ determiners += 1
105
+
106
+ return determiners / n_nouns if n_nouns else None
107
+
108
+
109
+ def metrics(doc, lang):
110
+ dependency = Dependency(doc)
111
+ metrics = dependency.metrics
112
+ metrics.update(Sentences(doc, lang).metrics)
113
+ metrics.update(nominal_sentences_stats(doc))
114
+ metrics.update(count_verb_tenses(doc, lang))
115
+ metrics["clauses_per_sentence"] = clauses_per_sentence(doc)
116
+ metrics["nouns_with_determiners_proportion"] = nouns_with_determiners_proportion(
117
+ doc
118
+ )
119
+ return metrics
@@ -0,0 +1,40 @@
1
+ # Database Caractéristiques syntaxiques
2
+
3
+ ## Traductions des étiquettes de dépendance en français
4
+ dep_mapping = {
5
+ "nsubj": "Sujet_nominal",
6
+ "csubj": "Sujets_Clausaux",
7
+ "ROOT": "Racine",
8
+ "det": "Determinant",
9
+ "amod": "Modificateur_adjectival",
10
+ "compound": "Compose",
11
+ "dobj": "Objet_direct",
12
+ "prep": "Preposition",
13
+ "pobj": "Objet_de_preposition",
14
+ "punct": "Ponctuation",
15
+ "expl": "Element_expletif",
16
+ "attr": "Attribut",
17
+ "acl": "Clause_adjectivale",
18
+ "advcl": "Clause_adverbiale",
19
+ "aux": "Auxiliaire",
20
+ "xcomp": "Complement_ouvert",
21
+ "ccomp": "Complements_Clausaux_Non_Controles",
22
+ "conj": "Conjonction",
23
+ "cc": "Conjonction_de_coordination",
24
+ "neg": "Negation",
25
+ "dative": "Datif",
26
+ }
27
+
28
+ translation_dict = {
29
+ "csubj": "Sujets_Clausaux",
30
+ "xcomp": "Complements_Clausaux_Controles",
31
+ "ccomp": "Complements_Clausaux_Non_Controles",
32
+ "advcl": "Modificateurs_Clauses_Adverbiaux",
33
+ "acl": "Modificateurs_Clauses_Adnominaux",
34
+ }
35
+
36
+ ## Dictionnaire des conjonctions de coordination par langue
37
+ coordinating_conjunctions = {
38
+ "en": {"and", "but", "for", "nor", "or", "yet", "so"},
39
+ "fr": {"et", "mais", "ou", "donc", "or", "ni", "car"},
40
+ }
@@ -0,0 +1,47 @@
1
+ import numpy as np
2
+ from collections import Counter
3
+
4
+ from speechmetryflow.syntactic.assets import dep_mapping
5
+
6
+
7
+ class Dependency:
8
+ def __init__(self, doc):
9
+ self.doc = doc
10
+ self.dependencies = [token.dep_ for token in doc]
11
+ self.counts = self._counts()
12
+ self.len_doc = len(doc)
13
+ self.distances = [abs(token.i - token.head.i) for token in self.doc]
14
+ self.lefts = [token.n_lefts for token in self.doc]
15
+ self.rights = [token.n_rights for token in self.doc]
16
+ self.metrics = {}
17
+ self._compute_metrics()
18
+
19
+ def _counts(self):
20
+ counts = Counter(dep_mapping.keys())
21
+ counts.subtract(dep_mapping.keys())
22
+ counts.update(self.dependencies)
23
+ return counts
24
+
25
+ def _compute_metrics(self):
26
+ self.counter()
27
+ self.proportions()
28
+
29
+ # average and maximum lengths of dependencies
30
+ self.metrics["dep_mean_distance"] = np.mean(self.distances)
31
+ self.metrics["dep_max_distance"] = max(self.distances)
32
+
33
+ # number of left-dependencies and right-dependencies for each word.
34
+ self.metrics["dep_total_n_lefts"] = sum(self.lefts)
35
+ self.metrics["dep_total_n_rights"] = sum(self.rights)
36
+ self.metrics["dep_mean_n_lefts"] = np.mean(self.lefts)
37
+ self.metrics["dep_mean_n_rights"] = np.mean(self.rights)
38
+
39
+ def counter(self):
40
+ for tag, count in self.counts.items():
41
+ self.metrics[f"dep_count_{tag}"] = count
42
+
43
+ def proportions(self):
44
+ for tag, count in self.counts.items():
45
+ self.metrics[f"dep_prop_{tag}"] = (
46
+ count / self.len_doc if self.len_doc > 0 else None
47
+ )
@@ -0,0 +1,189 @@
1
+ from typing import Iterable, Dict, Any, Union
2
+ import spacy
3
+
4
+ from speechmetryflow.syntactic.assets import coordinating_conjunctions
5
+
6
+
7
+ class Sentences:
8
+ def __init__(self, doc, lang):
9
+ self.doc = doc
10
+ self.lang = lang
11
+ self.len_doc = len(doc)
12
+ self.metrics = {}
13
+ self._compute_metrics()
14
+
15
+ def _compute_metrics(self):
16
+ # average sentence length in the text
17
+ self.metrics["mean_length_sentence"] = self.len_doc / len(list(self.doc.sents))
18
+
19
+ # Count type of sentences
20
+ incomplete = 0
21
+ prepositional = 0
22
+ verbal = 0
23
+ conjunctions = 0
24
+ for sentence in self.doc.sents:
25
+ incomplete += is_incomplete(sentence)
26
+ prepositional += is_prepositional(sentence)
27
+ verbal += is_verbal(sentence)
28
+ conjunctions += is_conjunctions(sentence, self.lang)
29
+ self.metrics["n_incomplete_sentences"] = incomplete
30
+ self.metrics["n_prepositional_sentences"] = prepositional
31
+ self.metrics["n_verbal_sentences"] = verbal
32
+
33
+ if self.lang in coordinating_conjunctions.keys():
34
+ self.metrics["n_conjunctions_sentences"] = conjunctions
35
+ self.metrics["prop_conjunctions_sentences"] = conjunctions / self.len_doc
36
+ else:
37
+ self.metrics["n_conjunctions_sentences"] = None
38
+ self.metrics["prop_conjunctions_sentences"] = None
39
+
40
+
41
+ def is_incomplete(sentence: Iterable[spacy.tokens.Token]) -> bool:
42
+ """
43
+ Determine whether a sentence is syntactically incomplete.
44
+
45
+ A sentence is considered incomplete if it lacks either a subject
46
+ (a token with dependency label 'nsubj' or 'csubj') or a main verb
47
+ (a token with part-of-speech tag 'VERB').
48
+
49
+ Parameters
50
+ ----------
51
+ sentence : Iterable[spacy.tokens.Token]
52
+ A sequence of SpaCy token objects representing the sentence.
53
+
54
+ Returns
55
+ -------
56
+ bool
57
+ True if the sentence is incomplete (missing subject or verb),
58
+ False otherwise.
59
+ """
60
+ sent_pos = [token.pos_ for token in sentence]
61
+ sent_dep = [token.dep_ for token in sentence]
62
+ has_subject = "nsubj" in sent_dep or "csubj" in sent_dep
63
+ has_verb = "VERB" in sent_pos
64
+ return not (has_verb and has_subject)
65
+
66
+
67
+ def is_prepositional(sentence: Iterable[spacy.tokens.Token]) -> bool:
68
+ """
69
+ Determine whether a sentence is headed by or centered around
70
+ a prepositional structure.
71
+
72
+ The function checks if the sentence contains at least one preposition
73
+ (token with part-of-speech tag 'ADP') that governs an object
74
+ (dependency label 'pobj' or 'dobj'). This helps identify sentences
75
+ whose main syntactic relation is prepositional.
76
+
77
+ Parameters
78
+ ----------
79
+ sentence : Iterable[spacy.tokens.Token]
80
+ A sequence of spacy token objects representing the sentence.
81
+
82
+ Returns
83
+ -------
84
+ bool
85
+ True if the sentence contains a prepositional structure
86
+ (a preposition with an object), False otherwise.
87
+ """
88
+ for token in sentence:
89
+ if token.pos_ == "ADP": # 'ADP' is the POS tag for prepositions in spacy
90
+ # Check if the preposition has an object as a dependent
91
+ return any(child.dep_ in ["pobj", "dobj"] for child in token.children)
92
+ return False
93
+
94
+
95
+ def is_verbal(sentence: Iterable[spacy.tokens.Token]) -> bool:
96
+ """
97
+ Determine whether a sentence contains a verb.
98
+
99
+ The function checks if any token in the sentence has a part-of-speech
100
+ tag of 'VERB', indicating the presence of a verbal predicate or
101
+ verbal structure.
102
+
103
+ Parameters
104
+ ----------
105
+ sentence : Iterable[spacy.tokens.Token]
106
+ A sequence of SpaCy token objects representing the sentence.
107
+
108
+ Returns
109
+ -------
110
+ bool
111
+ True if the sentence contains at least one verb, False otherwise.
112
+ """
113
+ return any(token.pos_ == "VERB" for token in sentence)
114
+
115
+
116
+ def is_conjunctions(sentence: Iterable[spacy.tokens.Token], lang: str) -> bool:
117
+ """
118
+ Determine whether a sentence contains any coordinating conjunctions.
119
+
120
+ Parameters
121
+ ----------
122
+ sentence : Iterable[spacy.tokens.Token]
123
+ A sequence of SpaCy token objects representing the sentence.
124
+ lang : str
125
+ The ISO language code of the sentence (e.g., 'en' for English, 'fr' for French).
126
+
127
+ Returns
128
+ -------
129
+ bool
130
+ True if the sentence contains at least one coordinating conjunction,
131
+ False otherwise.
132
+ """
133
+ conjunctions = coordinating_conjunctions.get(lang, set())
134
+ return any(token.lower_ in conjunctions for token in sentence)
135
+
136
+
137
+ def nominal_sentences_stats(doc) -> Dict[str, Any]:
138
+ """
139
+ Compute statistics about nominal sentences (noun chunks).
140
+
141
+ Returns
142
+ -------
143
+ - "n_nominal_sentences": int
144
+ Number of nominal sentences (noun chunks)
145
+ - "mean_length_nominal_sentence": float
146
+ Average number of tokens per nominal sentence
147
+ - "prop_nominal_sentences": float
148
+ Proportion of nominal sentences relative to the document length
149
+ """
150
+ nominal_sentences = [chunk for chunk in doc.noun_chunks]
151
+ n_nominal_sentences = len(nominal_sentences)
152
+ length_nominal_sentences = sum(len(chunk) for chunk in nominal_sentences)
153
+ mean_length_nominal_sentence = (
154
+ length_nominal_sentences / n_nominal_sentences if n_nominal_sentences else None
155
+ )
156
+ return {
157
+ "n_nominal_sentences": n_nominal_sentences,
158
+ "mean_length_nominal_sentence": mean_length_nominal_sentence,
159
+ "prop_nominal_sentences": n_nominal_sentences / len(doc),
160
+ }
161
+
162
+
163
+ def clauses_per_sentence(doc: spacy.tokens.Doc) -> Union[float, None]:
164
+ """
165
+ Compute the average number of clauses per sentence in a SpaCy document.
166
+
167
+ A clause is identified by the presence of specific dependency relations:
168
+ - 'csubj' (clausal subject)
169
+ - 'ccomp' (clausal complement)
170
+ - 'xcomp' (open clausal complement)
171
+
172
+ The function counts all tokens with these dependencies and divides by the
173
+ total number of sentences in the document.
174
+
175
+ Parameters
176
+ ----------
177
+ doc : spacy.tokens.Doc
178
+ A SpaCy Doc object representing the processed text.
179
+
180
+ Returns
181
+ -------
182
+ float or None
183
+ The mean number of clauses per sentence.
184
+ Returns None if the document contains no sentences.
185
+ """
186
+ # Count tokens whose dependency label indicates a clause
187
+ n_clauses = sum(1 for token in doc if token.dep_ in ["csubj", "ccomp", "xcomp"])
188
+ n_sents = len(list(doc.sents))
189
+ return n_clauses / n_sents if n_sents else None
@@ -0,0 +1,23 @@
1
+ """"""
2
+
3
+ from typing import Dict
4
+
5
+ from collections import Counter
6
+
7
+
8
+ def metrics(text: str) -> Dict:
9
+ disfluency_counter = Counter(text)
10
+ return {
11
+ "UCSF_disfluency_single_repetition": disfluency_counter["="],
12
+ "UCSF_disfluency_multiple_repetitions": disfluency_counter["@"],
13
+ "UCSF_disfluency_repeated_phrase": disfluency_counter["&"],
14
+ "UCSF_disfluency_restart_rephrase": disfluency_counter["#"],
15
+ "UCSF_disfluency_partial_word_false_start": disfluency_counter["%"],
16
+ "UCSF_disfluency_spoonerism": disfluency_counter["$"],
17
+ }
18
+
19
+
20
+ def cleaning(text: str) -> str:
21
+ for char in "=@&#%$":
22
+ text = text.replace(char, "")
23
+ return text
@@ -0,0 +1,53 @@
1
+ """"""
2
+
3
+ import re
4
+ from nltk.stem import SnowballStemmer
5
+
6
+
7
+ def general_cleaning(raw_text: str) -> str:
8
+ """
9
+ Cleans up text by performing several cleaning operations
10
+
11
+ Args:
12
+ raw_text (str): The text to be cleaned up
13
+
14
+ Returns:
15
+ str: Cleaned up text
16
+ """
17
+ # Removes content between square brackets ([...])
18
+ text = re.sub(r"\[.*?\]", "", raw_text)
19
+
20
+ # Removes punctuation, keeping letters, apostrophes, spaces, periods, and question marks
21
+ text = re.sub(r"[^\w\’ \.\?]", "", text)
22
+
23
+ # Removes periods following a space
24
+ text = re.sub(r"\s\.", "", text)
25
+
26
+ # Replaces two consecutive spaces with a single space
27
+ text = re.sub(r"\s\s+", " ", text)
28
+
29
+ return text
30
+
31
+
32
+ def compute_stemming(tokens, lang):
33
+ """
34
+ Stemming with Snowball
35
+ https://snowballstem.org/
36
+
37
+ Args:
38
+ tokens list(str): text to compute.
39
+ lang (str): language of the text.
40
+
41
+ Returns:
42
+ list(str): stems from SnowballStemmer.
43
+ """
44
+ if lang == "en":
45
+ stemmer = SnowballStemmer("english")
46
+
47
+ elif lang == "fr":
48
+ stemmer = SnowballStemmer("french")
49
+
50
+ else:
51
+ return
52
+
53
+ return list(map(stemmer.stem, tokens))