Skip to content

TMT-LFQ-OpenMS insight integration#29

Open
JKVISION1101 wants to merge 9 commits into
OpenMS:mainfrom
JKVISION1101:main
Open

TMT-LFQ-OpenMS insight integration#29
JKVISION1101 wants to merge 9 commits into
OpenMS:mainfrom
JKVISION1101:main

Conversation

@JKVISION1101

@JKVISION1101 JKVISION1101 commented Jul 17, 2026

Copy link
Copy Markdown

Re-integrate LFQ and TMT Workflow Modes

Summary

Re-introduces Label-Free Quantification (LFQ) and Tandem Mass Tag (TMT) proteomics workflows with improved UI reactivity and parameter handling.

Key Features

Dual Workflow Modes

  • LFQ: Comet → Percolator → IDFilter → Library → ProteomicsLFQ → Groups (6 steps)
  • TMT: IsobaricAnalyzer → Comet → PercolatorAdapter → IDFilter → IDMapper → FileMerger → ProteinInference → ProteinQuantifier → Groups (11 steps)
  • User-selectable mode with reactive UI updates

Reactive UI Components

  • Added reactive: bool parameter to conditionally re-render parent fragments
  • Enables conditional visibility for tool parameters (e.g., DecoyDatabase only shows when "Generate Decoys" is checked)

Boolean CLI Flags

  • Proper OpenMS CLI format: -parameter_name (not -parameter_name true)
  • Examples: PeptideIndexing:IL_equivalent, mass_recalibration, delete_unreferenced_peptide_hits

Data Processing

  • LFQ: CSV quantification + sample group mapping
  • TMT: Isobaric channel mapping + "skip" channel support
  • Absolute path resolution for multiprocessing compatibility

Visualization Integration

  • OpenMS-Insight components (Table, Heatmap, SequenceView, LinePlot) with caching
  • Per-sample MS2 spectra extraction and annotation

New Analysis Pages

  • enrichment.py, filtering.py, imputation.py, normalization.py, statistical.py

Files Changed

  • src/WorkflowTest.py - Dual workflow implementation
  • src/workflow/StreamlitUI.py - Reactive mode support
  • src/workflow/ParameterManager.py - Boolean flag handling
  • src/workflow/CommandExecutor.py - CLI flag formatting
  • src/common/results_helpers.py - LFQ/TMT data parsing
  • content/ - New analysis pages

Testing

  • LFQ: mzML upload → identification → quantification
  • TMT: TMT-labeled samples → isobaric analysis → quantification
  • UI: Mode switching, conditional parameters, group assignment

Summary by CodeRabbit

  • New Features

    • Added a differential protein analysis workflow covering filtering, imputation, normalization, statistical testing, and GO enrichment.
    • Added interactive volcano plots, PCA, heatmaps, clustered heatmaps, and enrichment visualizations.
    • Added mounted-directory browsing for selecting analysis files.
    • Added support for boolean TOPP tool options and dynamically generated TMT sample-group fields.
  • Bug Fixes

    • Improved stability on some Windows systems by preventing native thread-pool crashes.
    • Improved handling of cached abundance results and changing workflow parameters.

jkvision1101 and others added 9 commits July 16, 2026 10:34
Allows input_TOPP() to designate parameters as flags (present/absent)
instead of key-value pairs, persisted to params.json and session_state
so run_topp() can correctly build the command line.
Split WorkflowTest configure() into separate LFQ and TMT tool tabs, and
add new preprocessing/analysis pages (filtering, normalization,
imputation, statistical testing, GO enrichment, clustered heatmap,
pathway analysis) built on openms_insight engine functions.
Split WorkflowTest configure() into separate LFQ and TMT tool tabs, and
add new preprocessing/analysis pages (filtering, normalization,
imputation, statistical testing, GO enrichment, clustered heatmap,
pathway analysis) built on openms_insight engine functions.
Split WorkflowTest configure() into separate LFQ and TMT tool tabs, and
add new preprocessing/analysis pages (filtering, normalization,
imputation, statistical testing, GO enrichment, clustered heatmap,
pathway analysis) built on openms_insight engine functions.
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Differential protein analysis

Layer / File(s) Summary
Abundance data pipeline
src/common/results_helpers.py
Abundance loading now invalidates on parameter changes, normalizes identifiers and groups, and returns intensity-focused LFQ/TMT matrices.
Preprocessing workflow
content/filtering.py, content/imputation.py, content/normalization.py
Filtering, imputation, and normalization pages process staged session-state tables with Polars engines.
Statistics and visualizations
content/statistical.py, content/enrichment.py, content/results_*.py, app.py, requirements.txt
The navigation adds differential-analysis pages, statistical and enrichment workflows, and OpenMS-Insight PCA, volcano, and heatmap components.

Workflow interfaces

Layer / File(s) Summary
TOPP boolean flag execution
src/workflow/ParameterManager.py, src/workflow/CommandExecutor.py, src/workflow/StreamlitUI.py, pr-397.patch
TOPP boolean parameters are persisted, rendered as checkboxes, and emitted as presence-only CLI flags.
Mounted-directory upload flow
src/workflow/StreamlitUI.py
Online uploads can browse validated mounted directories and persist selected files.
Reactive TMT group assignment
src/WorkflowTest.py
TMT channel inputs are generated from validated plex types and stale channel parameters are removed.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant StatisticalPage
  participant SessionState
  participant EnrichmentPage
  participant VisualizationPage
  User->>StatisticalPage: run statistical analysis
  StatisticalPage->>SessionState: store statistics_df
  EnrichmentPage->>SessionState: read statistics_df
  VisualizationPage->>SessionState: read analysis tables
Loading
sequenceDiagram
  participant StreamlitUI
  participant ParameterManager
  participant CommandExecutor
  StreamlitUI->>ParameterManager: persist flag metadata
  ParameterManager->>CommandExecutor: provide merged parameters
  CommandExecutor->>CommandExecutor: emit CLI flags and values
Loading

Possibly related PRs

Suggested reviewers: t0mdavid-m

Poem

I twitch my nose at plots anew,
Through filtered fields and values true.
Flags hop lightly, mounted files gleam,
PCA floats in a polars dream.
TMT channels line up bright—
Binky-approved workflow delight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change by naming the LFQ/TMT workflow re-integration and OpenMS Insight integration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/workflow/StreamlitUI.py (1)

1695-1713: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exclude _flag_params from the exported method summary.

The new internal dictionary is classified as a TOPP tool and appears in user-facing exports. Skip all internal keys while retaining _defaults for the explicit merge below.

Proposed fix
         for k, v in params.items():
-            if k == "_defaults":
+            if k.startswith("_"):
                 continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workflow/StreamlitUI.py` around lines 1695 - 1713, Update the parameter
classification loop to skip `_flag_params` alongside `_defaults`, preventing
this internal dictionary from being added to the exported TOPP summary while
preserving the existing `_defaults` merge below.
🧹 Nitpick comments (4)
content/enrichment.py (1)

134-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Render interactive Plotly output through streamlit_plotly_events.

st.plotly_chart does not expose the required selection/click event flow. Route the generated figures through plotly_events and persist the returned interaction state.

As per coding guidelines, interactive visualizations must use Plotly and streamlit_plotly_events.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/enrichment.py` around lines 134 - 139, Replace the st.plotly_chart
call in the fig/df_go rendering block with
streamlit_plotly_events.plotly_events, passing the generated fig and capturing
its returned interaction state in the existing session-state flow. Preserve the
dataframe rendering and ensure the Plotly figure remains interactive.

Source: Coding guidelines

content/statistical.py (1)

96-130: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Isolate interactive controls and rendering in @st.fragment functions.

Each widget currently triggers a full-page rerun, including abundance loading, conversions, previews, and component initialization.

  • content/statistical.py#L96-L130: wrap statistical configuration and execution controls in a fragment.
  • content/enrichment.py#L62-L100: fragment the cutoff controls and enrichment trigger.
  • content/results_pca.py#L93-L161: fragment PCA controls and component rendering.
  • content/results_volcano.py#L48-L88: fragment threshold controls and plot rendering.
  • content/results_heatmap.py#L39-L91: fragment variance selection and heatmap rendering.
  • content/results_heatmap_clustered.py#L38-L99: fragment variance selection and clustered rendering.

As per coding guidelines, **/*.py interactive UI updates must use @st.fragment to avoid full-page reloads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/statistical.py` around lines 96 - 130, Wrap the interactive UI
sections in `@st.fragment` functions so widget updates do not rerun the full page:
content/statistical.py lines 96-130 should contain the statistical configuration
and execution controls; content/enrichment.py lines 62-100 the cutoff controls
and enrichment trigger; content/results_pca.py lines 93-161 the PCA controls and
component rendering; content/results_volcano.py lines 48-88 the threshold
controls and plot rendering; content/results_heatmap.py lines 39-91 the variance
selection and heatmap rendering; and content/results_heatmap_clustered.py lines
38-99 the variance selection and clustered rendering. Preserve each section’s
existing behavior while ensuring all interactive updates are handled within its
fragment.

Source: Coding guidelines

content/filtering.py (1)

78-124: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Wrap the interactive preprocessing controls in @st.fragment.

These widgets currently rerun each entire page, including abundance loading and large table rendering.

  • content/filtering.py#L78-L124: move filter controls and execution into a fragment.
  • content/imputation.py#L86-L118: fragment the imputation controls and execution.
  • content/normalization.py#L152-L190: fragment the normalization controls and execution.

As per coding guidelines, **/*.py: Use @st.fragment decorator for interactive UI updates without full page reloads. <coding_guidelines>

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/filtering.py` around lines 78 - 124, Wrap the filtering controls and
Apply Filter execution in content/filtering.py (lines 78-124) with an
`@st.fragment` function. Apply the same change to the imputation controls and
execution in content/imputation.py (lines 86-118), and the normalization
controls and execution in content/normalization.py (lines 152-190), preserving
each workflow’s existing behavior while limiting widget updates to the fragment.

Source: Coding guidelines

pr-397.patch (1)

1-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove this generated patch artifact.

The changes already exist in the source files; retaining this duplicate patch adds noise and will drift from the implementation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pr-397.patch` around lines 1 - 120, Remove the generated patch artifact
represented by pr-397.patch from the change set. Keep the existing
implementations in CommandExecutor.run_topp and StreamlitUI.input_TOPP
unchanged, since those source-file changes already contain the intended
functionality.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app.py`:
- Around line 1-6: Scope the POLARS_MAX_THREADS override in the app.py startup
initialization to Windows only, applying
os.environ.setdefault("POLARS_MAX_THREADS", "1") when the runtime platform is
Windows. Preserve the ordering so this condition executes before any direct or
transitive Polars import, while leaving non-Windows platforms’ default thread
settings unchanged.

In `@content/filtering.py`:
- Around line 153-154: Invalidate downstream artifacts when replacing
filtered_df: in content/filtering.py lines 153-154, clear imputation,
normalization, and statistics session results. In content/imputation.py lines
50-52, reuse filtered output only when its workspace/input fingerprint matches;
at lines 136-139, clear normalization and statistics after replacing imputed_df.
In content/normalization.py lines 54-62, validate provenance before using cached
upstream outputs; at lines 227-230, clear statistics after replacing
normalized_df.
- Around line 11-15: Resolve the incompatible openms_insight imports by either
pinning the dependency to a fork or revision that provides the analysis
namespace, or migrating the imports to the current supported API. Apply the
chosen fix at content/filtering.py lines 11-15, content/imputation.py lines
10-11, and content/normalization.py lines 9-14 so all three pages import
successfully.

In `@content/results_abundance.py`:
- Around line 63-72: Update the non-LFQ branch around id_col to derive the
identifier from the report schema’s first column instead of hardcoding
"protein". Exclude that derived identifier from sample_cols and preserve it in
display_cols so nonstandard first-column names are not passed to np.log2 or
omitted from the output.

In `@content/results_heatmap.py`:
- Around line 37-49: Update the data source used by the heatmap flow in
content/results_heatmap.py lines 37-49 to select the active preprocessing
checkpoint in the same order as PCA/statistics: normalized, then imputed, then
filtered, then raw, before variance selection. Apply the same checkpoint
selection in content/results_heatmap_clustered.py lines 38-45 before its Z-score
normalization; preserve each page’s existing variance and rendering logic after
using the selected dataframe.

In `@content/statistical.py`:
- Around line 71-95: The sample-column logic in content/statistical.py lines
71-95 and content/results_pca.py lines 66-71 must stop using exclusion lists
that treat metadata columns as samples. Reuse the shared analysis-mode-aware
loader helper, or derive the columns from normalized sample_group_map keys,
consistently in both sites before group counting, variance calculation, and
metadata construction; preserve the existing downstream behavior once the valid
sample set is established.
- Around line 40-69: Bind all shared artifacts to current workspace and upstream
provenance before use: in content/statistical.py lines 40-69, validate
workspace, source-file, parameters, and preprocessing metadata before selecting
normalized_df, imputed_df, or filtered_df; in content/statistical.py lines
130-153, invalidate statistics_df before each new run and store it with
workspace, input, and configuration provenance; in content/results_pca.py lines
34-64, reject preprocessing artifacts from another workspace or abundance
revision; in content/enrichment.py lines 25-46, verify statistics match the
current workspace and abundance inputs; and in content/results_volcano.py lines
22-45, verify statistics provenance before combining with the current workspace
identifier schema.

In `@requirements.txt`:
- Line 149: Update the requirements dependency entries to use the
polars[rtcompat] extra and remove the standalone polars-runtime-compat entry,
while retaining only the existing pinned polars dependency so the runtime
compatibility package stays version-locked.

In `@src/common/results_helpers.py`:
- Around line 397-424: Update get_sample_group_map so every sample column in
pivot_df is present in the returned normalized map, defaulting missing or blank
group_map entries to "Unassigned". Preserve the existing LFQ name normalization
and TMT index-to-column matching, while ensuring unmatched sample columns are
also included with the required default.

In `@src/workflow/CommandExecutor.py`:
- Around line 308-315: Update the regular-parameter handling in CommandExecutor
so list-valued parameters are expanded into separate command arguments rather
than converted to one string representation. Preserve the existing skipping of
empty or None values, option-key emission, and newline splitting for string
values; convert each list element to its own argument using the existing value
formatting behavior.

In `@src/workflow/ParameterManager.py`:
- Around line 84-94: Connect ParamXML boolean discovery through all listed
sites: in src/workflow/ParameterManager.py lines 84-94, expose discovered
boolean paths per tool instance instead of only caching them; in
src/workflow/CommandExecutor.py lines 272-277, merge those instance flags with
persisted explicit flags; in src/workflow/StreamlitUI.py lines 938-948, persist
discovered paths under tool_instance_name; and in src/workflow/StreamlitUI.py
lines 1131-1139, use boolean metadata to render checkboxes and normalize
"true"/"false" values.

In `@src/workflow/StreamlitUI.py`:
- Around line 443-485: Update the selectable-entry handling around _is_match and
the selectable loop to resolve each entry and reject any whose resolved path is
outside the resolved mount_root, including symlinked files and directories.
Perform this validation before rendering or persisting entries, while preserving
the existing directory/file filtering and selection behavior for paths that
remain beneath mount_root.

In `@src/WorkflowTest.py`:
- Around line 576-582: Update the validation in the selected_type handling
before num_plex and channels are created: replace the broad substring/regex
acceptance with an explicit whitelist of supported plex types or enforce a
strict maximum channel count. Ensure invalid or excessively large values trigger
the existing warning and never reach channel widget allocation.
- Around line 612-619: Update the orphan cleanup block in the workflow test to
remove each orphaned channel from both self.params and the parameter manager’s
session-key storage before persistence. Write the cleaned self.params dictionary
directly without reloading or merging the deleted prefixed keys, ensuring
subsequent loads do not reintroduce orphaned TMT-group entries.

---

Outside diff comments:
In `@src/workflow/StreamlitUI.py`:
- Around line 1695-1713: Update the parameter classification loop to skip
`_flag_params` alongside `_defaults`, preventing this internal dictionary from
being added to the exported TOPP summary while preserving the existing
`_defaults` merge below.

---

Nitpick comments:
In `@content/enrichment.py`:
- Around line 134-139: Replace the st.plotly_chart call in the fig/df_go
rendering block with streamlit_plotly_events.plotly_events, passing the
generated fig and capturing its returned interaction state in the existing
session-state flow. Preserve the dataframe rendering and ensure the Plotly
figure remains interactive.

In `@content/filtering.py`:
- Around line 78-124: Wrap the filtering controls and Apply Filter execution in
content/filtering.py (lines 78-124) with an `@st.fragment` function. Apply the
same change to the imputation controls and execution in content/imputation.py
(lines 86-118), and the normalization controls and execution in
content/normalization.py (lines 152-190), preserving each workflow’s existing
behavior while limiting widget updates to the fragment.

In `@content/statistical.py`:
- Around line 96-130: Wrap the interactive UI sections in `@st.fragment` functions
so widget updates do not rerun the full page: content/statistical.py lines
96-130 should contain the statistical configuration and execution controls;
content/enrichment.py lines 62-100 the cutoff controls and enrichment trigger;
content/results_pca.py lines 93-161 the PCA controls and component rendering;
content/results_volcano.py lines 48-88 the threshold controls and plot
rendering; content/results_heatmap.py lines 39-91 the variance selection and
heatmap rendering; and content/results_heatmap_clustered.py lines 38-99 the
variance selection and clustered rendering. Preserve each section’s existing
behavior while ensuring all interactive updates are handled within its fragment.

In `@pr-397.patch`:
- Around line 1-120: Remove the generated patch artifact represented by
pr-397.patch from the change set. Keep the existing implementations in
CommandExecutor.run_topp and StreamlitUI.input_TOPP unchanged, since those
source-file changes already contain the intended functionality.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d64fff3c-3c59-422e-a377-f3ad96fbacee

📥 Commits

Reviewing files that changed from the base of the PR and between 83646ec and 262d451.

📒 Files selected for processing (19)
  • app.py
  • content/enrichment.py
  • content/filtering.py
  • content/imputation.py
  • content/normalization.py
  • content/results_abundance.py
  • content/results_heatmap.py
  • content/results_heatmap_clustered.py
  • content/results_pca.py
  • content/results_proteomicslfq.py
  • content/results_volcano.py
  • content/statistical.py
  • pr-397.patch
  • requirements.txt
  • src/WorkflowTest.py
  • src/common/results_helpers.py
  • src/workflow/CommandExecutor.py
  • src/workflow/ParameterManager.py
  • src/workflow/StreamlitUI.py

Comment thread app.py
Comment on lines +1 to +6
import os
# Polars' default (CPU-core-count-sized) native thread pool access-violation
# crashes the whole process on some high-core-count Windows machines when
# invoked from Streamlit's script-runner thread. Must be set before polars
# is imported anywhere (including transitively via openms_insight).
os.environ.setdefault("POLARS_MAX_THREADS", "1")

Copy link
Copy Markdown

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:

#!/bin/bash
set -euo pipefail

printf '\n== app.py ==\n'
nl -ba app.py | sed -n '1,120p'

printf '\n== search POLARS_MAX_THREADS / polars imports ==\n'
rg -n --hidden -S 'POLARS_MAX_THREADS|import polars|from polars|thread_pool_size|thread pool' .

printf '\n== files mentioning openms_insight ==\n'
rg -n --hidden -S 'openms_insight' .

Repository: OpenMS/quantms-web

Length of output: 210


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== app.py ==\n'
sed -n '1,120p' app.py | cat -n

printf '\n== search POLARS_MAX_THREADS / polars imports ==\n'
grep -RIn --exclude-dir=.git --exclude-dir=.venv --exclude-dir=node_modules \
  -e 'POLARS_MAX_THREADS' -e 'import polars' -e 'from polars' -e 'thread_pool_size' -e 'thread pool' .

printf '\n== files mentioning openms_insight ==\n'
grep -RIn --exclude-dir=.git --exclude-dir=.venv --exclude-dir=node_modules \
  -e 'openms_insight' .

Repository: OpenMS/quantms-web

Length of output: 6090


Scope POLARS_MAX_THREADS to Windows.
app.py:1-6 sets this override for every platform, so Linux/macOS deployments lose Polars’ default parallelism too.

Proposed fix
 import os
+import sys
 
-if os.environ.setdefault("POLARS_MAX_THREADS", "1")
+if sys.platform == "win32":
+    os.environ.setdefault("POLARS_MAX_THREADS", "1")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import os
# Polars' default (CPU-core-count-sized) native thread pool access-violation
# crashes the whole process on some high-core-count Windows machines when
# invoked from Streamlit's script-runner thread. Must be set before polars
# is imported anywhere (including transitively via openms_insight).
os.environ.setdefault("POLARS_MAX_THREADS", "1")
import os
import sys
# Polars' default (CPU-core-count-sized) native thread pool access-violation
# crashes the whole process on some high-core-count Windows machines when
# invoked from Streamlit's script-runner thread. Must be set before polars
# is imported anywhere (including transitively via openms_insight).
if sys.platform == "win32":
os.environ.setdefault("POLARS_MAX_THREADS", "1")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.py` around lines 1 - 6, Scope the POLARS_MAX_THREADS override in the
app.py startup initialization to Windows only, applying
os.environ.setdefault("POLARS_MAX_THREADS", "1") when the runtime platform is
Windows. Preserve the ordering so this condition executes before any direct or
transitive Polars import, while leaving non-Windows platforms’ default thread
settings unchanged.

Comment thread content/filtering.py
Comment on lines +11 to +15
from openms_insight.analysis.filter import (
filter_low_abundance,
filter_low_repeatability,
filter_low_variance,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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*' . || true

Repository: 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"
done

Repository: OpenMS/quantms-web

Length of output: 10510


🌐 Web query:

openms-insight 0.1.13 openms_insight.analysis package

💡 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

  • content/filtering.py
  • content/imputation.py
  • content/normalization.py

openms-insight>=0.1.13 does not expose openms_insight.analysis.*, so these pages fail on import unless the dependency is switched to a fork/revision that provides that namespace or the code is moved to the current API.

📍 Affects 3 files
  • content/filtering.py#L11-L15 (this comment)
  • content/imputation.py#L10-L11
  • content/normalization.py#L9-L14
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/filtering.py` around lines 11 - 15, Resolve the incompatible
openms_insight imports by either pinning the dependency to a fork or revision
that provides the analysis namespace, or migrating the imports to the current
supported API. Apply the chosen fix at content/filtering.py lines 11-15,
content/imputation.py lines 10-11, and content/normalization.py lines 9-14 so
all three pages import successfully.

Comment thread content/filtering.py
Comment on lines +153 to +154
filtered_df = strip_stat_columns(filtered_lazy.collect().to_pandas())
st.session_state["filtered_df"] = filtered_df

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.

  • content/filtering.py#L153-L154: clear imputation, normalization, and statistics after replacing filtered_df.
  • content/imputation.py#L50-L52: only reuse filtering output when its workspace/input fingerprint matches.
  • content/imputation.py#L136-L139: clear normalization and statistics after replacing imputed_df.
  • content/normalization.py#L54-L62: validate provenance before selecting cached upstream outputs.
  • content/normalization.py#L227-L230: clear statistics after replacing normalized_df.
📍 Affects 3 files
  • content/filtering.py#L153-L154 (this comment)
  • content/imputation.py#L50-L52
  • content/imputation.py#L136-L139
  • content/normalization.py#L54-L62
  • content/normalization.py#L227-L230
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/filtering.py` around lines 153 - 154, Invalidate downstream artifacts
when replacing filtered_df: in content/filtering.py lines 153-154, clear
imputation, normalization, and statistics session results. In
content/imputation.py lines 50-52, reuse filtered output only when its
workspace/input fingerprint matches; at lines 136-139, clear normalization and
statistics after replacing imputed_df. In content/normalization.py lines 54-62,
validate provenance before using cached upstream outputs; at lines 227-230,
clear statistics after replacing normalized_df.

Comment on lines 63 to +72
else:
# Handle non-LFQ mode columns (Log2-transformed Intensity)
id_col = "protein"
exclude_cols = [id_col, "log2FC", "p-value", "p-adj", "n_proteins", "n_peptides", "protein_score"]
exclude_cols = [id_col, "n_proteins", "n_peptides", "protein_score"]
sample_cols = [c for c in pivot_df.columns if c not in exclude_cols and "ratio" not in c.lower()]

pivot_df["Intensity"] = pivot_df[sample_cols].apply(
lambda row: [np.log2(v + 1) for v in row], axis=1
)
display_cols = [id_col, "log2FC", "p-value", "Intensity"] + sample_cols
display_cols = [id_col, "Intensity"] + sample_cols

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Derive the TMT identifier from the report schema.

The shared loader supports any first-column identifier, but this renderer hardcodes protein. Other valid names become sample columns and reach np.log2, causing a type error and omitting the identifier from the table.

Proposed fix
-        id_col = "protein"
+        id_col = pivot_df.columns[0]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else:
# Handle non-LFQ mode columns (Log2-transformed Intensity)
id_col = "protein"
exclude_cols = [id_col, "log2FC", "p-value", "p-adj", "n_proteins", "n_peptides", "protein_score"]
exclude_cols = [id_col, "n_proteins", "n_peptides", "protein_score"]
sample_cols = [c for c in pivot_df.columns if c not in exclude_cols and "ratio" not in c.lower()]
pivot_df["Intensity"] = pivot_df[sample_cols].apply(
lambda row: [np.log2(v + 1) for v in row], axis=1
)
display_cols = [id_col, "log2FC", "p-value", "Intensity"] + sample_cols
display_cols = [id_col, "Intensity"] + sample_cols
else:
# Handle non-LFQ mode columns (Log2-transformed Intensity)
id_col = pivot_df.columns[0]
exclude_cols = [id_col, "n_proteins", "n_peptides", "protein_score"]
sample_cols = [c for c in pivot_df.columns if c not in exclude_cols and "ratio" not in c.lower()]
pivot_df["Intensity"] = pivot_df[sample_cols].apply(
lambda row: [np.log2(v + 1) for v in row], axis=1
)
display_cols = [id_col, "Intensity"] + sample_cols
🧰 Tools
🪛 Ruff (0.15.21)

[warning] 72-72: Consider [id_col, "Intensity", *sample_cols] instead of concatenation

Replace with [id_col, "Intensity", *sample_cols]

(RUF005)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/results_abundance.py` around lines 63 - 72, Update the non-LFQ branch
around id_col to derive the identifier from the report schema’s first column
instead of hardcoding "protein". Exclude that derived identifier from
sample_cols and preserve it in display_cols so nonstandard first-column names
are not passed to np.log2 or omitted from the output.

Comment on lines +37 to +49
sample_cols = expr_df.columns.tolist()

# UI settings (number of top variance proteins)
top_n = st.slider("Number of proteins (Highest Variance)", 20, 200, 50, key="heatmap_top_n")

# Process data (variance selection -> Z-score normalization)
var_series = expr_df.var(axis=1)
top_proteins = var_series.sort_values(ascending=False).head(top_n).index
heatmap_df = expr_df.loc[top_proteins]

# Compute Z-scores and clean missing/invalid values
heatmap_z = heatmap_df.sub(heatmap_df.mean(axis=1), axis=0).div(heatmap_df.std(axis=1), axis=0)
heatmap_z = heatmap_z.replace([np.inf, -np.inf], np.nan).dropna()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Render heatmaps from the active preprocessing checkpoint.

Both pages always use the original expr_df, so filtering, imputation, and normalization have no effect on either heatmap.

  • content/results_heatmap.py#L37-L49: select normalized → imputed → filtered → raw data consistently with PCA/statistics before variance selection.
  • content/results_heatmap_clustered.py#L38-L45: apply the same checkpoint selection before Z-score normalization.
📍 Affects 2 files
  • content/results_heatmap.py#L37-L49 (this comment)
  • content/results_heatmap_clustered.py#L38-L45
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@content/results_heatmap.py` around lines 37 - 49, Update the data source used
by the heatmap flow in content/results_heatmap.py lines 37-49 to select the
active preprocessing checkpoint in the same order as PCA/statistics: normalized,
then imputed, then filtered, then raw, before variance selection. Apply the same
checkpoint selection in content/results_heatmap_clustered.py lines 38-45 before
its Z-score normalization; preserve each page’s existing variance and rendering
logic after using the selected dataframe.

Comment on lines +308 to 315
# Regular parameter: skip empty/None, append value otherwise
if v == "" or v is None:
continue
command += [f"-{k}"]
if isinstance(v, str) and "\n" in v:
command += v.split("\n")
elif isinstance(v, bool):
command += [str(v).lower()]
else:
command += [str(v)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Expand list parameters instead of stringifying them.

A merged value such as ["A", "B"] becomes the single argument "['A', 'B']". This breaks imported/default list-valued TOPP parameters.

Proposed fix
                 if isinstance(v, str) and "\n" in v:
                     command += v.split("\n")
+                elif isinstance(v, list):
+                    command += [str(item) for item in v]
                 else:
                     command += [str(v)]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Regular parameter: skip empty/None, append value otherwise
if v == "" or v is None:
continue
command += [f"-{k}"]
if isinstance(v, str) and "\n" in v:
command += v.split("\n")
elif isinstance(v, bool):
command += [str(v).lower()]
else:
command += [str(v)]
# Regular parameter: skip empty/None, append value otherwise
if v == "" or v is None:
continue
command += [f"-{k}"]
if isinstance(v, str) and "\n" in v:
command += v.split("\n")
elif isinstance(v, list):
command += [str(item) for item in v]
else:
command += [str(v)]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workflow/CommandExecutor.py` around lines 308 - 315, Update the
regular-parameter handling in CommandExecutor so list-valued parameters are
expanded into separate command arguments rather than converted to one string
representation. Preserve the existing skipping of empty or None values,
option-key emission, and newline splitting for string values; convert each list
element to its own argument using the existing value formatting behavior.

Comment on lines +84 to +94
def _merge_bool_params_from_ini(self, tool: str) -> None:
"""Load tool.ini (XML) and merge type=bool parameter paths into session_state."""
ini_path = Path(self.ini_dir, f"{tool}.ini")
if not ini_path.exists():
return
try:
sk = self.bool_pairs_session_key()
if sk not in st.session_state:
st.session_state[sk] = set()
for short in bool_param_paths_from_param_xml_ini(ini_path, tool):
st.session_state[sk].add((tool, short))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Connect ParamXML boolean discovery to rendering and execution.

The discovered boolean set currently terminates in an unused session cache, so automatic checkbox rendering and presence-only CLI serialization still depend on manually maintained lists.

  • src/workflow/ParameterManager.py#L84-L94: expose discovered paths for each tool instance.
  • src/workflow/CommandExecutor.py#L272-L277: merge discovered instance flags with persisted explicit flags.
  • src/workflow/StreamlitUI.py#L938-L948: persist the discovered paths under tool_instance_name.
  • src/workflow/StreamlitUI.py#L1131-L1139: choose checkboxes from boolean metadata and normalize "true"/"false" values.
📍 Affects 3 files
  • src/workflow/ParameterManager.py#L84-L94 (this comment)
  • src/workflow/CommandExecutor.py#L272-L277
  • src/workflow/StreamlitUI.py#L938-L948
  • src/workflow/StreamlitUI.py#L1131-L1139
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workflow/ParameterManager.py` around lines 84 - 94, Connect ParamXML
boolean discovery through all listed sites: in src/workflow/ParameterManager.py
lines 84-94, expose discovered boolean paths per tool instance instead of only
caching them; in src/workflow/CommandExecutor.py lines 272-277, merge those
instance flags with persisted explicit flags; in src/workflow/StreamlitUI.py
lines 938-948, persist discovered paths under tool_instance_name; and in
src/workflow/StreamlitUI.py lines 1131-1139, use boolean metadata to render
checkboxes and normalize "true"/"false" values.

Comment on lines +443 to +485
try:
entries = sorted(
(p for p in cwd.iterdir() if not p.name.startswith(".")),
key=lambda p: (not p.is_dir(), p.name.lower()),
)
except PermissionError:
st.error(f"Permission denied reading `{cwd}`.")
return

def _is_match(p: Path) -> bool:
return any(p.name.endswith(f".{ft}") for ft in file_types)

subdirs = [p for p in entries if p.is_dir() and not _is_match(p)]
bundled = [p for p in entries if p.is_dir() and _is_match(p)]
files = [p for p in entries if p.is_file() and _is_match(p)]

for d in subdirs:
indent, body = st.columns([1, 60], vertical_alignment="center")
if body.button(
f"📂 {d.name}/",
key=f"mounted_dir_{key}_{d.name}",
type="tertiary",
):
st.session_state[sess_cwd_key] = str(d)
st.rerun(scope="fragment")

selectable = bundled + files
selected_paths: List[str] = []
for f in selectable:
cb_key = f"mounted_pick_{key}_{f}"
size_label = ""
if f.is_file():
try:
size_mb = f.stat().st_size / (1024 * 1024)
size_label = f" · {size_mb:.1f} MB"
except OSError:
pass
icon = "🗂️" if f.is_dir() else "📄"
if st.checkbox(
f"{icon} {f.name}{size_label}",
key=cb_key,
):
selected_paths.append(str(f))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject symlink targets outside the mounted root.

A symlinked file under mount_root passes is_file() and is persisted without resolving its target, allowing the browser to reference files outside the configured mount. Resolve every selectable entry and verify it remains beneath mount_root before rendering or saving it.

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 460-460: Unpacked variable indent is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workflow/StreamlitUI.py` around lines 443 - 485, Update the
selectable-entry handling around _is_match and the selectable loop to resolve
each entry and reject any whose resolved path is outside the resolved
mount_root, including symlinked files and directories. Perform this validation
before rendering or persisting entries, while preserving the existing
directory/file filtering and selection behavior for paths that remain beneath
mount_root.

Comment thread src/WorkflowTest.py
Comment on lines +576 to +582
m = re.search(r'\d+', selected_type)
is_supported_type = any(label in selected_type for label in ["tmt", "itraq"])
if not m or not is_supported_type:
st.warning("Please select a supported isobaric type in the IsobaricAnalyzer tab first.")
else:
num_plex = int(m.group())
channels = [f"sample{i+1}" for i in range(num_plex)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Whitelist plex types before allocating channel widgets.

The substring/regex check accepts values such as tmt100000000plex; an invalid imported parameter can generate an unbounded number of widgets and hang the session. Validate against supported types, or enforce a strict channel limit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/WorkflowTest.py` around lines 576 - 582, Update the validation in the
selected_type handling before num_plex and channels are created: replace the
broad substring/regex acceptance with an explicit whitelist of supported plex
types or enforce a strict maximum channel count. Ensure invalid or excessively
large values trigger the existing warning and never reach channel widget
allocation.

Comment thread src/WorkflowTest.py
Comment on lines +612 to +619
# Remove orphaned params from a previously selected larger plex
self.params = self.parameter_manager.get_parameters_from_json()
valid_keys = {f"TMT-group-{ch}" for ch in channels}
orphaned = [k for k in self.params if k.startswith("TMT-group-") and k not in valid_keys]
if orphaned:
for k in orphaned:
del self.params[k]
self.parameter_manager.save_parameters()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Persist orphan removal instead of reloading the deleted values.

self.params is modified, but save_parameters() reloads params.json and merges all prefixed session keys, so the orphaned channels remain or are reintroduced. Remove their session keys and write the cleaned dictionary directly.

Proposed fix
                 if orphaned:
                     for k in orphaned:
                         del self.params[k]
-                    self.parameter_manager.save_parameters()
+                        st.session_state.pop(
+                            f"{self.parameter_manager.param_prefix}{k}",
+                            None,
+                        )
+                    with open(
+                        self.parameter_manager.params_file,
+                        "w",
+                        encoding="utf-8",
+                    ) as params_file:
+                        json.dump(self.params, params_file, indent=4)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Remove orphaned params from a previously selected larger plex
self.params = self.parameter_manager.get_parameters_from_json()
valid_keys = {f"TMT-group-{ch}" for ch in channels}
orphaned = [k for k in self.params if k.startswith("TMT-group-") and k not in valid_keys]
if orphaned:
for k in orphaned:
del self.params[k]
self.parameter_manager.save_parameters()
# Remove orphaned params from a previously selected larger plex
self.params = self.parameter_manager.get_parameters_from_json()
valid_keys = {f"TMT-group-{ch}" for ch in channels}
orphaned = [k for k in self.params if k.startswith("TMT-group-") and k not in valid_keys]
if orphaned:
for k in orphaned:
del self.params[k]
st.session_state.pop(
f"{self.parameter_manager.param_prefix}{k}",
None,
)
with open(
self.parameter_manager.params_file,
"w",
encoding="utf-8",
) as params_file:
json.dump(self.params, params_file, indent=4)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/WorkflowTest.py` around lines 612 - 619, Update the orphan cleanup block
in the workflow test to remove each orphaned channel from both self.params and
the parameter manager’s session-key storage before persistence. Write the
cleaned self.params dictionary directly without reloading or merging the deleted
prefixed keys, ensuring subsequent loads do not reintroduce orphaned TMT-group
entries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants