Spaces:
Running
Running
##modules/text_analysis/morpho_analysis.py | |
import spacy | |
from collections import Counter | |
from spacy import displacy | |
import re | |
from streamlit.components.v1 import html | |
import base64 | |
from collections import Counter | |
import re | |
from ..utils.widget_utils import generate_unique_key | |
import logging | |
logger = logging.getLogger(__name__) | |
# Define colors for grammatical categories | |
POS_COLORS = { | |
'ADJ': '#FFA07A', # Light Salmon | |
'ADP': '#98FB98', # Pale Green | |
'ADV': '#87CEFA', # Light Sky Blue | |
'AUX': '#DDA0DD', # Plum | |
'CCONJ': '#F0E68C', # Khaki | |
'DET': '#FFB6C1', # Light Pink | |
'INTJ': '#FF6347', # Tomato | |
'NOUN': '#90EE90', # Light Green | |
'NUM': '#FAFAD2', # Light Goldenrod Yellow | |
'PART': '#D3D3D3', # Light Gray | |
'PRON': '#FFA500', # Orange | |
'PROPN': '#20B2AA', # Light Sea Green | |
'SCONJ': '#DEB887', # Burlywood | |
'SYM': '#7B68EE', # Medium Slate Blue | |
'VERB': '#FF69B4', # Hot Pink | |
'X': '#A9A9A9', # Dark Gray | |
} | |
POS_TRANSLATIONS = { | |
'es': { | |
'ADJ': 'Adjetivo', | |
'ADP': 'Preposición', | |
'ADV': 'Adverbio', | |
'AUX': 'Auxiliar', | |
'CCONJ': 'Conjunción Coordinante', | |
'DET': 'Determinante', | |
'INTJ': 'Interjección', | |
'NOUN': 'Sustantivo', | |
'NUM': 'Número', | |
'PART': 'Partícula', | |
'PRON': 'Pronombre', | |
'PROPN': 'Nombre Propio', | |
'SCONJ': 'Conjunción Subordinante', | |
'SYM': 'Símbolo', | |
'VERB': 'Verbo', | |
'X': 'Otro', | |
}, | |
'en': { | |
'ADJ': 'Adjective', | |
'ADP': 'Preposition', | |
'ADV': 'Adverb', | |
'AUX': 'Auxiliary', | |
'CCONJ': 'Coordinating Conjunction', | |
'DET': 'Determiner', | |
'INTJ': 'Interjection', | |
'NOUN': 'Noun', | |
'NUM': 'Number', | |
'PART': 'Particle', | |
'PRON': 'Pronoun', | |
'PROPN': 'Proper Noun', | |
'SCONJ': 'Subordinating Conjunction', | |
'SYM': 'Symbol', | |
'VERB': 'Verb', | |
'X': 'Other', | |
}, | |
'fr': { | |
'ADJ': 'Adjectif', | |
'ADP': 'Préposition', | |
'ADV': 'Adverbe', | |
'AUX': 'Auxiliaire', | |
'CCONJ': 'Conjonction de Coordination', | |
'DET': 'Déterminant', | |
'INTJ': 'Interjection', | |
'NOUN': 'Nom', | |
'NUM': 'Nombre', | |
'PART': 'Particule', | |
'PRON': 'Pronom', | |
'PROPN': 'Nom Propre', | |
'SCONJ': 'Conjonction de Subordination', | |
'SYM': 'Symbole', | |
'VERB': 'Verbe', | |
'X': 'Autre', | |
} | |
} | |
############################################################################################# | |
def get_repeated_words_colors(doc): | |
word_counts = Counter(token.text.lower() for token in doc if token.pos_ != 'PUNCT') | |
repeated_words = {word: count for word, count in word_counts.items() if count > 1} | |
word_colors = {} | |
for token in doc: | |
if token.text.lower() in repeated_words: | |
word_colors[token.text.lower()] = POS_COLORS.get(token.pos_, '#FFFFFF') | |
return word_colors | |
###################################################################################################### | |
def highlight_repeated_words(doc, word_colors): | |
highlighted_text = [] | |
for token in doc: | |
if token.text.lower() in word_colors: | |
color = word_colors[token.text.lower()] | |
highlighted_text.append(f'<span style="background-color: {color};">{token.text}</span>') | |
else: | |
highlighted_text.append(token.text) | |
return ' '.join(highlighted_text) | |
################################################################################################# | |
def generate_arc_diagram(doc): | |
""" | |
Genera diagramas de arco para cada oración en el documento. | |
Args: | |
doc: Documento procesado por spaCy | |
Returns: | |
list: Lista de diagramas en formato HTML | |
""" | |
arc_diagrams = [] | |
for sent in doc.sents: | |
# Configuración básica que sabemos que funciona | |
html = displacy.render(sent, style="dep", options={ | |
"distance": 100, | |
"color": "#ffffff", | |
"bg": "#0d6efd", | |
"font": "Arial" | |
}) | |
# Ajustes básicos que funcionaban en la versión anterior | |
html = html.replace('height="375"', 'height="200"') | |
html = re.sub(r'<svg[^>]*>', lambda m: m.group(0).replace('height="450"', 'height="300"'), html) | |
html = re.sub(r'<g [^>]*transform="translate\((\d+),(\d+)\)"', | |
lambda m: f'<g transform="translate({m.group(1)},50)"', html) | |
arc_diagrams.append(html) | |
return arc_diagrams | |
""" | |
def generate_arc_diagram(doc): | |
sentences = list(doc.sents) | |
arc_diagrams = [] | |
for sent in sentences: | |
html = displacy.render(sent, style="dep", options={"distance": 100}) | |
html = html.replace('height="375"', 'height="200"') | |
html = re.sub(r'<svg[^>]*>', lambda m: m.group(0).replace('height="450"', 'height="300"'), html) | |
html = re.sub(r'<g [^>]*transform="translate\((\d+),(\d+)\)"', lambda m: f'<g transform="translate({m.group(1)},50)"', html) | |
arc_diagrams.append(html) | |
return arc_diagrams | |
def generate_arc_diagram(doc): | |
Genera diagramas de arco para cada oración en el documento. | |
Args: | |
doc: Documento procesado por spaCy | |
Returns: | |
list: Lista de diagramas en formato HTML | |
arc_diagrams = [] | |
for sent in doc.sents: | |
words = [token.text for token in sent] | |
# Calculamos el ancho del SVG basado en la longitud de la oración | |
svg_width = max(600, len(words) * 120) | |
# Altura fija para cada oración | |
svg_height = 350 # Controla la altura del SVG | |
# Renderizamos el diagrama de dependencias | |
html = displacy.render(sent, style="dep", options={ | |
"add_lemma":False, # Introduced in version 2.2.4, this argument prints the lemma’s in a separate row below the token texts. | |
"arrow_spacing": 12, #This argument is used for adjusting the spacing between arrows in px to avoid overlaps. | |
"arrow_width": 2, #This argument is used for adjusting the width of arrow head in px. | |
"arrow_stroke": 2, #This argument is used for adjusting the width of arrow path in px. | |
"collapse_punct": True, #It attaches punctuation to the tokens. | |
"collapse_phrases": False, # This argument merges the noun phrases into one token. | |
"compact":False, # If you will take this argument as true, you will get the “Compact mode” with square arrows that takes up less space. | |
"color": "#ffffff", | |
"bg": "#0d6efd", | |
"compact": False, #Put the value of this argument True, if you want to use fine-grained part-of-speech tags (Token.tag_), instead of coarse-grained tags (Token.pos_). | |
"distance": 100, # Aumentamos la distancia entre palabras | |
"fine_grained": False, #Put the value of this argument True, if you want to use fine-grained part-of-speech tags (Token.tag_), instead of coarse-grained tags (Token.pos_). | |
"offset_x": 55, # This argument is used for spacing on left side of the SVG in px. | |
"word_spacing": 25, #This argument is used for adjusting the vertical spacing between words and arcs in px. | |
}) | |
# Ajustamos el tamaño del SVG y el viewBox | |
html = re.sub(r'width="(\d+)"', f'width="{svg_width}"', html) | |
html = re.sub(r'height="(\d+)"', f'height="{svg_height}"', html) | |
html = re.sub(r'<svg', f'<svg viewBox="0 0 {svg_width} {svg_height}"', html) | |
#html = re.sub(r'<svg[^>]*>', lambda m: m.group(0).replace('height="450"', 'height="300"'), html) | |
#html = re.sub(r'<g [^>]*transform="translate\((\d+),(\d+)\)"', lambda m: f'<g transform="translate({m.group(1)},50)"', html) | |
# Movemos todo el contenido hacia abajo | |
#html = html.replace('<g', f'<g transform="translate(50, {svg_height - 200})"') | |
# Movemos todo el contenido hacia arriba para eliminar el espacio vacío en la parte superior | |
html = re.sub(r'<g transform="translate\((\d+),(\d+)\)"', | |
lambda m: f'<g transform="translate({m.group(1)},10)"', html) | |
# Ajustamos la posición de las etiquetas de las palabras | |
html = html.replace('dy="1em"', 'dy="-1em"') | |
# Ajustamos la posición de las etiquetas POS | |
html = html.replace('dy="0.25em"', 'dy="-3em"') | |
# Aumentamos el tamaño de la fuente para las etiquetas POS | |
html = html.replace('.displacy-tag {', '.displacy-tag { font-size: 14px;') | |
# Rotamos las etiquetas de las palabras para mejorar la legibilidad | |
#html = html.replace('class="displacy-label"', 'class="displacy-label" transform="rotate(30)"') | |
arc_diagrams.append(html) | |
return arc_diagrams | |
""" | |
################################################################################################# | |
def get_detailed_pos_analysis(doc): | |
""" | |
Realiza un análisis detallado de las categorías gramaticales (POS) en el texto. | |
""" | |
pos_counts = Counter(token.pos_ for token in doc) | |
total_tokens = len(doc) | |
pos_analysis = [] | |
for pos, count in pos_counts.items(): | |
percentage = (count / total_tokens) * 100 | |
pos_analysis.append({ | |
'pos': pos, | |
'count': count, | |
'percentage': round(percentage, 2), | |
'examples': [token.text for token in doc if token.pos_ == pos][:5] # Primeros 5 ejemplos | |
}) | |
return sorted(pos_analysis, key=lambda x: x['count'], reverse=True) | |
################################################################################################# | |
def get_morphological_analysis(doc): | |
""" | |
Realiza un análisis morfológico detallado de las palabras en el texto. | |
""" | |
morphology_analysis = [] | |
for token in doc: | |
if token.pos_ in ['NOUN', 'VERB', 'ADJ', 'ADV']: # Enfocarse en categorías principales | |
morphology_analysis.append({ | |
'text': token.text, | |
'lemma': token.lemma_, | |
'pos': token.pos_, | |
'tag': token.tag_, | |
'dep': token.dep_, | |
'shape': token.shape_, | |
'is_alpha': token.is_alpha, | |
'is_stop': token.is_stop, | |
'morph': str(token.morph) | |
}) | |
return morphology_analysis | |
################################################################################################# | |
def get_sentence_structure_analysis(doc): | |
""" | |
Analiza la estructura de las oraciones en el texto. | |
""" | |
sentence_analysis = [] | |
for sent in doc.sents: | |
sentence_analysis.append({ | |
'text': sent.text, | |
'root': sent.root.text, | |
'root_pos': sent.root.pos_, | |
'num_tokens': len(sent), | |
'num_words': len([token for token in sent if token.is_alpha]), | |
'subjects': [token.text for token in sent if "subj" in token.dep_], | |
'objects': [token.text for token in sent if "obj" in token.dep_], | |
'verbs': [token.text for token in sent if token.pos_ == "VERB"] | |
}) | |
return sentence_analysis | |
################################################################################################# | |
def perform_advanced_morphosyntactic_analysis(text, nlp): | |
""" | |
Realiza un análisis morfosintáctico avanzado del texto. | |
""" | |
try: | |
doc = nlp(text) | |
return { | |
'doc': doc, | |
'pos_analysis': get_detailed_pos_analysis(doc), | |
'morphological_analysis': get_morphological_analysis(doc), | |
'sentence_structure': get_sentence_structure_analysis(doc), | |
'arc_diagrams': generate_arc_diagram(doc), # Quitamos nlp.lang | |
'repeated_words': get_repeated_words_colors(doc), | |
'highlighted_text': highlight_repeated_words(doc, get_repeated_words_colors(doc)) | |
} | |
except Exception as e: | |
logger.error(f"Error en análisis morfosintáctico: {str(e)}") | |
return None | |
# Al final del archivo morph_analysis.py | |
__all__ = ['get_repeated_words_colors', 'highlight_repeated_words', 'generate_arc_diagram', 'perform_advanced_morphosyntactic_analysis', 'POS_COLORS', 'POS_TRANSLATIONS'] | |