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.
- speechmetryflow/__init__.py +25 -0
- speechmetryflow/_version.py +34 -0
- speechmetryflow/cli.py +131 -0
- speechmetryflow/lexical/__init__.py +17 -0
- speechmetryflow/lexical/assets/__init__.py +103 -0
- speechmetryflow/lexical/assets/concreteness.tsv +39955 -0
- speechmetryflow/lexical/assets/familiarity_imageability.tsv +5554 -0
- speechmetryflow/lexical/assets/frequency.tsv +74289 -0
- speechmetryflow/lexical/assets/valence.tsv +13916 -0
- speechmetryflow/lexical/database.py +143 -0
- speechmetryflow/lexical/part_of_speech.py +172 -0
- speechmetryflow/lexical/utils.py +119 -0
- speechmetryflow/pragmatic/__init__.py +60 -0
- speechmetryflow/pragmatic/assets.py +62 -0
- speechmetryflow/pragmatic/database.py +113 -0
- speechmetryflow/semantic/__init__.py +23 -0
- speechmetryflow/semantic/assets.py +285 -0
- speechmetryflow/semantic/icu.py +33 -0
- speechmetryflow/semantic/idea_density.py +78 -0
- speechmetryflow/speech_production/__init__.py +62 -0
- speechmetryflow/speech_production/assets/__init__.py +58 -0
- speechmetryflow/speech_production/assets/words_fr.json +1 -0
- speechmetryflow/speech_production/fluency.py +42 -0
- speechmetryflow/speech_production/fragment.py +76 -0
- speechmetryflow/syntactic/__init__.py +119 -0
- speechmetryflow/syntactic/assets.py +40 -0
- speechmetryflow/syntactic/dependency.py +47 -0
- speechmetryflow/syntactic/sentences.py +189 -0
- speechmetryflow/ucsf_disfluency/__init__.py +23 -0
- speechmetryflow/utils.py +53 -0
- speechmetryflow-0.2.2.dist-info/METADATA +764 -0
- speechmetryflow-0.2.2.dist-info/RECORD +35 -0
- speechmetryflow-0.2.2.dist-info/WHEEL +4 -0
- speechmetryflow-0.2.2.dist-info/entry_points.txt +3 -0
- 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
|
speechmetryflow/utils.py
ADDED
|
@@ -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))
|