-
Notifications
You must be signed in to change notification settings - Fork 2
TMT-LFQ-OpenMS insight integration #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3016b0a
b1dfb4d
c12bc1c
af57d21
7c5a225
9c1df80
01ca126
e7dd66b
262d451
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| """Pathway Analysis Page.""" | ||
|
|
||
| from pathlib import Path | ||
| import pandas as pd | ||
| import polars as pl | ||
| import streamlit as st | ||
| from src.common.common import page_setup | ||
| from src.common.results_helpers import get_abundance_data, get_id_column | ||
| # Import GO Enrichment modules from openms_insight engine | ||
| from openms_insight.analysis.enrichment import calculate_go_enrichment | ||
|
|
||
| params = page_setup() | ||
| st.title("GO Enrichment Analysis") | ||
|
|
||
| st.markdown( | ||
| """ | ||
| Identify overrepresented biological themes (BP, CC, MF) within your differentially expressed protein features using MyGene.info and Fisher's Exact Test. | ||
| """ | ||
| ) | ||
|
|
||
| if "workspace" not in st.session_state: | ||
| st.warning("Please initialize your workspace first.") | ||
| st.stop() | ||
|
|
||
| # --- STEP 1: Upstream Statistics Checkpoint --- | ||
| if ( | ||
| "statistics_df" in st.session_state | ||
| and st.session_state["statistics_df"] is not None | ||
| ): | ||
| final_statistics_report = st.session_state["statistics_df"] | ||
| st.info( | ||
| "🔄 **Upstream Pipeline Detected**: Using analyzed matrices from the **Statistical Inference** step." | ||
| ) | ||
| else: | ||
| st.warning( | ||
| "⚠️ **Missing Prerequisites**: Statistical inference data not detected. Please run hypothesis testing first." | ||
| ) | ||
| st.page_link( | ||
| "content/statistical.py", label="Go to Statistical Inference", icon="🔬" | ||
| ) | ||
| st.stop() | ||
|
|
||
| # --- STEP 2: Preprocessing Mapping Key Configuration --- | ||
| # Identify target identifier columns dynamically | ||
| abundance_result = get_abundance_data(st.session_state["workspace"]) | ||
| id_col = get_id_column(st.session_state["workspace"], abundance_result[0]) if abundance_result else "ProteinName" | ||
| if id_col not in final_statistics_report.columns: | ||
| st.error(f"❌ Structural Error: Column '{id_col}' is missing from the active matrix context.") | ||
| st.stop() | ||
|
|
||
| # --- SECTION 1: Parameter Setup & Dynamic Cutoff Labels --- | ||
| st.subheader("Configure Enrichment Thresholds") | ||
|
|
||
| # Check if target p-value should be adjusted or raw based on previous selections (Fallback safely to 'p-adj') | ||
| target_p_col = "p-adj" if "p-adj" in final_statistics_report.columns else "p-value" | ||
| p_label = ( | ||
| "Adjusted P-value (p-adj) Cutoff" | ||
| if target_p_col == "p-adj" | ||
| else "Raw P-value (p-value) Cutoff" | ||
| ) | ||
|
|
||
| ui_go_col1, ui_go_col2 = st.columns(2) | ||
|
|
||
| with ui_go_col1: | ||
| p_cutoff = st.number_input( | ||
| f"🔬 {p_label}", | ||
| min_value=0.0001, | ||
| max_value=1.0, | ||
| value=0.05, | ||
| step=0.01, | ||
| format="%.4f", | ||
| help="Proteins with significance metrics below this value are mapped to the foreground cohort.", | ||
| ) | ||
|
|
||
| with ui_go_col2: | ||
| fc_cutoff = st.number_input( | ||
| "📈 Absolute Difference Cutoff (|log2FC|)", | ||
| min_value=0.0, | ||
| max_value=10.0, | ||
| value=1.0, | ||
| step=0.1, | ||
| format="%.2f", | ||
| help="Proteins with absolute log2 fold change greater than or equal to this threshold will be selected.", | ||
| ) | ||
|
|
||
| # --- SECTION 2: Execution and Interactive View Charts --- | ||
| st.markdown("<br>", unsafe_allow_html=True) | ||
| if st.button("🚀 Run GO Enrichment Analysis", type="primary", key="run_go_analysis"): | ||
|
|
||
| with st.spinner("Querying MyGene.info API & executing hyper-geometric calculation loops..."): | ||
| # Convert internal pandas DataFrame to openms_insight Polars DataFrame expectation | ||
| stats_pl = pl.from_pandas(final_statistics_report) | ||
|
|
||
| status, output = calculate_go_enrichment( | ||
| final_report=stats_pl, | ||
| id_col=id_col, | ||
| target_p_col=target_p_col, | ||
| p_cutoff=p_cutoff, | ||
| fc_cutoff=fc_cutoff, | ||
| ) | ||
|
|
||
| # Route response structures based on analysis output status code | ||
| if status == "empty_data": | ||
| st.error("❌ No valid statistical rows found containing standard columns to run GO alignment.") | ||
|
|
||
| elif status == "insufficient_proteins": | ||
| st.warning( | ||
| f"⚠️ Not enough significant proteins found to construct target datasets. " | ||
| f"(Criteria: {target_p_col} < {p_cutoff:.4f}, |log2FC| ≥ {fc_cutoff:.2f})." | ||
| ) | ||
| st.info(f"💡 Found significant proteins count: **{output}**. Try relaxing your p-value or log2FC filters.") | ||
|
|
||
| elif status == "success": | ||
| st.success("⭕ GO Enrichment Analysis completed successfully!") | ||
|
|
||
| # Display operational matrix scale | ||
| st.markdown( | ||
| f"📊 **Analysis Profile Scope**: Mapped **{output['fg_count']}** significant foreground profiles out of **{output['bg_count']}** reference background items." | ||
| ) | ||
|
|
||
| # Build multi-tab interface layer for ontology subcategories | ||
| tabs = st.tabs([ | ||
| "🧬 Biological Process (BP)", | ||
| "🔬 Cellular Component (CC)", | ||
| "🧪 Molecular Function (MF)" | ||
| ]) | ||
| categories_data = output["categories"] | ||
|
|
||
| for idx, go_type in enumerate(["BP", "CC", "MF"]): | ||
| with tabs[idx]: | ||
| fig = categories_data[go_type]["fig"] | ||
| df_go = categories_data[go_type]["df"] | ||
|
|
||
| if fig is not None and df_go is not None: | ||
| # Render plotly bar figures generated straight from backend engine | ||
| st.plotly_chart(fig, use_container_width=True) | ||
|
|
||
| st.subheader(f"📊 {go_type} Results Dataframe") | ||
| st.dataframe(df_go, use_container_width=True) | ||
| else: | ||
| st.info(f"No statistically overrepresented terms identified for Category: **{go_type}**") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| """Filtering Page.""" | ||
|
|
||
| from pathlib import Path | ||
| import pandas as pd | ||
| import polars as pl | ||
| import streamlit as st | ||
| from src.common.common import page_setup | ||
| from src.common.results_helpers import get_abundance_data, get_id_column, get_sample_group_map | ||
|
|
||
| # Import filtering functions from openms_insight package | ||
| from openms_insight.analysis.filter import ( | ||
| filter_low_abundance, | ||
| filter_low_repeatability, | ||
| filter_low_variance, | ||
| ) | ||
|
Comment on lines
+11
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '== repo files =='
git ls-files 'content/*.py' 'src/common/common.py' 'pyproject.toml' 'requirements*.txt' 'setup.py' 'setup.cfg' 'environment*.yml' | sed 's#^`#-` #'
printf '\n%s\n' '== relevant imports/usages =='
for f in content/filtering.py content/imputation.py content/normalization.py; do
if [ -f "$f" ]; then
echo "--- $f"
rg -n "openms_insight\.analysis|page_setup\(|`@st`\.fragment|executor\.run_topp|streamlit_plotly_events|plotly" "$f" || true
fi
done
printf '\n%s\n' '== package availability probe =='
python3 - <<'PY'
import importlib.util
mods = [
"openms_insight",
"openms_insight.analysis",
"openms_insight.analysis.filter",
"openms_insight.analysis.imputation",
"openms_insight.analysis.normalization",
]
for m in mods:
spec = importlib.util.find_spec(m)
print(f"{m}: {'FOUND' if spec else 'MISSING'}")
PY
printf '\n%s\n' '== tree around openms_insight if present in repo =='
fd -t f 'openms_insight*' . || trueRepository: OpenMS/quantms-web Length of output: 1421 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '== requirements.txt =='
cat -n requirements.txt
printf '\n%s\n' '== openms_insight references in repo =='
rg -n "openms_insight" .
printf '\n%s\n' '== top of affected files =='
for f in content/filtering.py content/imputation.py content/normalization.py; do
echo "--- $f"
sed -n '1,40p' "$f"
doneRepository: OpenMS/quantms-web Length of output: 10510 🌐 Web query:
💡 Result: OpenMS-Insight is a Python package designed to provide interactive visualization components for mass spectrometry data within Streamlit applications, leveraging Vue.js for the frontend [1][2]. While the package structure includes core visualization classes like Table, LinePlot, Heatmap, and VolcanoPlot, there is no public-facing module explicitly named openms_insight.analysis in the 0.1.13 version or subsequent releases [1][3]. The package's primary functionality is focused on data visualization and component-based interactivity, such as cross-component selection linking, memory-efficient preprocessing, and disk-based caching [1][4]. Developers extending the package add new components by implementing specific abstract methods—such as _preprocess, _prepare_vue_data, and _get_cache_config—within a component class, rather than using a dedicated analysis package [5]. If you are looking for mass spectrometry analysis tools, you should refer to the core pyOpenMS library, which contains extensive modules for tasks such as chromatographic analysis, peak picking, and signal processing [6]. OpenMS-Insight is specifically intended for the visualization layer of a Streamlit-based data science workflow, not as a backend for raw data analysis algorithms [2][4]. Citations:
Pin a dependency or rewrite these imports
📍 Affects 3 files
🤖 Prompt for AI Agents |
||
|
|
||
| STAT_COLUMNS = ["log2FC", "p-value", "p-adj", "stat"] | ||
|
|
||
|
|
||
| def strip_stat_columns(df: pd.DataFrame) -> pd.DataFrame: | ||
| """Keep preprocessing tables intensity-only before statistical analysis.""" | ||
| return df.drop(columns=[c for c in STAT_COLUMNS if c in df.columns], errors="ignore") | ||
|
|
||
| params = page_setup() | ||
| st.title("Data Filtering") | ||
|
|
||
| st.markdown( | ||
| """ | ||
| Filter out low-quality proteins from your dataset based on abundance, repeatability, or variance thresholds. | ||
| """ | ||
| ) | ||
|
|
||
| if "workspace" not in st.session_state: | ||
| st.warning("Please initialize your workspace first.") | ||
| st.stop() | ||
|
|
||
| result = get_abundance_data(st.session_state["workspace"]) | ||
| if result is None: | ||
| st.info( | ||
| "Abundance data not available. Please run the workflow and configure sample groups first." | ||
| ) | ||
| st.page_link( | ||
| "content/results_abundance.py", label="Go to Abundance", icon="📋" | ||
| ) | ||
| st.stop() | ||
|
|
||
| pivot_df, expr_df, group_map = result | ||
| pivot_df = strip_stat_columns(pivot_df) | ||
| id_col = get_id_column(st.session_state["workspace"], pivot_df) | ||
| sample_group_map = get_sample_group_map(st.session_state["workspace"], pivot_df, group_map) | ||
|
|
||
| # 1. Identify actual sample columns dynamically | ||
| sample_cols = [ | ||
| c | ||
| for c in pivot_df.columns | ||
| if c not in [id_col, "PeptideSequence", "log2FC", "p-value", "p-adj"] | ||
| ] | ||
|
|
||
| # --- SECTION 1: Original Data View --- | ||
| st.subheader("Original Abundance Table") | ||
| st.markdown( | ||
| f"Currently displaying **{pivot_df.shape[0]}** proteins and **{len(sample_cols)}** samples before filtering." | ||
| ) | ||
| st.dataframe(pivot_df, use_container_width=True) | ||
|
|
||
| st.markdown("---") | ||
|
|
||
| # --- SECTION 2: Filter Configuration --- | ||
| st.subheader("Configure Filter Engine") | ||
|
|
||
| # Prepare Polars Metadata DataFrame required by openms_insight functions | ||
| metadata_rows = [{"sample_id": s, "group": sample_group_map[s]} for s in sample_cols if s in sample_group_map] | ||
| metadata_pl = pl.DataFrame( | ||
| metadata_rows, schema={"sample_id": pl.String, "group": pl.String} | ||
| ) | ||
|
|
||
| # User selection for filtering strategy | ||
| filter_method = st.selectbox( | ||
| "Select Filtering Method", | ||
| options=["Low Abundance", "Low Repeatability", "Low Variance"], | ||
| index=0, | ||
| help="Choose the statistical criteria to prune unreliable protein entries.", | ||
| ) | ||
|
|
||
| # Render threshold sliders dynamically based on the selected filter method | ||
| if filter_method == "Low Abundance": | ||
| st.markdown( | ||
| "**Low Abundance Filter**: Keeps rows where at least one group's median is above the selected percentile threshold." | ||
| ) | ||
| threshold = st.slider( | ||
| "Threshold Percentile (%)", | ||
| min_value=0.0, | ||
| max_value=100.0, | ||
| value=10.0, | ||
| step=5.0, | ||
| ) | ||
|
|
||
| elif filter_method == "Low Repeatability": | ||
| st.markdown( | ||
| "**Low Repeatability Filter**: Keeps rows where at least one group has a missing value ratio within the allowed maximum." | ||
| ) | ||
| threshold = st.slider( | ||
| "Max Missing Ratio", | ||
| min_value=0.0, | ||
| max_value=100.0, | ||
| value=50.0, | ||
| step=5.0, | ||
| help="Allowed missing value (zero or null) ratio per group.", | ||
| ) | ||
|
|
||
| elif filter_method == "Low Variance": | ||
| st.markdown( | ||
| "**Low Variance Filter**: Keeps rows where at least one group's variance is above the selected percentile threshold." | ||
| ) | ||
| threshold = st.slider( | ||
| "Threshold Percentile (%)", | ||
| min_value=0.0, | ||
| max_value=100.0, | ||
| value=10.0, | ||
| step=5.0, | ||
| ) | ||
|
|
||
| # --- SECTION 3: Filter Execution and Collected Results View --- | ||
| if st.button("Apply Filter", type="primary"): | ||
| # Convert the original Pandas DataFrame into a Polars LazyFrame graph | ||
| quant_lazy = pl.from_pandas(pivot_df).lazy() | ||
|
|
||
| # Route execution to the chosen openms_insight engine function | ||
| if filter_method == "Low Abundance": | ||
| filtered_lazy = filter_low_abundance( | ||
| quantification_data=quant_lazy, | ||
| metadata=metadata_pl, | ||
| group_column="group", | ||
| threshold_percentile=threshold, | ||
| ) | ||
| elif filter_method == "Low Repeatability": | ||
| # Convert percent slider input to ratio expected by the function (e.g., 50.0% -> 0.5) | ||
| filtered_lazy = filter_low_repeatability( | ||
| quantification_data=quant_lazy, | ||
| metadata=metadata_pl, | ||
| group_column="group", | ||
| max_missing_ratio=threshold / 100.0, | ||
| ) | ||
| elif filter_method == "Low Variance": | ||
| filtered_lazy = filter_low_variance( | ||
| quantification_data=quant_lazy, | ||
| metadata=metadata_pl, | ||
| group_column="group", | ||
| threshold_percentile=threshold, | ||
| ) | ||
|
|
||
| # Collect the evaluated lazy graph and convert back to Pandas for visualization | ||
| filtered_df = strip_stat_columns(filtered_lazy.collect().to_pandas()) | ||
| st.session_state["filtered_df"] = filtered_df | ||
|
Comment on lines
+153
to
+154
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Invalidate or version downstream pipeline artifacts. The session keys have no workspace/input fingerprint, and recomputing an upstream stage leaves downstream results intact. Users can therefore run statistics against outputs from an older filter, workspace, or abundance dataset.
📍 Affects 3 files
🤖 Prompt for AI Agents |
||
|
|
||
| # Layout response metrics and the filtered matrix | ||
| st.success(f"Successfully applied **{filter_method}** filter!") | ||
|
|
||
| # Display dataset scale compression stats | ||
| col1, col2, col3 = st.columns(3) | ||
| col1.metric("Original Proteins", pivot_df.shape[0]) | ||
| col2.metric("Filtered Proteins", filtered_df.shape[0]) | ||
| col3.metric( | ||
| "Removed Proteins", pivot_df.shape[0] - filtered_df.shape[0], delta=None | ||
| ) | ||
|
|
||
| st.subheader("Filtered Abundance Table") | ||
| if filtered_df.empty: | ||
| st.warning( | ||
| "The filtered table is empty. Try relaxing the threshold constraints." | ||
| ) | ||
| else: | ||
| st.dataframe(filtered_df, use_container_width=True) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: OpenMS/quantms-web
Length of output: 210
🏁 Script executed:
Repository: OpenMS/quantms-web
Length of output: 6090
Scope
POLARS_MAX_THREADSto Windows.app.py:1-6sets this override for every platform, so Linux/macOS deployments lose Polars’ default parallelism too.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents