openms-insight 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openms_insight/__init__.py +32 -0
- openms_insight/components/__init__.py +11 -0
- openms_insight/components/heatmap.py +823 -0
- openms_insight/components/lineplot.py +492 -0
- openms_insight/components/sequenceview.py +384 -0
- openms_insight/components/table.py +400 -0
- openms_insight/core/__init__.py +14 -0
- openms_insight/core/base.py +413 -0
- openms_insight/core/cache.py +39 -0
- openms_insight/core/registry.py +82 -0
- openms_insight/core/state.py +215 -0
- openms_insight/js-component/dist/assets/index.css +5 -0
- openms_insight/js-component/dist/assets/index.js +4220 -0
- openms_insight/js-component/dist/assets/materialdesignicons-webfont.eot +0 -0
- openms_insight/js-component/dist/assets/materialdesignicons-webfont.ttf +0 -0
- openms_insight/js-component/dist/assets/materialdesignicons-webfont.woff +0 -0
- openms_insight/js-component/dist/assets/materialdesignicons-webfont.woff2 +0 -0
- openms_insight/js-component/dist/index.html +14 -0
- openms_insight/preprocessing/__init__.py +22 -0
- openms_insight/preprocessing/compression.py +338 -0
- openms_insight/preprocessing/filtering.py +316 -0
- openms_insight/rendering/__init__.py +8 -0
- openms_insight/rendering/bridge.py +312 -0
- openms_insight-0.1.0.dist-info/METADATA +256 -0
- openms_insight-0.1.0.dist-info/RECORD +27 -0
- openms_insight-0.1.0.dist-info/WHEEL +4 -0
- openms_insight-0.1.0.dist-info/licenses/LICENSE +29 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""Bridge between Python components and Vue frontend."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import polars as pl
|
|
10
|
+
import streamlit as st
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _make_hashable(value: Any) -> Any:
|
|
14
|
+
"""
|
|
15
|
+
Convert a value to a hashable form for use in cache keys.
|
|
16
|
+
|
|
17
|
+
Handles dicts and lists by converting to JSON strings.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
value: Any value from state
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
A hashable version of the value
|
|
24
|
+
"""
|
|
25
|
+
if isinstance(value, dict):
|
|
26
|
+
# Convert dict to sorted JSON string for consistent hashing
|
|
27
|
+
return json.dumps(value, sort_keys=True)
|
|
28
|
+
if isinstance(value, list):
|
|
29
|
+
return json.dumps(value)
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from ..core.base import BaseComponent
|
|
34
|
+
from ..core.state import StateManager
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Cache the Vue component function
|
|
38
|
+
_vue_component_func = None
|
|
39
|
+
|
|
40
|
+
# Session state key for Vue's echoed hash (what Vue currently has)
|
|
41
|
+
# Used for bidirectional hash confirmation - we only send data when
|
|
42
|
+
# Vue's echoed hash doesn't match the current data hash
|
|
43
|
+
_VUE_ECHOED_HASH_KEY = "_svc_vue_echoed_hashes"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@st.cache_data(max_entries=10, show_spinner=False)
|
|
48
|
+
def _cached_prepare_vue_data(
|
|
49
|
+
_component: 'BaseComponent',
|
|
50
|
+
component_id: str,
|
|
51
|
+
filter_state_hashable: Tuple[Tuple[str, Any], ...],
|
|
52
|
+
_data_id: int,
|
|
53
|
+
_state_dict: Dict[str, Any],
|
|
54
|
+
) -> Tuple[Dict[str, Any], str]:
|
|
55
|
+
"""
|
|
56
|
+
Cached wrapper for _prepare_vue_data.
|
|
57
|
+
|
|
58
|
+
Cache key is based on:
|
|
59
|
+
- component_id: unique key for this component instance
|
|
60
|
+
- filter_state_hashable: hashable version of state values (for cache key only)
|
|
61
|
+
|
|
62
|
+
The _component, _data_id, and _state_dict parameters are prefixed with underscore
|
|
63
|
+
so they are not hashed (component instances are not hashable, and state_dict
|
|
64
|
+
may contain unhashable values like dicts).
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
_component: The component to prepare data for (not hashed)
|
|
68
|
+
component_id: Unique identifier for this component
|
|
69
|
+
filter_state_hashable: Tuple of (identifier, hashable_value) for cache key
|
|
70
|
+
_data_id: id() of the raw data object (not used in cache key)
|
|
71
|
+
_state_dict: Original state dict with actual values (not hashed)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Tuple of (vue_data dict, data_hash string)
|
|
75
|
+
"""
|
|
76
|
+
# Use the original state dict (not the hashable version)
|
|
77
|
+
vue_data = _component._prepare_vue_data(_state_dict)
|
|
78
|
+
|
|
79
|
+
# Compute hash before any conversion
|
|
80
|
+
data_hash = _hash_data(vue_data)
|
|
81
|
+
|
|
82
|
+
return vue_data, data_hash
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_vue_component_function():
|
|
86
|
+
"""
|
|
87
|
+
Get the Streamlit component function for the Vue frontend.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
The declared Streamlit component function
|
|
91
|
+
"""
|
|
92
|
+
global _vue_component_func
|
|
93
|
+
|
|
94
|
+
if _vue_component_func is None:
|
|
95
|
+
import streamlit.components.v1 as st_components
|
|
96
|
+
|
|
97
|
+
# Check for development mode
|
|
98
|
+
dev_mode = os.environ.get('SVC_DEV_MODE', 'false').lower() == 'true'
|
|
99
|
+
|
|
100
|
+
if dev_mode:
|
|
101
|
+
# Development mode: connect to Vite dev server
|
|
102
|
+
dev_url = os.environ.get('SVC_DEV_URL', 'http://localhost:5173')
|
|
103
|
+
_vue_component_func = st_components.declare_component(
|
|
104
|
+
"streamlit_vue_component",
|
|
105
|
+
url=dev_url,
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
# Production mode: use built component
|
|
109
|
+
parent_dir = os.path.dirname(os.path.abspath(__file__))
|
|
110
|
+
build_dir = os.path.join(parent_dir, '..', 'js-component', 'dist')
|
|
111
|
+
|
|
112
|
+
if not os.path.exists(build_dir):
|
|
113
|
+
raise RuntimeError(
|
|
114
|
+
f"Vue component build not found at {build_dir}. "
|
|
115
|
+
"Please build the Vue component or set SVC_DEV_MODE=true "
|
|
116
|
+
"for development."
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
_vue_component_func = st_components.declare_component(
|
|
120
|
+
"streamlit_vue_component",
|
|
121
|
+
path=build_dir,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return _vue_component_func
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def render_component(
|
|
128
|
+
component: 'BaseComponent',
|
|
129
|
+
state_manager: 'StateManager',
|
|
130
|
+
key: Optional[str] = None,
|
|
131
|
+
height: Optional[int] = None,
|
|
132
|
+
) -> Any:
|
|
133
|
+
"""
|
|
134
|
+
Render a component in Streamlit.
|
|
135
|
+
|
|
136
|
+
This function:
|
|
137
|
+
1. Gets current state from StateManager
|
|
138
|
+
2. Calls component._prepare_vue_data() to get filtered data (cached!)
|
|
139
|
+
3. Computes hash for change detection
|
|
140
|
+
4. Only sends data if hash changed from last render (optimization)
|
|
141
|
+
5. Calls the Vue component with data payload
|
|
142
|
+
6. Updates StateManager from Vue response
|
|
143
|
+
7. Triggers st.rerun() if state changed
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
component: The component to render
|
|
147
|
+
state_manager: StateManager for cross-component state
|
|
148
|
+
key: Optional unique key for the Streamlit component
|
|
149
|
+
height: Optional height in pixels
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
The value returned by the Vue component
|
|
153
|
+
"""
|
|
154
|
+
# Get current state
|
|
155
|
+
state = state_manager.get_state_for_vue()
|
|
156
|
+
|
|
157
|
+
# Generate unique key if not provided (needed for cache)
|
|
158
|
+
# Use cache_id instead of id(component) since components are recreated each rerun
|
|
159
|
+
if key is None:
|
|
160
|
+
key = f"svc_{component._cache_id}_{hash(str(component._interactivity))}"
|
|
161
|
+
|
|
162
|
+
# Extract state keys that affect this component's data for cache key
|
|
163
|
+
# This includes filters and any additional dependencies (e.g., zoom for heatmaps)
|
|
164
|
+
# Uses get_state_dependencies() which can be overridden by subclasses
|
|
165
|
+
state_keys = set(component.get_state_dependencies())
|
|
166
|
+
|
|
167
|
+
# Build hashable version for cache key (converts dicts/lists to JSON strings)
|
|
168
|
+
filter_state_hashable = tuple(sorted(
|
|
169
|
+
(k, _make_hashable(state.get(k))) for k in state_keys
|
|
170
|
+
))
|
|
171
|
+
|
|
172
|
+
# Build original state dict for passing to _prepare_vue_data
|
|
173
|
+
# (contains actual values, not JSON strings)
|
|
174
|
+
relevant_state = {k: state.get(k) for k in state_keys}
|
|
175
|
+
|
|
176
|
+
# Build component ID for cache (includes type to avoid collisions)
|
|
177
|
+
component_type = component._get_vue_component_name()
|
|
178
|
+
component_id = f"{component_type}:{key}"
|
|
179
|
+
|
|
180
|
+
# Get data identity - cache invalidates if raw data object changes
|
|
181
|
+
data_id = id(component._raw_data)
|
|
182
|
+
|
|
183
|
+
# Get component data using cached function
|
|
184
|
+
# Cache key: (component_id, filter_state_hashable, data_id)
|
|
185
|
+
# - Filterless components: filter_state=() always → always cache hit
|
|
186
|
+
# - Filtered components: cache hit when filter values unchanged
|
|
187
|
+
vue_data, data_hash = _cached_prepare_vue_data(
|
|
188
|
+
component, component_id, filter_state_hashable, data_id, relevant_state
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
component_args = component._get_component_args()
|
|
192
|
+
|
|
193
|
+
# Initialize hash cache in session state if needed
|
|
194
|
+
if _VUE_ECHOED_HASH_KEY not in st.session_state:
|
|
195
|
+
st.session_state[_VUE_ECHOED_HASH_KEY] = {}
|
|
196
|
+
|
|
197
|
+
# Hash tracking key includes filter state so different filter values have separate tracking
|
|
198
|
+
# This ensures data is re-sent when filters change (e.g., different spectrum selected)
|
|
199
|
+
hash_tracking_key = f"{key}:{filter_state_hashable}"
|
|
200
|
+
|
|
201
|
+
# Get Vue's last-echoed hash for this component + filter state
|
|
202
|
+
# This is what Vue reported having in its last response for this exact filter state
|
|
203
|
+
vue_echoed_hash = st.session_state[_VUE_ECHOED_HASH_KEY].get(hash_tracking_key)
|
|
204
|
+
|
|
205
|
+
# Send data if Vue's hash doesn't match current hash
|
|
206
|
+
# This handles: first render, data change, browser refresh, Vue hot reload
|
|
207
|
+
# Vue echoes null/None if it has no data, so mismatch triggers send
|
|
208
|
+
# IMPORTANT: Also send data if vue_echoed_hash is None - this means Vue
|
|
209
|
+
# hasn't confirmed receipt yet (e.g., after page navigation destroys Vue component)
|
|
210
|
+
data_changed = (vue_echoed_hash is None) or (vue_echoed_hash != data_hash)
|
|
211
|
+
|
|
212
|
+
# Only include full data if hash changed
|
|
213
|
+
if data_changed:
|
|
214
|
+
# Convert any non-pandas data to pandas for Arrow serialization
|
|
215
|
+
# pandas DataFrames are passed through (already optimal for Arrow)
|
|
216
|
+
# Also filter out internal keys (starting with _)
|
|
217
|
+
converted_data = {}
|
|
218
|
+
for data_key, value in vue_data.items():
|
|
219
|
+
if data_key.startswith('_'):
|
|
220
|
+
# Skip metadata keys like _hash, _plotConfig
|
|
221
|
+
continue
|
|
222
|
+
if isinstance(value, pl.LazyFrame):
|
|
223
|
+
converted_data[data_key] = value.collect().to_pandas()
|
|
224
|
+
elif isinstance(value, pl.DataFrame):
|
|
225
|
+
converted_data[data_key] = value.to_pandas()
|
|
226
|
+
else:
|
|
227
|
+
converted_data[data_key] = value
|
|
228
|
+
# pandas DataFrames pass through unchanged (optimal for Arrow)
|
|
229
|
+
data_payload = {
|
|
230
|
+
**converted_data,
|
|
231
|
+
'selection_store': state,
|
|
232
|
+
'hash': data_hash,
|
|
233
|
+
'dataChanged': True,
|
|
234
|
+
}
|
|
235
|
+
# Note: We don't pre-set the hash here anymore. We trust Vue's echo
|
|
236
|
+
# at the end of the render cycle. This ensures we detect when Vue
|
|
237
|
+
# loses its data (e.g., page navigation) and needs it resent.
|
|
238
|
+
else:
|
|
239
|
+
# Data unchanged - only send hash and state, Vue will use cached data
|
|
240
|
+
data_payload = {
|
|
241
|
+
'selection_store': state,
|
|
242
|
+
'hash': data_hash,
|
|
243
|
+
'dataChanged': False,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# Add height to component args if specified
|
|
247
|
+
if height is not None:
|
|
248
|
+
component_args['height'] = height
|
|
249
|
+
|
|
250
|
+
# Component layout: [[{componentArgs: {...}}]]
|
|
251
|
+
components = [[{'componentArgs': component_args}]]
|
|
252
|
+
|
|
253
|
+
# Call Vue component
|
|
254
|
+
vue_func = get_vue_component_function()
|
|
255
|
+
|
|
256
|
+
kwargs = {
|
|
257
|
+
'components': components,
|
|
258
|
+
'key': key,
|
|
259
|
+
**data_payload,
|
|
260
|
+
}
|
|
261
|
+
if height is not None:
|
|
262
|
+
kwargs['height'] = height
|
|
263
|
+
|
|
264
|
+
result = vue_func(**kwargs)
|
|
265
|
+
|
|
266
|
+
# Update state from Vue response
|
|
267
|
+
if result is not None:
|
|
268
|
+
# Store Vue's echoed hash for next render comparison
|
|
269
|
+
# ALWAYS update from Vue's echo - if Vue lost its data (page navigation),
|
|
270
|
+
# it echoes None, and we need to know that to resend data next time
|
|
271
|
+
vue_hash = result.get('_vueDataHash')
|
|
272
|
+
st.session_state[_VUE_ECHOED_HASH_KEY][hash_tracking_key] = vue_hash
|
|
273
|
+
|
|
274
|
+
# Update state and rerun if state changed
|
|
275
|
+
if state_manager.update_from_vue(result):
|
|
276
|
+
st.rerun()
|
|
277
|
+
|
|
278
|
+
return result
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _hash_data(data: Dict[str, Any]) -> str:
|
|
282
|
+
"""
|
|
283
|
+
Compute hash of data payload for change detection.
|
|
284
|
+
|
|
285
|
+
Uses efficient hashing based on shape and samples for DataFrames,
|
|
286
|
+
avoiding full data serialization.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
data: The data dict to hash
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
SHA256 hash string
|
|
293
|
+
"""
|
|
294
|
+
from ..preprocessing.filtering import compute_dataframe_hash
|
|
295
|
+
|
|
296
|
+
hash_parts = []
|
|
297
|
+
for key, value in sorted(data.items()):
|
|
298
|
+
if key.startswith('_'):
|
|
299
|
+
continue # Skip metadata
|
|
300
|
+
if isinstance(value, pd.DataFrame):
|
|
301
|
+
# Efficient hash for DataFrames
|
|
302
|
+
df_polars = pl.from_pandas(value)
|
|
303
|
+
hash_parts.append(f"{key}:{compute_dataframe_hash(df_polars)}")
|
|
304
|
+
elif isinstance(value, pl.DataFrame):
|
|
305
|
+
hash_parts.append(f"{key}:{compute_dataframe_hash(value)}")
|
|
306
|
+
elif isinstance(value, (list, dict)):
|
|
307
|
+
# For small data, use string repr
|
|
308
|
+
hash_parts.append(f"{key}:{hash(str(value)[:1000])}")
|
|
309
|
+
else:
|
|
310
|
+
hash_parts.append(f"{key}:{hash(str(value))}")
|
|
311
|
+
|
|
312
|
+
return hashlib.sha256("|".join(hash_parts).encode()).hexdigest()
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openms-insight
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive visualization components for mass spectrometry data in Streamlit
|
|
5
|
+
Project-URL: Homepage, https://github.com/t0mdavid-m/OpenMS-Insight
|
|
6
|
+
Project-URL: Documentation, https://github.com/t0mdavid-m/OpenMS-Insight#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/t0mdavid-m/OpenMS-Insight
|
|
8
|
+
Project-URL: Issues, https://github.com/t0mdavid-m/OpenMS-Insight/issues
|
|
9
|
+
Author: Kohlbacher Lab
|
|
10
|
+
License-Expression: BSD-3-Clause
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: mass-spectrometry,openms,plotly,proteomics,streamlit,tabulator,visualization,vue
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: pandas>=1.5.0
|
|
26
|
+
Requires-Dist: polars>=0.19.0
|
|
27
|
+
Requires-Dist: streamlit>=1.20.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# OpenMS-Insight
|
|
37
|
+
|
|
38
|
+
[](https://badge.fury.io/py/openms-insight)
|
|
39
|
+
[](https://www.python.org/downloads/)
|
|
40
|
+
|
|
41
|
+
Interactive visualization components for mass spectrometry data in Streamlit, backed by Vue.js.
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
- **Cross-component selection linking** via shared identifiers
|
|
46
|
+
- **Polars LazyFrame support** for efficient data handling
|
|
47
|
+
- **Automatic disk caching** with config-based invalidation
|
|
48
|
+
- **Table component** (Tabulator.js) with filtering, sorting, go-to, pagination
|
|
49
|
+
- **Line plot component** (Plotly.js) with highlighting, annotations, zoom
|
|
50
|
+
- **Heatmap component** (Plotly scattergl) with multi-resolution downsampling
|
|
51
|
+
- **Sequence view component** for peptide/protein visualization
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install openms-insight
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import streamlit as st
|
|
63
|
+
import polars as pl
|
|
64
|
+
from openms_insight import Table, LinePlot, StateManager
|
|
65
|
+
|
|
66
|
+
# Create state manager for cross-component linking
|
|
67
|
+
state_manager = StateManager()
|
|
68
|
+
|
|
69
|
+
# Create a table - clicking a row sets the 'item' selection
|
|
70
|
+
table = Table(
|
|
71
|
+
cache_id="items_table",
|
|
72
|
+
data=pl.scan_parquet("items.parquet"),
|
|
73
|
+
interactivity={'item': 'item_id'},
|
|
74
|
+
column_definitions=[
|
|
75
|
+
{'field': 'item_id', 'title': 'ID', 'sorter': 'number'},
|
|
76
|
+
{'field': 'name', 'title': 'Name'},
|
|
77
|
+
],
|
|
78
|
+
)
|
|
79
|
+
table(state_manager=state_manager)
|
|
80
|
+
|
|
81
|
+
# Create a linked plot - filters by the selected 'item'
|
|
82
|
+
plot = LinePlot(
|
|
83
|
+
cache_id="values_plot",
|
|
84
|
+
data=pl.scan_parquet("values.parquet"),
|
|
85
|
+
filters={'item': 'item_id'},
|
|
86
|
+
x_column='x',
|
|
87
|
+
y_column='y',
|
|
88
|
+
)
|
|
89
|
+
plot(state_manager=state_manager)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Cross-Component Linking
|
|
93
|
+
|
|
94
|
+
Components communicate through **identifiers** using two mechanisms:
|
|
95
|
+
|
|
96
|
+
- **`filters`**: INPUT - filter this component's data by the selection
|
|
97
|
+
- **`interactivity`**: OUTPUT - set a selection when user clicks
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# Master table: no filters, sets 'spectrum' on click
|
|
101
|
+
master = Table(
|
|
102
|
+
cache_id="spectra",
|
|
103
|
+
data=spectra_data,
|
|
104
|
+
interactivity={'spectrum': 'scan_id'}, # Click -> sets spectrum=scan_id
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Detail table: filters by 'spectrum', sets 'peak' on click
|
|
108
|
+
detail = Table(
|
|
109
|
+
cache_id="peaks",
|
|
110
|
+
data=peaks_data,
|
|
111
|
+
filters={'spectrum': 'scan_id'}, # Filters where scan_id = selected spectrum
|
|
112
|
+
interactivity={'peak': 'peak_id'}, # Click -> sets peak=peak_id
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Plot: filters by 'spectrum', highlights selected 'peak'
|
|
116
|
+
plot = LinePlot(
|
|
117
|
+
cache_id="plot",
|
|
118
|
+
data=peaks_data,
|
|
119
|
+
filters={'spectrum': 'scan_id'},
|
|
120
|
+
interactivity={'peak': 'peak_id'},
|
|
121
|
+
x_column='mass',
|
|
122
|
+
y_column='intensity',
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Components
|
|
129
|
+
|
|
130
|
+
### Table
|
|
131
|
+
|
|
132
|
+
Interactive table using Tabulator.js with filtering dialogs, sorting, pagination, and CSV export.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
Table(
|
|
136
|
+
cache_id="spectra_table",
|
|
137
|
+
data=pl.scan_parquet("spectra.parquet"),
|
|
138
|
+
interactivity={'spectrum': 'scan_id'},
|
|
139
|
+
column_definitions=[
|
|
140
|
+
{'field': 'scan_id', 'title': 'Scan', 'sorter': 'number'},
|
|
141
|
+
{'field': 'rt', 'title': 'RT (min)', 'sorter': 'number', 'hozAlign': 'right'},
|
|
142
|
+
{'field': 'precursor_mz', 'title': 'm/z', 'sorter': 'number'},
|
|
143
|
+
],
|
|
144
|
+
index_field='scan_id',
|
|
145
|
+
go_to_fields=['scan_id'],
|
|
146
|
+
default_row=0,
|
|
147
|
+
pagination=True,
|
|
148
|
+
page_size=50,
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### LinePlot
|
|
153
|
+
|
|
154
|
+
Stick-style line plot using Plotly.js for mass spectra visualization.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
LinePlot(
|
|
158
|
+
cache_id="spectrum_plot",
|
|
159
|
+
data=pl.scan_parquet("peaks.parquet"),
|
|
160
|
+
filters={'spectrum': 'scan_id'},
|
|
161
|
+
interactivity={'peak': 'peak_id'},
|
|
162
|
+
x_column='mass',
|
|
163
|
+
y_column='intensity',
|
|
164
|
+
highlight_column='is_annotated',
|
|
165
|
+
annotation_column='ion_label',
|
|
166
|
+
title="MS/MS Spectrum",
|
|
167
|
+
x_label="m/z",
|
|
168
|
+
y_label="Intensity",
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Heatmap
|
|
173
|
+
|
|
174
|
+
2D scatter heatmap using Plotly scattergl with multi-resolution downsampling for large datasets (millions of points).
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
Heatmap(
|
|
178
|
+
cache_id="peaks_heatmap",
|
|
179
|
+
data=pl.scan_parquet("all_peaks.parquet"),
|
|
180
|
+
x_column='retention_time',
|
|
181
|
+
y_column='mass',
|
|
182
|
+
intensity_column='intensity',
|
|
183
|
+
interactivity={'spectrum': 'scan_id', 'peak': 'peak_id'},
|
|
184
|
+
min_points=30000,
|
|
185
|
+
title="Peak Map",
|
|
186
|
+
x_label="Retention Time (min)",
|
|
187
|
+
y_label="m/z",
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### SequenceView
|
|
192
|
+
|
|
193
|
+
Peptide/protein sequence visualization with fragment ion matching.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
SequenceView(
|
|
197
|
+
cache_id="peptide_view",
|
|
198
|
+
sequence="PEPTIDEK",
|
|
199
|
+
observed_masses=[147.1, 244.2, 359.3, 456.4],
|
|
200
|
+
peak_ids=[0, 1, 2, 3],
|
|
201
|
+
precursor_mass=944.5,
|
|
202
|
+
interactivity={'peak': 'peak_id'},
|
|
203
|
+
title="Fragment Coverage",
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Shared Component Arguments
|
|
210
|
+
|
|
211
|
+
All components accept these common arguments:
|
|
212
|
+
|
|
213
|
+
| Argument | Type | Default | Description |
|
|
214
|
+
|----------|------|---------|-------------|
|
|
215
|
+
| `cache_id` | `str` | **Required** | Unique identifier for disk cache |
|
|
216
|
+
| `data` | `pl.LazyFrame` | `None` | Polars LazyFrame with source data |
|
|
217
|
+
| `filters` | `Dict[str, str]` | `None` | Map identifier -> column for filtering |
|
|
218
|
+
| `interactivity` | `Dict[str, str]` | `None` | Map identifier -> column for click actions |
|
|
219
|
+
| `cache_path` | `str` | `"."` | Base directory for cache storage |
|
|
220
|
+
|
|
221
|
+
## Rendering
|
|
222
|
+
|
|
223
|
+
All components are callable. Pass a `StateManager` to enable cross-component linking:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from openms_insight import StateManager
|
|
227
|
+
|
|
228
|
+
state_manager = StateManager()
|
|
229
|
+
|
|
230
|
+
table(state_manager=state_manager, height=300)
|
|
231
|
+
plot(state_manager=state_manager, height=400)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Development
|
|
237
|
+
|
|
238
|
+
### Building the Vue Component
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
cd js-component
|
|
242
|
+
npm install
|
|
243
|
+
npm run build
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Development Mode
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
cd js-component
|
|
250
|
+
npm run dev
|
|
251
|
+
|
|
252
|
+
# In another terminal:
|
|
253
|
+
export SVC_DEV_MODE=true
|
|
254
|
+
export SVC_DEV_URL=http://localhost:5173
|
|
255
|
+
streamlit run app.py
|
|
256
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
openms_insight/__init__.py,sha256=-QMPUfCk1DwwlQKW6NFSZodRz-r82R4rJPOiWDf_mwA,830
|
|
2
|
+
openms_insight/components/__init__.py,sha256=aNy1E8ttqQiiA5Ka9sviRy17gQT8lfc_EHsl1v1o-UQ,177
|
|
3
|
+
openms_insight/components/heatmap.py,sha256=wqNgmccxI5PeTC08kVf06UUtVEg8YhUf_eY9CN4wAMw,33125
|
|
4
|
+
openms_insight/components/lineplot.py,sha256=klGzpGO-gi76sIocr9T4TYUTV-ztfIIAtdd5k-U-aGE,19382
|
|
5
|
+
openms_insight/components/sequenceview.py,sha256=IPLyUiXTLYwdZ79B3pZDH1intUsjBIknTSLUQ2ftdD4,14359
|
|
6
|
+
openms_insight/components/table.py,sha256=VaUbn2lckeT3shqn9quDvw1HGMsiQrIPPK4w1XQFzzc,14795
|
|
7
|
+
openms_insight/core/__init__.py,sha256=yKRgLfRm1CQm6ZGqQiyDwblzw2Qh3jw57U-ZSd63PYk,338
|
|
8
|
+
openms_insight/core/base.py,sha256=e939N2cu0TEllj09Uw7AKS0FqxE2n_XecwF3ckM_YM0,14746
|
|
9
|
+
openms_insight/core/cache.py,sha256=3fnPDWjuWUnxazK2XflcUIeRZZPQ3N45kAKYu-xGBKw,1197
|
|
10
|
+
openms_insight/core/registry.py,sha256=iBb9xWvgOisxjVX74wtCvqWO20eBW5xH-XcNcVuW-Dg,2107
|
|
11
|
+
openms_insight/core/state.py,sha256=VhY_3Yg-zbx9VsdqHElsNyD2UbJj6ApE4TRcUywDrjQ,6871
|
|
12
|
+
openms_insight/preprocessing/__init__.py,sha256=XFnxlvG-VRMrFbreGmFSIDqfYhev8WPUyZluadMmCgY,462
|
|
13
|
+
openms_insight/preprocessing/compression.py,sha256=8oe-vqeSX-Xf1okPX0C0km2DRnl6Iz53Y1UgsLdMUPI,10499
|
|
14
|
+
openms_insight/preprocessing/filtering.py,sha256=chvmCqLLsMe5mXwG_S4KUagNqJZFLI_iLKKr9g-MkLM,10907
|
|
15
|
+
openms_insight/rendering/__init__.py,sha256=i9MRFrAEAX5kjbOnMjw2woP_6mEaI6HcxtbrNwUxNaM,198
|
|
16
|
+
openms_insight/rendering/bridge.py,sha256=yzXPyK_oKVGBymeDWxgzXeI_jOP7ntJ4p_npOQ1-d1A,11279
|
|
17
|
+
openms_insight/js-component/dist/index.html,sha256=LSJ3B_YmGUrCCdZ1UaZO2p6Wqsih6nTH62Z_0uZxpD8,430
|
|
18
|
+
openms_insight/js-component/dist/assets/index.css,sha256=h0LrX503uebeJrJB0LtoPCVwA4QfDBMmOOR_tU2Wkic,883995
|
|
19
|
+
openms_insight/js-component/dist/assets/index.js,sha256=pakZe7BdeEjiDCPhzGYqEFumB3WtbdnnXYnBuTDVfWo,6060653
|
|
20
|
+
openms_insight/js-component/dist/assets/materialdesignicons-webfont.eot,sha256=CxgxBNL8XyYZbnc8d72vLgVQn9QlnS0V7O3Kebh-hPk,1307880
|
|
21
|
+
openms_insight/js-component/dist/assets/materialdesignicons-webfont.ttf,sha256=YeirpaTpgf4iz3yOi82-oAR251xiw38Bv37jM2HWhCg,1307660
|
|
22
|
+
openms_insight/js-component/dist/assets/materialdesignicons-webfont.woff,sha256=pZKKDVwvYk5G-Y2bFcL2AEU3f3xZTdeKF1kTLqO0Y-s,587984
|
|
23
|
+
openms_insight/js-component/dist/assets/materialdesignicons-webfont.woff2,sha256=Zi_vqPL4qVwYWI0hd0eJwQfGTnccvmWmmvRikcQxGvw,403216
|
|
24
|
+
openms_insight-0.1.0.dist-info/METADATA,sha256=S7rH1PAk9nJDomrQSDnJFmiGC7v8JcwnfrGcBqe55iM,7288
|
|
25
|
+
openms_insight-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
26
|
+
openms_insight-0.1.0.dist-info/licenses/LICENSE,sha256=INFF4rOMmpah7Oi14hLqu7NTOsx56KRRNChAAUcfh2E,1823
|
|
27
|
+
openms_insight-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
--------------------------------------------------------------------------
|
|
2
|
+
OpenMS -- Open-Source Mass Spectrometry
|
|
3
|
+
--------------------------------------------------------------------------
|
|
4
|
+
Copyright OpenMS Inc. -- Eberhard Karls University Tuebingen,
|
|
5
|
+
ETH Zurich, and Freie Universitaet Berlin 2002-present.
|
|
6
|
+
|
|
7
|
+
This software is released under a three-clause BSD license:
|
|
8
|
+
* Redistributions of source code must retain the above copyright
|
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
documentation and/or other materials provided with the distribution.
|
|
13
|
+
* Neither the name of any author or any participating institution
|
|
14
|
+
may be used to endorse or promote products derived from this software
|
|
15
|
+
without specific prior written permission.
|
|
16
|
+
For a full list of authors, refer to the git contributions.
|
|
17
|
+
--------------------------------------------------------------------------
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
21
|
+
ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
|
|
22
|
+
INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
23
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
24
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
25
|
+
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
26
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
27
|
+
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
28
|
+
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|