Spaces:
Paused
Paused
| import gradio as gr | |
| import re | |
| import tempfile | |
| import os | |
| # Importaciones de Docling (IA de Layout espacial y Visión) | |
| from docling.document_converter import DocumentConverter | |
| from docling.datamodel.document import DocItemLabel | |
| def heal_and_flatten_medical_text(text): | |
| """ | |
| Motor NLP de Sanación Semántica y Aplanamiento. | |
| Diseñado para cualquier libro médico/biológico universitario. | |
| Convierte el texto crudo en vectores puros para ingesta en RAG. | |
| """ | |
| if not text: | |
| return "" | |
| # 1. PURGA DE REFERENCIAS FANTASMAS | |
| # Elimina referencias cruzadas a figuras y tablas que la IA ya borró. | |
| text = re.sub(r'\(\s*[vV]?\.?\s*(?:fig\.|figs\.|tabla|tablas|cuadro|cuadros)\s*\d+[-.]\d+[^\)]*\)', '', text, flags=re.IGNORECASE) | |
| text = re.sub(r'\b(?:véase|ver)\s+(?:figura|tabla|cuadro)\s+\d+[-.]\d+\b', '', text, flags=re.IGNORECASE) | |
| # 2. APLANAMIENTO Y COSTURA DE COLUMNAS (Flattening & De-hyphenation) | |
| # Docling a veces deja guiones al unir columnas; aquí los forzamos a unirse. | |
| text = re.sub(r'(\w+)-\s*\n\s*(\w+)', r'\1\2', text) | |
| # Destruimos los saltos de línea internos: convertimos párrafos-bloque en cadenas lineales continuas. | |
| text = re.sub(r'(?<!\n)\n(?!\n)', ' ', text) | |
| # Purgar espacios múltiples | |
| text = re.sub(r'[ \t]+', ' ', text) | |
| # 3. SANACIÓN CIENTÍFICO-MATEMÁTICA UNIVERSAL | |
| # Potencias (ej. 104 -> 10^4) cuando están junto a palabras de biología/química. | |
| text = re.sub(r'\b10(3|4|5|6|7|8|9|10|11|12|13)\b(?=\s*(?:y|a|células|linfocitos|macrófagos|bacterias|virus|copias|moléculas|mg|ml|µl|µm|mm|pg|ng))', r'10^\1', text, flags=re.IGNORECASE) | |
| text = re.sub(r'\b1\s*[xX]\s*10(\d{1,2})\b', r'1 x 10^\1', text) | |
| # Corrección de Unidades de Medida corrompidas por OCR | |
| text = re.sub(r'\|xm', 'µm', text) | |
| text = re.sub(r'\bpm\b(?=\s)', 'µm', text) | |
| text = re.sub(r'\[iL', 'µL', text) | |
| # 4. GRECISMOS Y NOMENCLATURA INMUNOLÓGICA (Abbas, Harrison, Robbins) | |
| text = re.sub(r'\ba4p7\b', 'α4β7', text) | |
| text = re.sub(r'\baLp2\b', 'αLβ2', text) | |
| text = re.sub(r'\ba\(3\b', 'αβ', text) | |
| text = re.sub(r'\bLT\(3\b', 'LTβ', text) | |
| text = re.sub(r'\bIFN-7\b', 'IFN-γ', text) | |
| text = re.sub(r'\bIL-ip\b', 'IL-1β', text) | |
| text = re.sub(r'\bCD(\d+)\s*\+', r'CD\1+', text) # Ejemplo: CD8 + -> CD8+ | |
| return text.strip() | |
| def process_medical_pdf(pdf_file, progress=gr.Progress()): | |
| """ | |
| Controlador Orquestador. Administra la conversión de Docling, aplica los | |
| filtros topológicos y ensambla el archivo Markdown puro para Gemini/NotebookLM. | |
| """ | |
| if pdf_file is None: | |
| raise gr.Error("Debes subir un archivo PDF.") | |
| try: | |
| # FASE A: Ingesta Visual y Comprensión Geométrica (Docling) | |
| progress(0.1, desc="👁️ Fase 1/3: Iniciando IA de Visión (Cargando modelos locales)...") | |
| converter = DocumentConverter() | |
| progress(0.2, desc="📐 Analizando topología y dobles columnas del PDF (Puede tardar)...") | |
| doc_result = converter.convert(pdf_file.name) | |
| document = doc_result.document | |
| md_blocks = [] | |
| # Iterador de elementos detectados por la IA | |
| elements = list(document.iterate_items()) | |
| total_elements = len(elements) | |
| # FASE B & C: Triaje Topológico y Sanación | |
| progress(0.5, desc="🧹 Fase 2/3: Purgando tablas, imágenes y ruido editorial...") | |
| for idx, (item, level) in enumerate(elements): | |
| if idx % 10 == 0: # Actualización de UI progresiva | |
| progress(0.5 + (0.4 * (idx / total_elements))) | |
| # REGLA DE EXCLUSIÓN RADICAL: Ignorar ruido visual y tabular | |
| if item.label in[ | |
| DocItemLabel.PAGE_HEADER, | |
| DocItemLabel.PAGE_FOOTER, | |
| DocItemLabel.TABLE, | |
| DocItemLabel.PICTURE, | |
| DocItemLabel.FORMULA, | |
| DocItemLabel.CAPTION | |
| ]: | |
| continue | |
| # REGLA DE PRESERVACIÓN Y SANACIÓN (Preparación RAG) | |
| if item.label in[DocItemLabel.SECTION_HEADER, DocItemLabel.TITLE]: | |
| clean_text = heal_and_flatten_medical_text(item.text) | |
| if clean_text: | |
| # Garantiza que el nivel del encabezado sea entre # y ###### | |
| heading_level = min(max(level, 1), 6) | |
| md_blocks.append(f"{'#' * heading_level} {clean_text}") | |
| elif item.label in [DocItemLabel.PARAGRAPH, DocItemLabel.TEXT]: | |
| clean_text = heal_and_flatten_medical_text(item.text) | |
| if clean_text: | |
| md_blocks.append(clean_text) | |
| elif item.label == DocItemLabel.LIST_ITEM: | |
| clean_text = heal_and_flatten_medical_text(item.text) | |
| if clean_text: | |
| md_blocks.append(f"* {clean_text}") | |
| # FASE D: Ensamblaje Estructural para Gemini/NotebookLM | |
| progress(0.95, desc="📦 Fase 3/3: Ensamblando archivo Markdown optimizado...") | |
| # Une todos los bloques con exactamente DOS saltos de línea | |
| # Esto le indica al LLM dónde termina exactamente un chunk semántico. | |
| final_md = "\n\n".join(md_blocks) | |
| # Generar archivo descargable | |
| fd, path = tempfile.mkstemp(suffix=".md", text=True) | |
| with os.fdopen(fd, 'w', encoding='utf-8') as f: | |
| f.write(final_md) | |
| progress(1.0, desc="✅ ¡Proceso completado con éxito!") | |
| return path | |
| except Exception as e: | |
| raise gr.Error(f"Error crítico en el pipeline: {str(e)}") | |
| # ------------------------------------------------------------------------- | |
| # INTERFAZ DE USUARIO (GRADIO 6.0+) | |
| # ------------------------------------------------------------------------- | |
| with gr.Blocks() as app: | |
| gr.Markdown( | |
| """ | |
| # 🧠 Generador RAG: PDF Médico a `.md` Optimizado | |
| **Pipeline ETL de Grado Ingeniería:** Este sistema NO usa APIs externas. Descarga modelos de IA Espacial locales para deducir dobles columnas, **purga el 100% de tablas, imágenes, captions y marcas de agua**, y aplana los párrafos sanando anomalías ópticas. | |
| Genera un archivo Markdown puro, diseñado **exclusivamente para ingesta en Gemini 3.1 Pro, NotebookLM y bases de datos vectoriales**. | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### 1. Subir Libro Académico") | |
| input_file = gr.File(label="Sube tu archivo (.PDF)", file_types=[".pdf"]) | |
| process_btn = gr.Button("🚀 Extraer y Generar Markdown", variant="primary") | |
| gr.Markdown("*Nota: Este proceso ocurre localmente. Puede tomar varios minutos si el PDF tiene cientos de páginas.*") | |
| with gr.Column(): | |
| gr.Markdown("### 2. Descargar Dataset") | |
| output_file = gr.File(label="Archivo Optimizado para IA (.md)", interactive=False) | |
| # Conexión del orquestador | |
| process_btn.click( | |
| fn=process_medical_pdf, | |
| inputs=[input_file], | |
| outputs=[output_file] | |
| ) | |
| if __name__ == "__main__": | |
| app.launch(theme=gr.themes.Soft()) |