Skip to content

Commit 949b558

Browse files
committed
Add numpy to pyprophet build dependencies
1 parent 4230976 commit 949b558

7 files changed

Lines changed: 688 additions & 5 deletions

.github/workflows/build-windows-executable-app.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,8 @@ jobs:
245245
run: |
246246
sed -i 's/#import site/import site/' python-${{ env.PYTHON_VERSION }}/python311._pth
247247
248-
- name: Install Cython (required for pyprophet build)
249-
run: .\python-${{ env.PYTHON_VERSION }}\python -m pip install cython --no-warn-script-location
248+
- name: Install build dependencies (required for pyprophet build)
249+
run: .\python-${{ env.PYTHON_VERSION }}\python -m pip install cython numpy --no-warn-script-location
250250

251251
- name: Install Required Packages
252252
run: .\python-${{ env.PYTHON_VERSION }}\python -m pip install --force-reinstall -r requirements.txt --no-warn-script-location

.github/workflows/test-win-exe-w-embed-py.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ jobs:
3838
run: |
3939
cat python-${{ env.PYTHON_VERSION }}/python311._pth
4040
41-
- name: Install Cython (required for pyprophet build)
42-
run: .\python-${{ env.PYTHON_VERSION }}\python -m pip install cython --no-warn-script-location
41+
- name: Install build dependencies (required for pyprophet build)
42+
run: .\python-${{ env.PYTHON_VERSION }}\python -m pip install cython numpy --no-warn-script-location
4343

4444
- name: Install Required Packages
4545
run: .\python-${{ env.PYTHON_VERSION }}\python -m pip install -r requirements.txt --no-warn-script-location

.github/workflows/test-win-exe-w-pyinstaller.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
run: |
2424
python -m venv myenv
2525
call myenv\Scripts\activate.bat
26-
pip install cython
26+
pip install cython numpy
2727
pip install -r requirements.txt
2828
pip install pyinstaller
2929

CLAUDE.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
OpenMS Streamlit Template is a web application framework for building mass spectrometry (MS) analysis workflows using OpenMS/pyOpenMS. It supports both simple pyOpenMS workflows and complex multi-tool pipelines using OpenMS TOPP (The OpenMS Proteomics Pipeline) tools.
8+
9+
## Common Commands
10+
11+
```bash
12+
# Run the app locally
13+
streamlit run app.py
14+
15+
# Run tests
16+
python -m pytest test_gui.py tests/
17+
18+
# Build and run with Docker (includes OpenMS TOPP tools)
19+
docker-compose up -d --build
20+
21+
# Clean up old workspaces (removes workspaces older than 7 days)
22+
python clean-up-workspaces.py
23+
```
24+
25+
Note: Local runs have limited functionality. Features requiring OpenMS TOPP tools only work out of the box with Docker or when OpenMS Command Line Tools are installed separately.
26+
27+
## Architecture
28+
29+
### Core Framework (`src/workflow/`)
30+
31+
The workflow system is built around `WorkflowManager` as the base class with these components:
32+
33+
- **WorkflowManager**: Base class that orchestrates file management, parameters, command execution, and UI. Custom workflows inherit from this and override `upload()`, `configure()`, `execution()`, and `results()` methods.
34+
- **FileManager**: Handles input/output file organization in `workflow_dir/input-files/{key}/` and `workflow_dir/results/`
35+
- **ParameterManager**: Manages TOPP tool parameters (XML .ini files) and JSON parameters
36+
- **CommandExecutor**: Runs external commands (TOPP tools) with threading for parallelization
37+
- **StreamlitUI**: Provides Streamlit widgets including `upload_widget()` and `input_TOPP()` for TOPP parameter UIs
38+
- **Logger**: Multi-level logging (minimal, commands, all) to `workflow_dir/logs/`
39+
40+
### Page Structure
41+
42+
- **Entry point**: `app.py` defines multi-page navigation using `st.navigation()`
43+
- **Pages**: Each file in `content/` is a page that calls `page_setup()` from `src/common/common.py`, then instantiates a workflow class
44+
- **Utility pages** (`content/`): digest.py, fragmentation.py, isotope_pattern_generator.py, peptide_mz_calculator.py provide standalone analysis tools
45+
46+
### Workflow Data Flow
47+
48+
1. `page_setup()` initializes workspace and loads parameters from `workspace/params.json`
49+
2. Workflow class (e.g., `WorkflowTest`) inherits from `WorkflowManager`
50+
3. Each page calls the appropriate method: `show_file_upload_section()`, `show_parameter_section()`, `show_execution_section()`, `show_results_section()`
51+
4. Workflow execution runs in a multiprocessing.Process to avoid blocking Streamlit UI updates
52+
53+
### Key Patterns
54+
55+
- **Workspace isolation**: Each user session gets a unique workspace directory for files and parameters
56+
- **Streamlit fragments**: Use `@st.fragment` decorator for interactive UI updates without full page reloads
57+
- **TOPP tool execution**: `executor.run_topp("ToolName", {inputs/outputs}, {extra_params})` handles parameter files and command construction
58+
59+
## Configuration Files
60+
61+
- `settings.json`: App name, version, analytics, workspace settings
62+
- `default-parameters.json`: Workflow default parameters
63+
- `.streamlit/config.toml`: Streamlit server config (port 8501, 1000MB upload limit)
64+
65+
## Creating New Workflows
66+
67+
Inherit from `WorkflowManager` and implement the four core methods:
68+
69+
```python
70+
from src.workflow.WorkflowManager import WorkflowManager
71+
72+
class MyWorkflow(WorkflowManager):
73+
def __init__(self):
74+
super().__init__("My Workflow", st.session_state["workspace"])
75+
76+
def upload(self):
77+
self.ui.upload_widget(key="input-files", name="Input", file_types="mzML", fallback=[...])
78+
79+
def configure(self):
80+
self.ui.input_TOPP("ToolName", custom_defaults={...}, include_parameters=[...])
81+
82+
def execution(self):
83+
self.executor.run_topp("ToolName", {"in": [...], "out": [...]}, {...})
84+
85+
def results(self):
86+
st.dataframe(pd.read_csv(...))
87+
```
88+
89+
## Key Dependencies
90+
91+
- **pyOpenMS 3.5.0+**: Python bindings for OpenMS
92+
- **Streamlit 1.43.0**: Web UI framework
93+
- **Plotly + streamlit_plotly_events**: Interactive visualizations
94+
- **OpenMS TOPP tools**: External command-line tools (Docker or separate install required)

openms-insight-bug-report.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Bug Report: Heatmap-to-Table Row Selection Not Working
2+
3+
## Issue for openms-insight Repository
4+
5+
### Title
6+
Table component does not highlight/scroll to row when Heatmap selection changes
7+
8+
### Description
9+
10+
When using a shared `StateManager` between `Heatmap` and `Table` components with matching `interactivity` identifiers, clicking a point in the Heatmap does not scroll to or highlight the corresponding row in the Table.
11+
12+
### Expected Behavior
13+
14+
1. User clicks a point in the Heatmap
15+
2. Heatmap sets selection via `state_manager.set_selection("identification", id_idx_value)`
16+
3. Table's Vue component (`TabulatorTable.vue`) detects the selection change
17+
4. Table scrolls to the row with matching `id_idx` and highlights it
18+
5. **All rows remain visible** (no filtering)
19+
20+
### Actual Behavior
21+
22+
Clicking a Heatmap point does NOT cause the Table to scroll or highlight the corresponding row. The selection appears to be set (SequenceView and LinePlot update correctly), but the Table does not respond.
23+
24+
### Reproduction Steps
25+
26+
```python
27+
from openms_insight import Table, Heatmap, StateManager
28+
29+
# Initialize components with shared identifier
30+
table = Table(
31+
cache_id="my_table",
32+
data=df.lazy(),
33+
cache_path=str(cache_dir),
34+
interactivity={"identification": "id_idx"}, # Shared identifier
35+
column_definitions=[...],
36+
index_field="id_idx",
37+
)
38+
39+
heatmap = Heatmap(
40+
cache_id="my_heatmap",
41+
data=df.lazy(),
42+
cache_path=str(cache_dir),
43+
x_column="rt",
44+
y_column="mz",
45+
intensity_column="score",
46+
interactivity={"identification": "id_idx"}, # Same identifier
47+
)
48+
49+
# Render with shared state
50+
state_manager = StateManager()
51+
heatmap(state_manager=state_manager, height=350)
52+
table(state_manager=state_manager, height=533)
53+
```
54+
55+
### Environment
56+
57+
- **openms-insight version**: >=0.1.10
58+
- **Streamlit version**: 1.43.0
59+
- **Browser**: Chrome/Firefox (tested both)
60+
- **Python**: 3.12
61+
62+
### Root Cause Analysis (Deep Investigation)
63+
64+
After thorough code analysis, the root cause has been identified:
65+
66+
#### Primary Issue: Iterator Order Bug in `syncSelectionFromStore()`
67+
68+
**Location:** `TabulatorTable.vue` lines 1005-1034
69+
70+
The `syncSelectionFromStore()` function iterates through interactivity entries and uses the **first identifier with a non-null value**, then breaks:
71+
72+
```javascript
73+
for (const [identifier, column] of Object.entries(interactivity)) {
74+
const selectedValue = this.selectionStore.$state[identifier]
75+
if (selectedValue !== undefined && selectedValue !== null) {
76+
// ... try to find row
77+
break // STOPS after first non-null identifier
78+
}
79+
}
80+
```
81+
82+
**The problem:** When Table has multiple interactivity mappings (common case):
83+
```python
84+
interactivity={"file": "file_index", "spectrum": "scan_id", "identification": "id_idx"}
85+
```
86+
87+
And Heatmap only sets:
88+
```python
89+
interactivity={"identification": "id_idx"}
90+
```
91+
92+
**Failure scenario:**
93+
1. User previously selected a file → "file" has value (e.g., 0)
94+
2. User clicks Heatmap → sets "identification" = 50
95+
3. `syncSelectionFromStore()` iterates
96+
4. "file" has value 0 (non-null) → enters if block
97+
5. Searches for row where `file_index === 0` (wrong identifier!)
98+
6. **Breaks** - never checks "identification"
99+
100+
The function doesn't detect **which identifier changed**, it just uses the first one with a value.
101+
102+
#### Secondary Issue: Two-Render Cycle Timing
103+
104+
**Flow for external selection (Heatmap → Table):**
105+
106+
1. **First render:** Python cache miss → sends `dataChanged: false`
107+
- Vue selection store IS updated
108+
- `syncSelectionFromStore()` fires with stale `preparedTableData`
109+
- Row may not be found → stored as `pendingSelection`
110+
111+
2. **Second render:** Python cache hit → sends `dataChanged: true`
112+
- Selection store already has correct values (no change detected)
113+
- Watcher **doesn't fire** (values unchanged)
114+
- Navigation hints are in the data, but `navigateToPage` watcher may not trigger
115+
116+
This two-phase update cycle can cause the selection highlight to be missed.
117+
118+
### Suggested Fixes
119+
120+
#### Fix 1: Track Which Identifier Changed
121+
122+
Modify `syncSelectionFromStore()` to compare current vs previous selection state and prioritize the changed identifier:
123+
124+
```javascript
125+
syncSelectionFromStore(): void {
126+
const interactivity = this.args.interactivity || {}
127+
128+
// Find which identifier actually changed
129+
for (const [identifier, column] of Object.entries(interactivity)) {
130+
const selectedValue = this.selectionStore.$state[identifier]
131+
const previousValue = this.lastSyncedSelections?.[identifier]
132+
133+
// Prioritize changed identifiers
134+
if (selectedValue !== previousValue && selectedValue != null) {
135+
// Handle this identifier first
136+
this.selectRowByColumn(column, selectedValue)
137+
break
138+
}
139+
}
140+
141+
// Store current state for next comparison
142+
this.lastSyncedSelections = {...this.selectionStore.$state}
143+
}
144+
```
145+
146+
#### Fix 2: Ensure Selection Sync After Data Update
147+
148+
In the `currentDataHash` watcher, explicitly call `syncSelectionFromStore()` after data updates:
149+
150+
```javascript
151+
currentDataHash: {
152+
handler(newHash: string, oldHash: string) {
153+
// ... existing logic ...
154+
155+
// After data update, re-sync selection
156+
this.$nextTick(() => {
157+
this.syncSelectionFromStore()
158+
})
159+
}
160+
}
161+
```
162+
163+
### Verified Working Components
164+
165+
- **SequenceView**: Uses `filters` parameter → actively filters data based on selection (different mechanism)
166+
- **LinePlot**: Linked via SequenceView → inherits working filter-based selection
167+
- **Heatmap**: Correctly sets selection → Python StateManager updates correctly
168+
169+
### Related Code Paths
170+
171+
| File | Lines | Description |
172+
|------|-------|-------------|
173+
| `TabulatorTable.vue` | 989-1038 | `syncSelectionFromStore()` - main issue location |
174+
| `TabulatorTable.vue` | 370-376 | Selection store watcher |
175+
| `TabulatorTable.vue` | 378-410 | `navigateToPage` watcher |
176+
| `table.py` | 711-856 | Python-side navigation hint generation |
177+
| `bridge.py` | 456-797 | Render cycle with two-phase cache handling |
178+
| `streamlit-data.ts` | 39-203 | Vue data store update logic |
179+
180+
### Workaround
181+
182+
Currently, using `filters` instead of `interactivity` works but **filters** data instead of highlighting:
183+
184+
```python
185+
table = Table(
186+
...
187+
filters={"identification": "id_idx"}, # Filters, not highlights
188+
)
189+
```

openms-insight-feature-request.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Feature Request: Heatmap Rendering Order Control
2+
3+
## Summary
4+
5+
Add parameters to control the rendering order (z-order) of points in the Heatmap component, allowing users to specify which points should be drawn on top when overlapping.
6+
7+
## Use Case
8+
9+
When visualizing peptide-spectrum match (PSM) identification results, we display a heatmap with:
10+
- **x-axis**: Retention time (RT)
11+
- **y-axis**: Mass-to-charge ratio (m/z)
12+
- **intensity**: Score (e-value, PEP, or q-value)
13+
14+
For these score types, **lower values indicate better identifications**. Currently, when points overlap, there's no control over which points are rendered on top. Ideally, we want the best identifications (lowest scores) to be visible on top of worse ones.
15+
16+
## Current Workaround
17+
18+
We can reverse the colorscale using `colorscale="Portland_r"` to make low scores appear with high-intensity colors, but this doesn't address the overlapping point visibility issue.
19+
20+
## Proposed Solution
21+
22+
Add one or more of the following parameters to the `Heatmap` component:
23+
24+
### Option 1: `sort_column` and `sort_ascending`
25+
26+
```python
27+
Heatmap(
28+
data=df,
29+
x_column="rt",
30+
y_column="mz",
31+
intensity_column="score",
32+
sort_column="score", # Column to sort by for rendering order
33+
sort_ascending=False, # False = high values rendered first (low on top)
34+
)
35+
```
36+
37+
### Option 2: `render_order`
38+
39+
```python
40+
Heatmap(
41+
data=df,
42+
x_column="rt",
43+
y_column="mz",
44+
intensity_column="score",
45+
render_order="intensity_asc", # Options: "intensity_asc", "intensity_desc", "data_order"
46+
)
47+
```
48+
49+
### Option 3: `top_layer`
50+
51+
```python
52+
Heatmap(
53+
data=df,
54+
x_column="rt",
55+
y_column="mz",
56+
intensity_column="score",
57+
top_layer="low_intensity", # Options: "low_intensity", "high_intensity"
58+
)
59+
```
60+
61+
## Additional Context
62+
63+
- This is particularly important for proteomics workflows where score distributions often have many overlapping points
64+
- The feature would complement the existing `colorscale` parameter for full control over visual representation
65+
- Consider how this interacts with the multi-resolution downsampling - perhaps the sort should be applied before binning/downsampling
66+
67+
## Environment
68+
69+
- openms-insight version: >= 0.1.10
70+
- Application: quantms-web (DDA-LFQ) built on streamlit-template

0 commit comments

Comments
 (0)