KryonexBiologyKnowledgeEngine / src /streamlit_app.py
KnownAsJohn's picture
first
b795e38
Raw
History Blame Contribute Delete
17.2 kB
import streamlit as st
import pandas as pd
import plotly.express as px
import trafilatura
from transformers import pipeline
import textwrap
# Page configuration MUST be the first Streamlit command
st.set_page_config(
page_title="NASA Bioscience Explorer",
page_icon="https://github.com/KNOWASJOHN/SpaceApps/blob/main/kryonix.jpg?raw=true",
layout="wide",
initial_sidebar_state="collapsed" # Hide the sidebar by default
)
# Load the summarizer
def load_summarizer():
return pipeline("summarization", model="facebook/bart-large-cnn", device=-1)
# Summarization function for individual sections
def summarize_section(text, max_length=100, min_length=40):
try:
summarizer = load_summarizer()
# Check if text is too short
if not text or len(text.strip()) < 50:
return "Insufficient text for summary."
# Limit input to 2000 characters for sections
text = text[:4000] if len(text) > 4000 else text
result = summarizer(text, max_length=max_length, min_length=min_length, do_sample=False)
return result[0]['summary_text']
except Exception as e:
return f"Error summarizing section: {str(e)}"
# SECTION EXTRACTION FUNCTION with improved logic
def extract_sections(url):
try:
downloaded = trafilatura.fetch_url(url)
if not downloaded:
return None
full_text = trafilatura.extract(downloaded)
if not full_text:
return None
sections = {}
lines = full_text.split('\n')
current_section = None
for line in lines:
line_clean = line.strip()
if not line_clean:
continue
# Check for section headers (more precise matching)
line_lower = line_clean.lower()
# Reset current section if we find a new section header
if len(line_clean) < 100: # Likely a header if short
if 'introduction' in line_lower and current_section != 'introduction':
current_section = 'introduction'
sections[current_section] = []
continue
elif 'results' in line_lower and current_section != 'results':
current_section = 'results'
sections[current_section] = []
continue
elif 'conclusion' in line_lower and current_section != 'conclusion':
current_section = 'conclusion'
sections[current_section] = []
continue
elif ('methods' in line_lower or 'methodology' in line_lower) and current_section != 'methods':
current_section = 'methods'
sections[current_section] = []
continue
elif 'discussion' in line_lower and current_section != 'discussion':
current_section = 'discussion'
sections[current_section] = []
continue
elif 'abstract' in line_lower and current_section != 'abstract':
current_section = 'abstract'
sections[current_section] = []
continue
# Add content to current section if we're in one
if current_section and current_section in sections:
sections[current_section].append(line_clean)
# Convert lists to strings and limit each section
processed_sections = {}
for section, content in sections.items():
if content:
section_text = ' '.join(content)
# Limit to 2000 characters per section
section_text = section_text[:2000]
if len(section_text) > 50: # Only include if meaningful content
processed_sections[section] = section_text
return processed_sections if processed_sections else None
except Exception as e:
st.error(f"Error extracting sections: {str(e)}")
return None
# FALLBACK FUNCTION: Use full text if section extraction fails
def extract_full_text(url):
try:
downloaded = trafilatura.fetch_url(url)
text = trafilatura.extract(downloaded) if downloaded else None
return text[:4000] if text else None
except Exception as e:
return None
# UPDATED Function to summarize paper from URL with separate section summaries
def summarize_paper(url):
try:
# Extract specific sections
sections = extract_sections(url)
if sections:
# Summarize each section individually
section_summaries = {}
for section_name, section_text in sections.items():
if section_text and len(section_text) > 100:
section_summary = summarize_section(section_text)
section_summaries[section_name] = section_summary
return section_summaries if section_summaries else None
else:
# Fallback to full text extraction
st.warning("⚠️ Could not extract specific sections, using full text instead.")
full_text = extract_full_text(url)
if full_text and len(full_text) > 100:
# For full text, create a single summary but label it as "Overall Summary"
overall_summary = summarize_section(full_text, max_length=150, min_length=60)
return {"Overall Summary": overall_summary}
else:
return None
except Exception as e:
st.error(f"Error summarizing paper: {str(e)}")
return None
# UPDATED Simple summarizer function with separate sections
def summarize_from_url(url):
try:
# Extract sections and summarize each
sections = extract_sections(url)
if sections:
section_summaries = {}
for section_name, section_text in sections.items():
if section_text and len(section_text) > 100:
section_summary = summarize_section(section_text)
section_summaries[section_name] = section_summary
return section_summaries if section_summaries else None
else:
# Fallback to full text
full_text = extract_full_text(url)
if full_text and len(full_text) > 100:
overall_summary = summarize_section(full_text, max_length=150, min_length=60)
return {"Overall Summary": overall_summary}
else:
return "❌ Failed to extract meaningful text from the URL."
except Exception as e:
return f"❌ Error: {str(e)}"
# Load data
def load_data():
try:
df = pd.read_csv('./data/SB_publication_PMC.csv')
if df.empty:
return pd.DataFrame()
except Exception as e:
st.error(f"Error loading data: {str(e)}")
return pd.DataFrame()
# Simple categorization based on title keywords
def categorize_topic(title):
title_lower = title.lower()
if any(word in title_lower for word in ['bone', 'skeletal', 'oste']):
return 'Bone Health'
elif any(word in title_lower for word in ['muscle', 'atrophy']):
return 'Muscle Physiology'
elif any(word in title_lower for word in ['immune', 'infection', 'microbiome']):
return 'Immune System'
elif any(word in title_lower for word in ['plant', 'arabidopsis', 'root']):
return 'Plant Biology'
elif any(word in title_lower for word in ['radiation', 'dna', 'genomic']):
return 'Radiation Effects'
elif any(word in title_lower for word in ['microgravity', 'gravity']):
return 'Microgravity Adaptation'
else:
return 'Other'
def detect_organism(title):
title_lower = title.lower()
if 'mouse' in title_lower or 'mice' in title_lower:
return 'Mouse'
elif 'arabidopsis' in title_lower:
return 'Arabidopsis'
elif 'drosophila' in title_lower:
return 'Drosophila'
elif 'human' in title_lower or 'astronaut' in title_lower:
return 'Human'
elif 'rat' in title_lower:
return 'Rat'
else:
return 'Various'
df['topic'] = df['Title'].apply(categorize_topic)
df['organism'] = df['Title'].apply(detect_organism)
return df
# Filter publications
def filter_publications(df, search_term, selected_topics, selected_organisms):
filtered_df = df.copy()
# Only apply topic filter if topics are selected
if selected_topics and len(selected_topics) > 0:
filtered_df = filtered_df[filtered_df['topic'].isin(selected_topics)]
# Only apply organism filter if organisms are selected
if selected_organisms and len(selected_organisms) > 0:
filtered_df = filtered_df[filtered_df['organism'].isin(selected_organisms)]
# Only apply search filter if search term is provided
if search_term and search_term.strip():
search_terms = search_term.lower().split()
search_mask = pd.Series(True, index=filtered_df.index)
for term in search_terms:
term_mask = (
filtered_df['Title'].str.lower().str.contains(term, na=False) |
filtered_df['topic'].str.lower().str.contains(term, na=False) |
filtered_df['organism'].str.lower().str.contains(term, na=False)
)
search_mask &= term_mask
filtered_df = filtered_df[search_mask]
return filtered_df
# Function to display section summaries in a nice format
def display_section_summaries(summaries, use_expander=False):
if not summaries:
return
# Display each section summary either in an expander or container
for section_name, summary_text in summaries.items():
if use_expander:
with st.expander(f"πŸ“‹ {section_name.title()} Summary"):
st.info(summary_text)
else:
st.markdown(f"**πŸ“‹ {section_name.title()} Summary**")
st.info(summary_text)
st.markdown("---")
# Main app
def main():
st.title("πŸš€ NASA Bioscience Explorer")
st.markdown("Explore 608 NASA life sciences publications")
# Load data
df = load_data()
if df.empty:
st.error("Failed to load data. Please check if the data file exists.")
return
# Initialize session state for caching summaries
if 'summary_cache' not in st.session_state:
st.session_state.summary_cache = {}
# Create header section with filters
st.markdown("### πŸ” Search and Filter Publications")
# Create three columns for filters
search_col, topic_col, organism_col = st.columns([1, 1, 1])
with search_col:
search_term = st.text_input(
"Search publications:",
placeholder="Enter keywords..."
)
with topic_col:
topic_options = df['topic'].unique().tolist()
selected_topics = st.multiselect(
"Research Topics:",
options=topic_options,
default=[]
)
with organism_col:
organism_options = df['organism'].unique().tolist()
selected_organisms = st.multiselect(
"Organisms:",
options=organism_options,
default=[]
)
st.markdown("---")
# Filter data
filtered_df = filter_publications(df, search_term, selected_topics, selected_organisms)
# Metrics
col1, col2, col3, col4 = st.columns(4)
col1.metric("Total Publications", len(df))
col2.metric("Filtered Publications", len(filtered_df))
col3.metric("Research Topics", df['topic'].nunique())
col4.metric("Organisms Studied", df['organism'].nunique())
# Create tabs
tab1, tab2 = st.tabs(["πŸ“Š Research Dashboard", "πŸ“„ Paper Summarizer"])
with tab1:
if not filtered_df.empty:
col1, col2 = st.columns(2)
with col1:
topic_counts = filtered_df['topic'].value_counts()
topic_labels = [f"{topic} ({count})" for topic, count in topic_counts.items()]
fig1 = px.pie(
values=topic_counts.values,
names=topic_labels,
title="πŸ“ˆ Research Topics Distribution"
)
fig1.update_traces(textinfo='percent+label')
st.plotly_chart(fig1, use_container_width=True)
with col2:
organism_counts = filtered_df['organism'].value_counts().reset_index()
organism_counts.columns = ['Organism', 'Count']
organism_counts['Label'] = organism_counts.apply(lambda x: f"{x['Organism']} ({x['Count']})", axis=1)
fig2 = px.bar(
data_frame=organism_counts,
x='Label',
y='Count',
title="🧬 Publications by Organism"
)
fig2.update_xaxes(tickangle=45)
fig2.update_layout(xaxis_title="")
st.plotly_chart(fig2, use_container_width=True)
st.markdown("---")
st.subheader("πŸ“š Publication Browser")
if not filtered_df.empty:
for idx, row in filtered_df.iterrows():
# Create an expander for each paper
with st.expander(f"πŸ“‘ {row['Title']}", expanded=False):
st.write(f"**Topic:** {row['topic']}")
st.write(f"**Organism:** {row['organism']}")
st.markdown(f"[πŸ“„ Read Paper]({row['Link']})")
summary_key = f"summary_{idx}"
if summary_key not in st.session_state:
st.session_state[summary_key] = None
if st.button("πŸ“ Generate Summary", key=f"btn_{idx}"):
with st.spinner("Generating section summaries..."):
summaries = summarize_paper(row['Link'])
if summaries:
st.session_state[summary_key] = summaries
else:
st.error("❌ Failed to extract text from this paper.")
if st.session_state[summary_key]:
st.write("πŸ“‹ Section Summaries:")
display_section_summaries(st.session_state[summary_key], use_expander=False)
else:
st.warning("πŸ” No publications match the current filters.")
st.markdown("---")
st.subheader("πŸ’‘ Research Insights")
col1, col2 = st.columns(2)
with col1:
st.markdown("### 🎯 Most Studied Areas")
top_topics = df['topic'].value_counts().head(3)
for topic, count in top_topics.items():
st.write(f"- **{topic}**: {count} publications")
with col2:
st.markdown("### πŸ” Research Gaps")
gaps = [
"Limited long-duration human studies",
"Combined radiation + microgravity effects",
"Psychological health in space"
]
for gap in gaps:
st.write(f"- {gap}")
with tab2:
st.markdown("### πŸ“„ Research Paper Summarizer")
st.markdown("Enter any scientific article URL to get AI-generated section summaries")
url_input = st.text_input(
"Enter Article URL:",
value="https://pmc.ncbi.nlm.nih.gov/articles/PMC10772081/",
placeholder="https://pmc.ncbi.nlm.nih.gov/articles/...",
key="url_input"
)
col1, col2 = st.columns([1, 4])
with col1:
if st.button("πŸš€ Summarize Paper", type="primary"):
if url_input:
if url_input not in st.session_state.summary_cache:
with st.spinner("πŸ“ Generating section summaries..."):
summaries = summarize_from_url(url_input)
st.session_state.summary_cache[url_input] = summaries
with col2:
if url_input in st.session_state.summary_cache:
summaries = st.session_state.summary_cache[url_input]
if isinstance(summaries, dict):
display_section_summaries(summaries, use_expander=True) # Use expanders in the Paper Summarizer tab
else:
st.info(summaries)
elif not url_input:
st.warning("⚠️ Please enter a URL to summarize")
if __name__ == "__main__":
main()