Spaces:
Runtime error
Runtime error
| # ========================================================== | |
| # METHODOLOGY EXTRACTION MODULE (FINAL SV APPROACH) | |
| # FILE: models/methodology_extraction.py | |
| # | |
| # Purpose: | |
| # - Extract methodology steps for poster flowchart | |
| # - Hybrid robust approach: | |
| # (A) Bullet/numbered detection (high priority) | |
| # (B) Regex keyword-based action sentence detection | |
| # (C) Optional spaCy action sentence filtering | |
| # (D) Optional Flan-T5-base refinement (reformatting) | |
| # (E) Fallback safe steps if everything fails | |
| # | |
| # Output: | |
| # [ | |
| # "Step 1 ...", | |
| # "Step 2 ...", | |
| # ... | |
| # ] | |
| # | |
| # Notes (SV-style design): | |
| # - We DO NOT try to generate a perfect flowchart directly. | |
| # - We ensure system always returns usable steps. | |
| # - We keep steps short, actionable, poster-friendly. | |
| # ========================================================== | |
| import re | |
| # ========================================================== | |
| # NORMALIZE TEXT | |
| # ========================================================== | |
| def normalize_text(text: str) -> str: | |
| if not text: | |
| return "" | |
| text = text.replace("\r", "\n") | |
| text = re.sub(r"\n{3,}", "\n\n", text) | |
| text = re.sub(r"[ \t]{2,}", " ", text) | |
| return text.strip() | |
| # ========================================================== | |
| # REMOVE IEEE / PDF NOISE | |
| # ========================================================== | |
| def remove_ieee_noise(text: str) -> str: | |
| if not text: | |
| return "" | |
| noise_patterns = [ | |
| r"authorized licensed use.*", | |
| r"downloaded on.*", | |
| r"ieee xplore.*", | |
| r"copyright.*", | |
| r"doi:\s*\S+", | |
| r"\(cid:\d+\)", | |
| r"this article has been accepted.*", | |
| r"personal use is permitted.*", | |
| r"vol\.\s*\d+.*", | |
| r"pp\.\s*\d+.*", | |
| ] | |
| for pat in noise_patterns: | |
| text = re.sub(pat, "", text, flags=re.IGNORECASE) | |
| text = re.sub(r"\s{2,}", " ", text) | |
| return text.strip() | |
| # ========================================================== | |
| # CLEAN STEP TEXT | |
| # ========================================================== | |
| def clean_step_text(step: str) -> str: | |
| if not step: | |
| return "" | |
| step = step.strip() | |
| # remove bullets | |
| step = step.replace("•", "") | |
| step = step.replace("◦", "") | |
| step = step.replace("▪", "") | |
| step = step.replace("–", "-") | |
| step = step.replace("—", "-") | |
| # remove numbering prefix | |
| step = re.sub(r"^\(?[0-9]{1,2}\)?[\.\)]\s*", "", step) | |
| step = re.sub(r"^[A-Za-z]\)\s*", "", step) | |
| step = re.sub(r"^[-]\s*", "", step) | |
| # remove "Step X:" prefix | |
| step = re.sub(r"^step\s*\d+\s*[:\-]\s*", "", step, flags=re.IGNORECASE) | |
| # remove extra spaces | |
| step = re.sub(r"\s{2,}", " ", step).strip() | |
| # fix broken hyphen words | |
| step = step.replace("pre- processing", "preprocessing") | |
| step = step.replace("pre - processing", "preprocessing") | |
| # cut too long step | |
| if len(step) > 260: | |
| step = step[:260].rsplit(" ", 1)[0] + "..." | |
| return step.strip() | |
| # ========================================================== | |
| # VALIDATE STEP QUALITY | |
| # ========================================================== | |
| def is_valid_step(step: str) -> bool: | |
| if not step: | |
| return False | |
| step = step.strip() | |
| if len(step) < 12: | |
| return False | |
| # reject pure symbols/numbers | |
| if re.fullmatch(r"[0-9\.\-\s]+", step): | |
| return False | |
| low = step.lower() | |
| # reject typical noise | |
| bad_phrases = [ | |
| "authorized licensed use", | |
| "downloaded on", | |
| "ieee xplore", | |
| "all rights reserved", | |
| "doi:", | |
| "references", | |
| "bibliography", | |
| ] | |
| if any(bp in low for bp in bad_phrases): | |
| return False | |
| # reject figure/table headers | |
| if re.match(r"^(table|fig|figure)\s+[0-9ivx]+", low): | |
| return False | |
| # reject if too many words (paragraph-like) | |
| if len(step.split()) > 40: | |
| return False | |
| return True | |
| # ========================================================== | |
| # EXTRACT BULLET / NUMBERED STEPS (HIGH PRIORITY) | |
| # ========================================================== | |
| def extract_numbered_or_bullet_steps(text: str, max_steps=10): | |
| """ | |
| Detect common patterns: | |
| - 1. ... | |
| - 1) ... | |
| - (1) ... | |
| - Step 1: ... | |
| - • ... | |
| - - ... | |
| """ | |
| if not text: | |
| return [] | |
| text = normalize_text(text) | |
| text = remove_ieee_noise(text) | |
| lines = [l.strip() for l in text.split("\n") if l.strip()] | |
| bullet_patterns = [ | |
| r"^\d+\.\s+", | |
| r"^\d+\)\s+", | |
| r"^\(\d+\)\s+", | |
| r"^[A-Za-z]\)\s+", | |
| r"^[-•▪◦]\s+", | |
| r"^step\s*\d+\s*[:\-]\s+", | |
| ] | |
| bullet_regex = re.compile("|".join(bullet_patterns), re.IGNORECASE) | |
| steps = [] | |
| for ln in lines: | |
| if bullet_regex.search(ln): | |
| step = clean_step_text(ln) | |
| if is_valid_step(step): | |
| steps.append(step) | |
| if len(steps) >= max_steps: | |
| break | |
| return steps[:max_steps] | |
| # ========================================================== | |
| # SENTENCE SPLITTER (ROBUST) | |
| # ========================================================== | |
| def split_into_sentences(text: str): | |
| if not text: | |
| return [] | |
| text = normalize_text(text) | |
| # split by period + newline + semicolon | |
| sentences = re.split(r"(?<=[\.\!\?])\s+|\n+", text) | |
| cleaned = [] | |
| for s in sentences: | |
| s = s.strip() | |
| if len(s) >= 10: | |
| cleaned.append(s) | |
| return cleaned | |
| # ========================================================== | |
| # RULE-BASED KEYWORD METHOD STEPS (SV SAFE RULES) | |
| # ========================================================== | |
| def extract_keyword_based_steps(text: str, max_steps=10): | |
| """ | |
| Extract methodology-like sentences based on strong action keywords. | |
| This works when paper has no explicit bullets. | |
| """ | |
| if not text: | |
| return [] | |
| text = normalize_text(text) | |
| text = remove_ieee_noise(text) | |
| sentences = split_into_sentences(text) | |
| # Strong methodology action indicators | |
| keywords = [ | |
| "we propose", "we present", "we develop", "we design", "we introduce", | |
| "we implement", "we apply", "we applied", "we use", "we utilized", | |
| "we employ", "we evaluate", "we test", "we trained", "we train", | |
| "we validate", "we compare", "we compute", "we extract", | |
| "dataset", "data set", "preprocess", "pre-processing", | |
| "feature extraction", "segmentation", "classification", | |
| "training", "testing", "evaluation", "hyperparameter", | |
| "cross validation", "k-fold", "architecture", "pipeline", | |
| "framework", "optimizer", "loss function", "learning rate", | |
| "confusion matrix", "accuracy", "precision", "recall", "f1-score", | |
| "rouge", "bertscore" | |
| ] | |
| steps = [] | |
| for s in sentences: | |
| low = s.lower() | |
| if any(k in low for k in keywords): | |
| step = clean_step_text(s) | |
| if is_valid_step(step): | |
| steps.append(step) | |
| if len(steps) >= max_steps: | |
| break | |
| return steps[:max_steps] | |
| # ========================================================== | |
| # OPTIONAL SPACY EXTRACTION (ACTION VERB FILTERING) | |
| # ========================================================== | |
| def extract_steps_spacy(text: str, max_steps=10): | |
| """ | |
| Use spaCy if installed to detect verb-driven action sentences. | |
| """ | |
| if not text: | |
| return [] | |
| try: | |
| import spacy | |
| except Exception: | |
| return [] | |
| try: | |
| nlp = spacy.load("en_core_web_sm") | |
| except Exception: | |
| return [] | |
| text = normalize_text(text) | |
| text = remove_ieee_noise(text) | |
| doc = nlp(text) | |
| action_verbs = { | |
| "use", "apply", "employ", "train", "test", "evaluate", | |
| "extract", "clean", "preprocess", "classify", "detect", | |
| "generate", "summarize", "measure", "compare", "validate", | |
| "propose", "design", "implement" | |
| } | |
| steps = [] | |
| for sent in doc.sents: | |
| s = sent.text.strip() | |
| if len(s) < 15: | |
| continue | |
| has_action = False | |
| for token in sent: | |
| if token.pos_ == "VERB" and token.lemma_.lower() in action_verbs: | |
| has_action = True | |
| break | |
| if has_action: | |
| step = clean_step_text(s) | |
| if is_valid_step(step): | |
| steps.append(step) | |
| if len(steps) >= max_steps: | |
| break | |
| return steps[:max_steps] | |
| # ========================================================== | |
| # OPTIONAL FLAN-T5 REFINEMENT (REFORMAT, NOT GENERATE NEW IDEA) | |
| # ========================================================== | |
| def refine_steps_with_flan_t5(method_text: str, max_steps=8): | |
| """ | |
| Flan-T5 is used ONLY for reformatting the extracted methodology | |
| into short steps for poster flowchart. | |
| """ | |
| if not method_text: | |
| return [] | |
| try: | |
| from transformers import pipeline | |
| except Exception: | |
| return [] | |
| try: | |
| model = pipeline( | |
| "text2text-generation", | |
| model="google/flan-t5-base", | |
| tokenizer="google/flan-t5-base" | |
| ) | |
| except Exception: | |
| return [] | |
| method_text = normalize_text(method_text) | |
| method_text = remove_ieee_noise(method_text) | |
| # truncate for safety | |
| if len(method_text) > 3500: | |
| method_text = method_text[:3500] | |
| prompt = ( | |
| f"Extract the research methodology as {max_steps} short steps for a poster flowchart. " | |
| f"Each step must be a short sentence. " | |
| f"Do not add new information.\n\n" | |
| f"Methodology:\n{method_text}" | |
| ) | |
| try: | |
| out = model( | |
| prompt, | |
| max_new_tokens=220, | |
| do_sample=False | |
| ) | |
| if not out or not isinstance(out, list): | |
| return [] | |
| generated = out[0].get("generated_text", "").strip() | |
| if not generated: | |
| return [] | |
| # split by numbering OR line breaks | |
| parts = re.split(r"\n+|\d+\.\s*", generated) | |
| steps = [] | |
| for p in parts: | |
| p = p.strip() | |
| if not p: | |
| continue | |
| step = clean_step_text(p) | |
| if is_valid_step(step): | |
| steps.append(step) | |
| if len(steps) >= max_steps: | |
| break | |
| return steps[:max_steps] | |
| except Exception: | |
| return [] | |
| # ========================================================== | |
| # FALLBACK SAFE STEPS (ENSURE NEVER EMPTY) | |
| # ========================================================== | |
| def fallback_generic_steps(max_steps=6): | |
| base = [ | |
| "Collect and prepare the dataset from the paper.", | |
| "Perform preprocessing and cleaning of the data.", | |
| "Apply the proposed model or framework for analysis.", | |
| "Train the model using selected hyperparameters.", | |
| "Evaluate performance using standard evaluation metrics.", | |
| "Compare results against baseline or existing methods." | |
| ] | |
| return base[:max_steps] | |
| # ========================================================== | |
| # MAIN FUNCTION (FINAL PIPELINE) | |
| # ========================================================== | |
| def extract_methodology_steps( | |
| methodology_text: str, | |
| max_steps=8, | |
| use_spacy=True, | |
| use_flan_refine=True | |
| ): | |
| """ | |
| Final SV hybrid pipeline: | |
| 1) Extract bullet/numbered steps (most accurate) | |
| 2) Extract keyword-based action sentences | |
| 3) Extract spaCy action sentences (optional) | |
| 4) Flan-T5 refinement (optional) to clean + restructure | |
| 5) Merge + remove duplicates | |
| 6) If still weak -> fallback generic steps | |
| """ | |
| methodology_text = normalize_text(methodology_text) | |
| methodology_text = remove_ieee_noise(methodology_text) | |
| if not methodology_text: | |
| return fallback_generic_steps(max_steps=max_steps) | |
| steps = [] | |
| # ------------------------------------------------------- | |
| # 1) Bullet / numbered detection | |
| # ------------------------------------------------------- | |
| bullet_steps = extract_numbered_or_bullet_steps(methodology_text, max_steps=max_steps) | |
| steps.extend(bullet_steps) | |
| # ------------------------------------------------------- | |
| # 2) Keyword-based extraction | |
| # ------------------------------------------------------- | |
| if len(steps) < max_steps: | |
| keyword_steps = extract_keyword_based_steps(methodology_text, max_steps=max_steps) | |
| steps.extend(keyword_steps) | |
| # ------------------------------------------------------- | |
| # 3) spaCy extraction | |
| # ------------------------------------------------------- | |
| if use_spacy and len(steps) < max_steps: | |
| spacy_steps = extract_steps_spacy(methodology_text, max_steps=max_steps) | |
| steps.extend(spacy_steps) | |
| # ------------------------------------------------------- | |
| # 4) Remove duplicates early | |
| # ------------------------------------------------------- | |
| deduped = [] | |
| seen = set() | |
| for s in steps: | |
| s_clean = clean_step_text(s) | |
| key = re.sub(r"[^a-z0-9 ]", "", s_clean.lower()).strip() | |
| if not key or key in seen: | |
| continue | |
| if is_valid_step(s_clean): | |
| deduped.append(s_clean) | |
| seen.add(key) | |
| if len(deduped) >= max_steps: | |
| break | |
| steps = deduped[:max_steps] | |
| # ------------------------------------------------------- | |
| # 5) Flan refinement (only if steps still weak) | |
| # ------------------------------------------------------- | |
| if use_flan_refine: | |
| flan_steps = refine_steps_with_flan_t5(methodology_text, max_steps=max_steps) | |
| # accept flan only if looks strong | |
| if len(flan_steps) >= 4: | |
| steps = flan_steps | |
| # ------------------------------------------------------- | |
| # 6) Final fallback if too weak | |
| # ------------------------------------------------------- | |
| if len(steps) < 3: | |
| return fallback_generic_steps(max_steps=max_steps) | |
| return steps[:max_steps] | |
| # ========================================================== | |
| # QUICK TEST | |
| # ========================================================== | |
| if __name__ == "__main__": | |
| sample_method = """ | |
| III. METHODOLOGY | |
| We collected datasets from Kaggle. | |
| 1. Data preprocessing and cleaning. | |
| 2. Training Flan-T5 base model. | |
| 3. Evaluation using ROUGE-L and BERTScore. | |
| • Comparison against baseline model. | |
| """ | |
| steps = extract_methodology_steps(sample_method, max_steps=8) | |
| print("Extracted Methodology Steps:") | |
| for i, s in enumerate(steps, 1): | |
| print(f"{i}. {s}") |