Skip to content

Commit 4de63dc

Browse files
committed
feat: LFQ/TMT Integration
1 parent 09dae8b commit 4de63dc

11 files changed

Lines changed: 2255 additions & 741 deletions

‎app.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
st.Page(Path("content", "results_pca.py"), title="PCA", icon="📊"),
2828
st.Page(Path("content", "results_heatmap.py"), title="Heatmap", icon="🔥"),
2929
st.Page(Path("content", "results_library.py"), title="Spectral Library", icon="📚"),
30+
st.Page(Path("content", "results_pathway_analysis.py"), title="Pathway Analysis", icon="📉"),
3031
],
3132
}
3233

‎content/results_abundance.py‎

Lines changed: 90 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""Abundance (ProteomicsLFQ) Results Page."""
22
import streamlit as st
33
import pandas as pd
4-
import numpy as np
54
from pathlib import Path
65
from src.common.common import page_setup
76
from src.common.results_helpers import get_workflow_dir, get_abundance_data
7+
from src.workflow.ParameterManager import ParameterManager
88

99
params = page_setup()
1010
st.title("Abundance Quantification")
@@ -22,6 +22,10 @@
2222

2323
workflow_dir = get_workflow_dir(st.session_state["workspace"])
2424
quant_dir = workflow_dir / "results" / "quant_results"
25+
parameter_manager = ParameterManager(workflow_dir, "TOPP Workflow")
26+
27+
workflow_params = parameter_manager.get_parameters_from_json()
28+
analysis_mode = workflow_params.get("analysis-mode", "LFQ")
2529

2630
if not quant_dir.exists():
2731
st.info("No quantification results available yet. Please run the workflow first.")
@@ -36,6 +40,60 @@
3640

3741
csv_file = csv_files[0]
3842

43+
def render_protein_table(pivot_df, group_map, is_lfq=True):
44+
"""Common function to render the protein-level abundance table"""
45+
st.markdown("### Protein-Level Abundance Table")
46+
st.info(
47+
"This protein-level table is generated by grouping all PSMs that map to the "
48+
"same protein and aggregating their intensities across samples.\n\n"
49+
"Additionally, log2 fold change and p-values are calculated between sample groups."
50+
)
51+
52+
# Display group comparison info
53+
groups = sorted(set(group_map.values()))
54+
if len(groups) >= 2:
55+
group1, group2 = sorted(groups)[:2]
56+
st.info(f"Statistical comparison: **{group2} vs {group1}**")
57+
58+
if is_lfq:
59+
# Handle LFQ mode columns (Raw Intensity)
60+
id_col = "ProteinName"
61+
exclude_cols = [id_col, "log2FC", "p-value", "PeptideSequence"]
62+
sample_cols = [c for c in pivot_df.columns if c not in exclude_cols]
63+
64+
pivot_df["Intensity"] = pivot_df[sample_cols].apply(list, axis=1)
65+
display_cols = [id_col, "log2FC", "p-value", "Intensity"] + sample_cols + ["PeptideSequence"]
66+
help_text = "Raw sample intensities"
67+
y_min = None
68+
else:
69+
# Handle non-LFQ mode columns (Log2-transformed Intensity)
70+
id_col = "protein"
71+
exclude_cols = [id_col, "log2FC", "p-value", "p-adj", "n_proteins", "n_peptides", "protein_score"]
72+
sample_cols = [c for c in pivot_df.columns if c not in exclude_cols and "ratio" not in c.lower()]
73+
74+
pivot_df["Intensity"] = pivot_df[sample_cols].apply(
75+
lambda row: [np.log2(v + 1) for v in row], axis=1
76+
)
77+
display_cols = [id_col, "log2FC", "p-value", "Intensity"] + sample_cols
78+
help_text = "Sample intensities (log2 scale)"
79+
y_min = 0
80+
81+
# Filter to available columns, then sort and display
82+
available_cols = [c for c in display_cols if c in pivot_df.columns]
83+
84+
st.dataframe(
85+
pivot_df[available_cols].sort_values("p-value"),
86+
column_config={
87+
"Intensity": st.column_config.BarChartColumn(
88+
"Intensity",
89+
help=help_text,
90+
width="small",
91+
y_min=y_min,
92+
),
93+
},
94+
use_container_width=True,
95+
)
96+
3997
protein_tab, psm_tab = st.tabs(["Protein Table", "PSM-level Quantification Table"])
4098

4199
try:
@@ -45,62 +103,44 @@
45103
st.info("No data found in this file.")
46104
st.stop()
47105

48-
with protein_tab:
49-
st.markdown("### Protein-Level Abundance Table")
106+
result = get_abundance_data(st.session_state["workspace"])
50107

51-
st.info(
52-
"This protein-level table is generated by grouping all PSMs that map to the "
53-
"same protein and aggregating their intensities across samples.\n\n"
54-
"Additionally, log2 fold change and p-values are calculated between sample groups."
55-
)
56-
57-
result = get_abundance_data(st.session_state["workspace"])
58-
if result is None:
59-
st.warning("Could not compute abundance data. Please ensure sample groups are defined in the Configure page.")
60-
st.page_link("content/workflow_configure.py", label="Go to Configure", icon="⚙️")
61-
st.stop()
108+
if analysis_mode == "LFQ":
109+
protein_tab, psm_tab = st.tabs(["Protein Table", "PSM-level Quantification Table"])
62110

63-
pivot_df, expr_df, group_map = result
111+
with protein_tab:
112+
if result is None:
113+
st.warning("Could not compute abundance data. Please ensure sample groups are defined in the Configure page.")
114+
# st.page_link("content/workflow_configure.py", label="Go to Configure", icon="⚙️")
115+
st.stop()
116+
117+
pivot_df, expr_df, group_map = result
118+
render_protein_table(pivot_df, group_map, is_lfq=True)
64119

65-
# Display group comparison info
66-
groups = sorted(set(group_map.values()))
67-
if len(groups) >= 2:
68-
group1, group2 = sorted(groups)[:2]
69-
st.info(f"Statistical comparison: **{group2} vs {group1}**")
120+
with psm_tab:
121+
st.markdown("### PSM-level Quantification Table")
122+
st.info(
123+
"This table shows the PSM-level quantification data, including protein IDs, "
124+
"peptide sequences, charge states, and intensities across samples. "
125+
"Each row represents one peptide-spectrum match detected from the MS/MS analysis."
126+
)
127+
st.dataframe(df, use_container_width=True)
70128

71-
# Get sample columns (between stats and PeptideSequence)
72-
sample_cols = [c for c in pivot_df.columns if c not in ["ProteinName", "log2FC", "p-value", "PeptideSequence"]]
129+
else:
130+
pre_processing_tab, protein_tab = st.tabs(["Pre-processing", "Protein Table"])
73131

74-
# Create bar chart column with log2-transformed values
75-
pivot_df["Intensity"] = pivot_df[sample_cols].apply(
76-
lambda row: [np.log2(v + 1) for v in row], axis=1
77-
)
132+
if result is None:
133+
st.info("💡 Please complete the configuration in the 'Configure' page to see results.")
134+
st.stop()
135+
136+
pivot_df, expr_df, group_map = result
78137

79-
# Reorder columns: place Intensity after p-value
80-
display_cols = ["ProteinName", "log2FC", "p-value", "Intensity"] + sample_cols + ["PeptideSequence"]
81-
display_df = pivot_df[display_cols]
82-
83-
st.dataframe(
84-
display_df.sort_values("p-value"),
85-
column_config={
86-
"Intensity": st.column_config.BarChartColumn(
87-
"Intensity",
88-
help="Sample intensities (log2 scale)",
89-
width="small",
90-
y_min=0,
91-
),
92-
},
93-
use_container_width=True,
94-
)
138+
with pre_processing_tab:
139+
st.write("### Final Results (Group row removed, Stats added)")
140+
st.dataframe(pivot_df.head(10))
95141

96-
with psm_tab:
97-
st.markdown("### PSM-level Quantification Table")
98-
st.info(
99-
"This table shows the PSM-level quantification data, including protein IDs, "
100-
"peptide sequences, charge states, and intensities across samples. "
101-
"Each row represents one peptide-spectrum match detected from the MS/MS analysis."
102-
)
103-
st.dataframe(df, use_container_width=True)
142+
with protein_tab:
143+
render_protein_table(pivot_df, group_map, is_lfq=False)
104144

105145
except Exception as e:
106146
st.error(f"Failed to load {csv_file.name}: {e}")

‎content/results_heatmap.py‎

Lines changed: 93 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from scipy.cluster.hierarchy import linkage, leaves_list
66
from scipy.spatial.distance import pdist
77
from src.common.common import page_setup
8-
from src.common.results_helpers import get_abundance_data
8+
from src.common.results_helpers import get_abundance_data, get_workflow_dir
9+
from src.workflow.ParameterManager import ParameterManager
910

1011
params = page_setup()
1112
st.title("Heatmap")
@@ -29,48 +30,103 @@
2930

3031
pivot_df, expr_df, group_map = result
3132

32-
top_n = st.slider("Number of proteins", 20, 200, 50, key="heatmap_top_n")
33+
workflow_dir = get_workflow_dir(st.session_state["workspace"])
34+
parameter_manager = ParameterManager(workflow_dir, "TOPP Workflow")
3335

34-
var_series = expr_df.var(axis=1)
35-
top_proteins = var_series.sort_values(ascending=False).head(top_n).index
36-
heatmap_df = expr_df.loc[top_proteins]
37-
heatmap_z = heatmap_df.sub(heatmap_df.mean(axis=1), axis=0).div(heatmap_df.std(axis=1), axis=0)
38-
heatmap_z = heatmap_z.replace([np.inf, -np.inf], np.nan).dropna()
36+
workflow_params = parameter_manager.get_parameters_from_json()
37+
analysis_mode = workflow_params.get("analysis-mode", "LFQ")
3938

40-
if not heatmap_z.empty:
41-
row_linkage = linkage(pdist(heatmap_z.values), method="average")
42-
row_order = leaves_list(row_linkage)
39+
st.write("Workflow Analysis Mode:", analysis_mode)
4340

44-
col_linkage = linkage(pdist(heatmap_z.T.values), method="average")
45-
col_order = leaves_list(col_linkage)
41+
if analysis_mode == "LFQ":
42+
top_n = st.slider("Number of proteins", 20, 200, 50, key="heatmap_top_n")
4643

47-
heatmap_clustered = heatmap_z.iloc[row_order, col_order]
44+
var_series = expr_df.var(axis=1)
45+
top_proteins = var_series.sort_values(ascending=False).head(top_n).index
46+
heatmap_df = expr_df.loc[top_proteins]
47+
heatmap_z = heatmap_df.sub(heatmap_df.mean(axis=1), axis=0).div(heatmap_df.std(axis=1), axis=0)
48+
heatmap_z = heatmap_z.replace([np.inf, -np.inf], np.nan).dropna()
4849

49-
fig_heatmap = px.imshow(
50-
heatmap_clustered,
51-
labels=dict(x="Sample", y="Protein", color="Z-score"),
52-
aspect="auto",
53-
color_continuous_scale=[[0.0, "#3b6fb6"], [0.5, "white"], [1.0, "#b40426"]],
54-
zmin=-3, zmax=3
55-
)
50+
if not heatmap_z.empty:
51+
row_linkage = linkage(pdist(heatmap_z.values), method="average")
52+
row_order = leaves_list(row_linkage)
5653

57-
fig_heatmap.update_layout(
58-
height=700,
59-
xaxis={'side': 'bottom'},
60-
yaxis={'side': 'left'}
61-
)
54+
col_linkage = linkage(pdist(heatmap_z.T.values), method="average")
55+
col_order = leaves_list(col_linkage)
6256

63-
fig_heatmap.update_xaxes(tickfont=dict(size=10))
64-
fig_heatmap.update_yaxes(tickfont=dict(size=8))
57+
heatmap_clustered = heatmap_z.iloc[row_order, col_order]
6558

66-
st.plotly_chart(fig_heatmap, use_container_width=True)
59+
fig_heatmap = px.imshow(
60+
heatmap_clustered,
61+
labels=dict(x="Sample", y="Protein", color="Z-score"),
62+
aspect="auto",
63+
color_continuous_scale=[[0.0, "#3b6fb6"], [0.5, "white"], [1.0, "#b40426"]],
64+
zmin=-3, zmax=3
65+
)
66+
67+
fig_heatmap.update_layout(
68+
height=700,
69+
xaxis={'side': 'bottom'},
70+
yaxis={'side': 'left'}
71+
)
72+
73+
fig_heatmap.update_xaxes(tickfont=dict(size=10))
74+
fig_heatmap.update_yaxes(tickfont=dict(size=8))
75+
76+
st.plotly_chart(fig_heatmap, use_container_width=True)
77+
else:
78+
st.warning("Insufficient data to generate the heatmap.")
79+
80+
st.markdown("---")
81+
st.markdown("**Other visualizations:**")
82+
col1, col2 = st.columns(2)
83+
with col1:
84+
st.page_link("content/results_volcano.py", label="Volcano Plot", icon="🌋")
85+
with col2:
86+
st.page_link("content/results_pca.py", label="PCA", icon="📊")
6787
else:
68-
st.warning("Insufficient data to generate the heatmap.")
69-
70-
st.markdown("---")
71-
st.markdown("**Other visualizations:**")
72-
col1, col2 = st.columns(2)
73-
with col1:
74-
st.page_link("content/results_volcano.py", label="Volcano Plot", icon="🌋")
75-
with col2:
76-
st.page_link("content/results_pca.py", label="PCA", icon="📊")
88+
top_n = st.slider("Number of proteins", 20, 200, 50, key="heatmap_top_n")
89+
90+
var_series = expr_df.var(axis=1)
91+
top_proteins = var_series.sort_values(ascending=False).head(top_n).index
92+
heatmap_df = expr_df.loc[top_proteins]
93+
heatmap_z = heatmap_df.sub(heatmap_df.mean(axis=1), axis=0).div(heatmap_df.std(axis=1), axis=0)
94+
heatmap_z = heatmap_z.replace([np.inf, -np.inf], np.nan).dropna()
95+
96+
if not heatmap_z.empty:
97+
row_linkage = linkage(pdist(heatmap_z.values), method="average")
98+
row_order = leaves_list(row_linkage)
99+
100+
col_linkage = linkage(pdist(heatmap_z.T.values), method="average")
101+
col_order = leaves_list(col_linkage)
102+
103+
heatmap_clustered = heatmap_z.iloc[row_order, col_order]
104+
105+
fig_heatmap = px.imshow(
106+
heatmap_clustered,
107+
labels=dict(x="Sample", y="Protein", color="Z-score"),
108+
aspect="auto",
109+
color_continuous_scale=[[0.0, "#3b6fb6"], [0.5, "white"], [1.0, "#b40426"]],
110+
zmin=-3, zmax=3
111+
)
112+
113+
fig_heatmap.update_layout(
114+
height=700,
115+
xaxis={'side': 'bottom'},
116+
yaxis={'side': 'left'}
117+
)
118+
119+
fig_heatmap.update_xaxes(tickfont=dict(size=10))
120+
fig_heatmap.update_yaxes(tickfont=dict(size=8))
121+
122+
st.plotly_chart(fig_heatmap, width="stretch")
123+
else:
124+
st.warning("Insufficient data to generate the heatmap.")
125+
126+
st.markdown("---")
127+
st.markdown("**Other visualizations:**")
128+
col1, col2 = st.columns(2)
129+
with col1:
130+
st.page_link("content/results_volcano.py", label="Volcano Plot", icon="🌋")
131+
with col2:
132+
st.page_link("content/results_pca.py", label="PCA", icon="📊")

0 commit comments

Comments
 (0)