From 17c41c252491a40928bd8600e7642926df09f47e Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Fri, 3 Oct 2025 19:04:13 +0200 Subject: [PATCH 01/15] utilize streaming during collect --- src/parse/deconv.py | 2 +- src/render/compression.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parse/deconv.py b/src/parse/deconv.py index aaa498b1..f6ff11d8 100644 --- a/src/parse/deconv.py +++ b/src/parse/deconv.py @@ -45,7 +45,7 @@ def parseDeconv( ) # Collect here as this is the data we are operating on - relevant_heatmap_lazy = relevant_heatmap_lazy.collect().lazy() + relevant_heatmap_lazy = relevant_heatmap_lazy.collect(streaming=True).lazy() # Get count for compression level calculation heatmap_count = relevant_heatmap_lazy.select(pl.len()).collect().item() diff --git a/src/render/compression.py b/src/render/compression.py index 316e5d2c..3db04a7e 100644 --- a/src/render/compression.py +++ b/src/render/compression.py @@ -50,7 +50,7 @@ def downsample_heatmap(data, max_datapoints=20000, rt_bins=400, mz_bins=50, logg ) # We need to collect here because scipy requires numpy arrays - sorted_data = sorted_data.collect() + sorted_data = sorted_data.collect(streaming=True).lazy() # Count peaks total_count = sorted_data.select(pl.count()).item() From 5420ac8aaf3894de4a97e7aef4eabeed2375a925 Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Fri, 3 Oct 2025 22:20:59 +0200 Subject: [PATCH 02/15] add tic table parsing --- src/parse/deconv.py | 24 ++++++++++++++++++++++++ src/render/compression.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/parse/deconv.py b/src/parse/deconv.py index f6ff11d8..fa3e16ab 100644 --- a/src/parse/deconv.py +++ b/src/parse/deconv.py @@ -69,6 +69,30 @@ def parseDeconv( dataset_id, f'ms{ms_level}_{descriptor}_heatmap_{size}', current_heatmap_lazy ) + + # Create TIC table + ms1_heatmap = file_manager.get_results( + dataset_id, ['ms1_raw_heatmap'], use_polars=True + )['ms1_raw_heatmap'] + ms1_heatmap = ms1_heatmap.with_columns(pl.lit(1).alias('level')) + ms1_heatmap = ms1_heatmap.drop(['mass', 'mass_idx']) + ms2_heatmap = file_manager.get_results( + dataset_id, ['ms2_raw_heatmap'], use_polars=True + )['ms2_raw_heatmap'] + ms2_heatmap = ms2_heatmap.with_columns(pl.lit(2).alias('level')) + ms2_heatmap = ms2_heatmap.drop(['mass', 'mass_idx']) + tic_data = pl.concat([ms1_heatmap, ms2_heatmap], how='vertical') + tic_data = ( + tic_data.group_by('scan_idx') + .agg([ + pl.col('rt').first().alias('rt'), + pl.col('level').first().alias('level'), + pl.col('intensity').sum().alias('tic'), + ]) + ) + file_manager.store_data(dataset_id, 'tic', tic_data) + + logger.log("20.0 %", level=2) diff --git a/src/render/compression.py b/src/render/compression.py index 3db04a7e..4590b288 100644 --- a/src/render/compression.py +++ b/src/render/compression.py @@ -50,7 +50,7 @@ def downsample_heatmap(data, max_datapoints=20000, rt_bins=400, mz_bins=50, logg ) # We need to collect here because scipy requires numpy arrays - sorted_data = sorted_data.collect(streaming=True).lazy() + sorted_data = sorted_data.collect(streaming=True) # Count peaks total_count = sorted_data.select(pl.count()).item() From d1ab85d8c72cdeaeee6d9fb9b36c27bf3df1b259 Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Fri, 3 Oct 2025 22:27:32 +0200 Subject: [PATCH 03/15] add tic component on back-end side --- content/FLASHDeconv/FLASHDeconvLayoutManager.py | 2 ++ src/render/components.py | 6 ++++++ src/render/initialize.py | 6 +++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/content/FLASHDeconv/FLASHDeconvLayoutManager.py b/content/FLASHDeconv/FLASHDeconvLayoutManager.py index a86164f5..8cbb760d 100644 --- a/content/FLASHDeconv/FLASHDeconvLayoutManager.py +++ b/content/FLASHDeconv/FLASHDeconvLayoutManager.py @@ -17,6 +17,7 @@ 'Mass table (Scan table needed)', '3D S/N plot (Mass table needed)', 'Score Distribution Plot' + 'TIC Chromatogram' # "Sequence view" and "Internal fragment map" is added when "input_sequence" is submitted ] @@ -31,6 +32,7 @@ 'mass_table', '3D_SN_plot', 'fdr_plot', + 'tic_chromatogram', # "sequence view" and "internal fragment map" added when "input_sequence" is submitted ] diff --git a/src/render/components.py b/src/render/components.py index 2469c1de..b651c141 100644 --- a/src/render/components.py +++ b/src/render/components.py @@ -99,3 +99,9 @@ class FLASHQuant: def __init__(self): self.title = 'QuantVis' self.componentName = 'FLASHQuantView' + + +class Chromatogram: + def __init__(self): + self.title = 'TIC' + self.componentName = 'TICChromatogram' diff --git a/src/render/initialize.py b/src/render/initialize.py index be693aef..ac69a30d 100644 --- a/src/render/initialize.py +++ b/src/render/initialize.py @@ -3,7 +3,7 @@ from src.render.components import ( PlotlyHeatmap, PlotlyLineplot, PlotlyLineplotTagger, Plotly3Dplot, Tabulator, SequenceView, InternalFragmentMap, FlashViewerComponent, - FDRPlotly, FLASHQuant + FDRPlotly, FLASHQuant, Chromatogram ) from src.render.compression import compute_compression_levels @@ -172,6 +172,10 @@ def initialize_data(comp_name, selected_data, file_manager, tool): data = file_manager.get_results(selected_data, ['quant_dfs']) data_to_send['quant_data'] = data['quant_dfs'] component_arguments = FLASHQuant() + elif comp_name == 'tic_chromatogram': + data = file_manager.get_results(selected_data, ['tic']) + data_to_send['tic'] = data['tic'] + component_arguments = Chromatogram() components = [[FlashViewerComponent(component_arguments)]] From d84b504c0a1dcbab830ca3001d8dd8fca68e7832 Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Sun, 5 Oct 2025 12:42:01 +0200 Subject: [PATCH 04/15] add tic chromatogram --- content/FLASHDeconv/FLASHDeconvLayoutManager.py | 4 ++-- src/parse/deconv.py | 2 ++ src/render/components.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/content/FLASHDeconv/FLASHDeconvLayoutManager.py b/content/FLASHDeconv/FLASHDeconvLayoutManager.py index 8cbb760d..67200348 100644 --- a/content/FLASHDeconv/FLASHDeconvLayoutManager.py +++ b/content/FLASHDeconv/FLASHDeconvLayoutManager.py @@ -16,8 +16,8 @@ 'Raw spectrum (Scan table needed)', 'Mass table (Scan table needed)', '3D S/N plot (Mass table needed)', - 'Score Distribution Plot' - 'TIC Chromatogram' + 'Score Distribution Plot', + 'TIC Chromatogram', # "Sequence view" and "Internal fragment map" is added when "input_sequence" is submitted ] diff --git a/src/parse/deconv.py b/src/parse/deconv.py index fa3e16ab..70e645fe 100644 --- a/src/parse/deconv.py +++ b/src/parse/deconv.py @@ -90,9 +90,11 @@ def parseDeconv( pl.col('intensity').sum().alias('tic'), ]) ) + tic_data = tic_data.sort("scan_idx", descending=False) file_manager.store_data(dataset_id, 'tic', tic_data) + logger.log("20.0 %", level=2) diff --git a/src/render/components.py b/src/render/components.py index b651c141..61da0f75 100644 --- a/src/render/components.py +++ b/src/render/components.py @@ -6,7 +6,7 @@ # Create a _RELEASE constant. We'll set this to False while we're developing # the component, and True when we're ready to package and distribute it. -_RELEASE = True +_RELEASE = False _component_func = None From ac399ded7de7cef535f3c5db6f1627cbda56e35d Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Sun, 5 Oct 2025 12:43:31 +0200 Subject: [PATCH 05/15] disable dev mode --- src/render/components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/components.py b/src/render/components.py index 61da0f75..b651c141 100644 --- a/src/render/components.py +++ b/src/render/components.py @@ -6,7 +6,7 @@ # Create a _RELEASE constant. We'll set this to False while we're developing # the component, and True when we're ready to package and distribute it. -_RELEASE = False +_RELEASE = True _component_func = None From 68a7615837528f64fecc28e329754726d0958b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20David=20M=C3=BCller?= <57191390+t0mdavid-m@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:16:49 +0100 Subject: [PATCH 06/15] trigger ci --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9f955c83..e70b56ce 100644 --- a/README.md +++ b/README.md @@ -57,3 +57,4 @@ After it has been built you can run the image with: `docker run -p 8501:8501 flashapp:latest` Navigate to `http://localhost:8501` in your browser. +. \ No newline at end of file From e567dc554d00ae08b361c05aa631247b2ed5f2df Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Sun, 5 Oct 2025 20:49:47 +0200 Subject: [PATCH 07/15] add parsing for ms1 deconv heatmap --- src/parse/deconv.py | 58 +++++++++++++++++++++++++++++++++++++++- src/render/components.py | 2 +- src/render/initialize.py | 7 +++++ src/render/update.py | 26 ++++++++++++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/parse/deconv.py b/src/parse/deconv.py index 70e645fe..e6038a07 100644 --- a/src/parse/deconv.py +++ b/src/parse/deconv.py @@ -8,7 +8,7 @@ def parseDeconv( file_manager, dataset_id, out_deconv_mzML, anno_annotated_mzML, - spec1_tsv=None, spec2_tsv=None, logger=None + spec1_tsv, spec2_tsv=None, logger=None ): logger.log("Progress of 'processing FLASHDeconv results':", level=2) logger.log("0.0 %", level=2) @@ -21,6 +21,62 @@ def parseDeconv( file_manager.store_data(dataset_id, 'deconv_dfs', deconv_df) del deconv_df del anno_df + + spec1_df = pd.read_csv( + spec1_tsv, sep='\t', usecols=[ + 'FeatureIndex', 'MonoisotopicMass', 'SumIntensity', 'RetentionTime', + 'ScanNum' + ] + ) + spec1_df.loc[:,'Level'] = 1 + file_manager.store_data(dataset_id, 'spec1_df', spec1_df) + spec2_df = pd.read_csv( + spec2_tsv, sep='\t', usecols=[ + 'FeatureIndex', 'MonoisotopicMass', 'SumIntensity', 'RetentionTime', + 'ScanNum' + ] + ) + spec2_df.loc[:,'Level'] = 2 + file_manager.store_data(dataset_id, 'spec2_df', spec2_df) + del spec1_df + del spec2_df + + features = file_manager.get_results( + dataset_id, ['spec1_df', 'spec2_df'], use_polars=True + ) + # Build the base once + base = pl.concat([features["spec1_df"], features["spec2_df"]]) + + # Sort first so indices reflect first appearance order in the data + sorted_base = base.sort("RetentionTime") + + # Create a ScanNum -> ScanIndex mapping in order of first occurrence + scan_index_map = ( + sorted_base + .select("ScanNum") + .unique(maintain_order=True) + .with_row_count("ScanIndex") + ) + + # Build dataframe + features = ( + sorted_base + # needed for MassIndex; global index after sort + .with_row_count("RowID") + .with_columns( + # per-ScanNum 0-based MassIndex using RowID + (pl.col("RowID") - pl.col("RowID").min().over("ScanNum")).alias("MassIndex"), + # Retention time in seconds to comply with other datastructures + (pl.col("RetentionTime") * 60).alias("RetentionTime"), + ) + # Attach scan index + .join(scan_index_map, on="ScanNum", how="left") + # For now we only consider features at ms1 level + .filter(pl.col("Level") == 1) + # Drop helper columns + .drop(["Level", "RowID"]) + ) + file_manager.store_data(dataset_id, 'feature_dfs', features) # Immediately reload as polars LazyFrames for efficient processing results = file_manager.get_results(dataset_id, ['anno_dfs', 'deconv_dfs'], use_polars=True) diff --git a/src/render/components.py b/src/render/components.py index b651c141..61da0f75 100644 --- a/src/render/components.py +++ b/src/render/components.py @@ -6,7 +6,7 @@ # Create a _RELEASE constant. We'll set this to False while we're developing # the component, and True when we're ready to package and distribute it. -_RELEASE = True +_RELEASE = False _component_func = None diff --git a/src/render/initialize.py b/src/render/initialize.py index ac69a30d..f12d2818 100644 --- a/src/render/initialize.py +++ b/src/render/initialize.py @@ -33,6 +33,13 @@ def initialize_data(comp_name, selected_data, file_manager, tool): data_to_send['deconv_heatmap_df'] = cached_compression_levels[0] additional_data['deconv_heatmap_df'] = cached_compression_levels + + # Get feature annotations + feature_data = file_manager.get_results( + selected_data, ['feature_dfs'], use_polars=True + )['feature_dfs'] + data_to_send['feature_data'] = feature_data + component_arguments = PlotlyHeatmap(title="Deconvolved MS1 Heatmap") elif comp_name == 'ms2_deconv_heat_map': diff --git a/src/render/update.py b/src/render/update.py index 3d940eda..3d1c59de 100644 --- a/src/render/update.py +++ b/src/render/update.py @@ -181,4 +181,30 @@ def filter_data(data, out_components, selection_store, additional_data, tool): ][selection_store['proteinIndex']] } + # Feature Level Information + if (component == 'Deconvolved MS1 Heatmap'): + if ('scanIndex' in selection_store) and ('massIndex' in selection_store): + feature_data = data['feature_data'] + feature_info = feature_data.filter( + (pl.col("ScanIndex") == selection_store['scanIndex']) + & (pl.col("MassIndex") == selection_store['massIndex']) + ) + mass_row = feature_info.collect(streaming=True) + if mass_row.height == 0: + data['feature_data'] = pd.DataFrame() + else: + idx = mass_row.row(0, named=True)['FeatureIndex'] + if idx is None: + data['feature_data'] = pd.DataFrame() + else: + feature_data = ( + feature_data + .filter(pl.col("FeatureIndex") == idx) + .sort("RetentionTime") + ) + data['feature_data'] = feature_data.collect(streaming=True) + else: + data['feature_data'] = pd.DataFrame() + + return data \ No newline at end of file From 2abab9c2251c1fa62d98379b6291ed9a2ffe2971 Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Mon, 6 Oct 2025 14:30:49 +0200 Subject: [PATCH 08/15] add feature index to mass table --- src/parse/deconv.py | 58 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/parse/deconv.py b/src/parse/deconv.py index e6038a07..8aebabb1 100644 --- a/src/parse/deconv.py +++ b/src/parse/deconv.py @@ -208,8 +208,66 @@ def parseDeconv( pl.col('snr').alias('SNR'), pl.col('qscore').alias('QScore') ]) + ) + + # Add FeatureIndex arrays to mass_table + features = file_manager.get_results(dataset_id, ['feature_dfs'], use_polars=True)['feature_dfs'] + + # Handle NaN FeatureIndex values by replacing with -1 + features = features.with_columns([ + pl.when(pl.col('FeatureIndex').is_null()) + .then(pl.lit(-1)) + .otherwise(pl.col('FeatureIndex')) + .alias('FeatureIndex') + ]) + + # Group by ScanNum and create arrays of FeatureIndex ordered by MassIndex + feature_arrays = ( + features + .sort(['ScanIndex', 'MassIndex']) + .group_by('ScanIndex') + .agg([ + pl.col('FeatureIndex').alias('FeatureIndices') + ]) + ) + + # Get scan info with MSLevel and number of masses for creating -1 arrays + scan_info = ( + pl_deconv_indexed + .select([ + pl.col('index'), + pl.col('Scan'), + pl.col('MSLevel'), + pl.col('mzarray').list.len().alias('num_masses') + ]) + ) + + # Join feature arrays with scan info and create FeatureIndex column + scans_with_features = ( + scan_info + .join(feature_arrays, left_on='index', right_on='ScanIndex', how='left') + .with_columns([ + # For MS2 scans create array of -1s + pl.when(pl.col('MSLevel') == 2) + .then( + pl.col('num_masses').map_elements( + lambda n: [-1] * n, + return_dtype=pl.List(pl.Int64) + ) + ) + .otherwise(pl.col('FeatureIndices')) + .alias('FeatureIndex') + ]) + .select(['index', 'FeatureIndex']) + ) + + # Add FeatureIndex to mass_table + mass_table_lazy = ( + mass_table_lazy + .join(scans_with_features, on='index', how='left') .sort("index") ) + file_manager.store_data(dataset_id, 'mass_table', mass_table_lazy) logger.log("50.0 %", level=2) From cb16cd5f6e7635e1b289d6225bffcaded1488137 Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Wed, 3 Dec 2025 12:23:55 +0100 Subject: [PATCH 09/15] add feature table component with bidirectional selection --- .../FLASHDeconv/FLASHDeconvLayoutManager.py | 2 ++ openms-streamlit-vue-component | 2 +- src/parse/deconv.py | 27 ++++++++++++++++++- src/render/components.py | 3 +++ src/render/initialize.py | 4 +++ src/render/update.py | 3 +-- 6 files changed, 37 insertions(+), 4 deletions(-) diff --git a/content/FLASHDeconv/FLASHDeconvLayoutManager.py b/content/FLASHDeconv/FLASHDeconvLayoutManager.py index 67200348..1e7c2375 100644 --- a/content/FLASHDeconv/FLASHDeconvLayoutManager.py +++ b/content/FLASHDeconv/FLASHDeconvLayoutManager.py @@ -15,6 +15,7 @@ 'Deconvolved spectrum (Scan table needed)', 'Raw spectrum (Scan table needed)', 'Mass table (Scan table needed)', + 'Feature table', '3D S/N plot (Mass table needed)', 'Score Distribution Plot', 'TIC Chromatogram', @@ -30,6 +31,7 @@ 'deconv_spectrum', 'anno_spectrum', 'mass_table', + 'feature_table', '3D_SN_plot', 'fdr_plot', 'tic_chromatogram', diff --git a/openms-streamlit-vue-component b/openms-streamlit-vue-component index fad86f46..6d0f54ba 160000 --- a/openms-streamlit-vue-component +++ b/openms-streamlit-vue-component @@ -1 +1 @@ -Subproject commit fad86f46c3aa788387ec5201fc5cceda5db30fd9 +Subproject commit 6d0f54ba186d13770d09b2c1be56effe4ce41c06 diff --git a/src/parse/deconv.py b/src/parse/deconv.py index 8aebabb1..e7731ebf 100644 --- a/src/parse/deconv.py +++ b/src/parse/deconv.py @@ -77,7 +77,32 @@ def parseDeconv( .drop(["Level", "RowID"]) ) file_manager.store_data(dataset_id, 'feature_dfs', features) - + + # Create aggregated feature table for display + # Group by FeatureIndex and compute summary statistics + feature_table = ( + features + .filter(pl.col('FeatureIndex').is_not_null() & (pl.col('FeatureIndex') >= 0)) + .group_by('FeatureIndex') + .agg([ + pl.col('MonoisotopicMass').mean().alias('MonoMass'), + pl.col('SumIntensity').sum().alias('TotalIntensity'), + pl.col('SumIntensity').max().alias('ApexIntensity'), + pl.col('RetentionTime').min().alias('RTStart'), + pl.col('RetentionTime').max().alias('RTEnd'), + pl.len().alias('NumScans'), + # Get the scan index at apex (max intensity) + pl.col('ScanIndex').sort_by('SumIntensity', descending=True).first().alias('ApexScanIndex'), + # Get the mass index at apex + pl.col('MassIndex').sort_by('SumIntensity', descending=True).first().alias('ApexMassIndex'), + ]) + .with_columns([ + (pl.col('RTEnd') - pl.col('RTStart')).alias('RTDuration'), + ]) + .sort('FeatureIndex') + ) + file_manager.store_data(dataset_id, 'feature_table', feature_table) + # Immediately reload as polars LazyFrames for efficient processing results = file_manager.get_results(dataset_id, ['anno_dfs', 'deconv_dfs'], use_polars=True) pl_anno = results['anno_dfs'] diff --git a/src/render/components.py b/src/render/components.py index 61da0f75..9df3f58f 100644 --- a/src/render/components.py +++ b/src/render/components.py @@ -59,6 +59,9 @@ def __init__(self, table_type): elif table_type == 'TagTable': self.title = 'Tag Table' self.componentName = "TabulatorTagTable" + elif table_type == 'FeatureTable': + self.title = 'Feature Table' + self.componentName = "TabulatorFeatureTable" class PlotlyLineplot: diff --git a/src/render/initialize.py b/src/render/initialize.py index f12d2818..df750bf1 100644 --- a/src/render/initialize.py +++ b/src/render/initialize.py @@ -183,6 +183,10 @@ def initialize_data(comp_name, selected_data, file_manager, tool): data = file_manager.get_results(selected_data, ['tic']) data_to_send['tic'] = data['tic'] component_arguments = Chromatogram() + elif comp_name == 'feature_table': + data = file_manager.get_results(selected_data, ['feature_table']) + data_to_send['feature_table'] = data['feature_table'] + component_arguments = Tabulator('FeatureTable') components = [[FlashViewerComponent(component_arguments)]] diff --git a/src/render/update.py b/src/render/update.py index 3d1c59de..b0d5215c 100644 --- a/src/render/update.py +++ b/src/render/update.py @@ -186,7 +186,7 @@ def filter_data(data, out_components, selection_store, additional_data, tool): if ('scanIndex' in selection_store) and ('massIndex' in selection_store): feature_data = data['feature_data'] feature_info = feature_data.filter( - (pl.col("ScanIndex") == selection_store['scanIndex']) + (pl.col("ScanIndex") == selection_store['scanIndex']) & (pl.col("MassIndex") == selection_store['massIndex']) ) mass_row = feature_info.collect(streaming=True) @@ -206,5 +206,4 @@ def filter_data(data, out_components, selection_store, additional_data, tool): else: data['feature_data'] = pd.DataFrame() - return data \ No newline at end of file From aee5eb74a3840cf2adc56dc33b0d0878a3ffdd51 Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Wed, 3 Dec 2025 12:24:13 +0100 Subject: [PATCH 10/15] add In Feature column to mass table --- openms-streamlit-vue-component | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openms-streamlit-vue-component b/openms-streamlit-vue-component index 6d0f54ba..4351a8c1 160000 --- a/openms-streamlit-vue-component +++ b/openms-streamlit-vue-component @@ -1 +1 @@ -Subproject commit 6d0f54ba186d13770d09b2c1be56effe4ce41c06 +Subproject commit 4351a8c17e6ba6e5a91ca7e6648d9e4dd44f203f From 0bf00bd05ca49add8db50ccbbb17333f8be7b0fa Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Wed, 3 Dec 2025 13:18:47 +0100 Subject: [PATCH 11/15] add selected feature trace to TIC chromatogram - Send feature_table and feature_dfs data to TIC chromatogram component - Vue component displays selected feature as red line trace - Both TIC and feature traces share same y-axis scale --- openms-streamlit-vue-component | 2 +- src/render/initialize.py | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/openms-streamlit-vue-component b/openms-streamlit-vue-component index 4351a8c1..fbfc6035 160000 --- a/openms-streamlit-vue-component +++ b/openms-streamlit-vue-component @@ -1 +1 @@ -Subproject commit 4351a8c17e6ba6e5a91ca7e6648d9e4dd44f203f +Subproject commit fbfc6035f8ba005f230cbfa324056c9456bd2f4b diff --git a/src/render/initialize.py b/src/render/initialize.py index df750bf1..1d05ee22 100644 --- a/src/render/initialize.py +++ b/src/render/initialize.py @@ -180,8 +180,25 @@ def initialize_data(comp_name, selected_data, file_manager, tool): data_to_send['quant_data'] = data['quant_dfs'] component_arguments = FLASHQuant() elif comp_name == 'tic_chromatogram': - data = file_manager.get_results(selected_data, ['tic']) + data = file_manager.get_results(selected_data, ['tic', 'feature_table', 'feature_dfs']) data_to_send['tic'] = data['tic'] + data_to_send['feature_table'] = data.get('feature_table') + # feature_dfs contains per-scan intensity data for each feature + feature_dfs = data.get('feature_dfs') + if feature_dfs is not None: + # Convert DataFrame to list of dicts for JSON serialization + if hasattr(feature_dfs, 'collect'): + # It's a Polars LazyFrame + df = feature_dfs.collect() + elif hasattr(feature_dfs, 'to_dicts'): + # It's a Polars DataFrame + df = feature_dfs + else: + # It's a pandas DataFrame - convert to polars for consistent handling + df = pl.from_pandas(feature_dfs) + # Select only needed columns and drop nulls to ensure clean JSON + df = df.select(['FeatureIndex', 'RetentionTime', 'SumIntensity']).drop_nulls() + data_to_send['feature_dfs'] = df.to_dicts() component_arguments = Chromatogram() elif comp_name == 'feature_table': data = file_manager.get_results(selected_data, ['feature_table']) From f63734804fe0170a85db11a7df66b0188842408c Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Wed, 3 Dec 2025 13:27:42 +0100 Subject: [PATCH 12/15] add toggle to show/hide feature trace in TIC chromatogram --- .gitignore | 1 + openms-streamlit-vue-component | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d65d045e..bcff8eac 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ run_app.bat python* gdpr_consent/node_modules/ *~ +CLAUDE.md diff --git a/openms-streamlit-vue-component b/openms-streamlit-vue-component index fbfc6035..4c210513 160000 --- a/openms-streamlit-vue-component +++ b/openms-streamlit-vue-component @@ -1 +1 @@ -Subproject commit fbfc6035f8ba005f230cbfa324056c9456bd2f4b +Subproject commit 4c210513c58de31390a547dc30d3a08f9b98dd44 From 4ffbbd9b0392c07ddce5c02bf0488de55020f965 Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Wed, 3 Dec 2025 13:34:16 +0100 Subject: [PATCH 13/15] add feature trace integration to TIC chromatogram --- openms-streamlit-vue-component | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openms-streamlit-vue-component b/openms-streamlit-vue-component index 4c210513..765b4d42 160000 --- a/openms-streamlit-vue-component +++ b/openms-streamlit-vue-component @@ -1 +1 @@ -Subproject commit 4c210513c58de31390a547dc30d3a08f9b98dd44 +Subproject commit 765b4d4237734a9402318d7388f52a14800bb0f9 From fb0e62098b70df9c602d78d3b570b61008b03bfb Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Wed, 3 Dec 2025 14:24:17 +0100 Subject: [PATCH 14/15] add auto-integration on feature trace click Clicking on the red feature trace line automatically sets integration bounds to the feature's RTStart/RTEnd and calculates both TIC and feature area. --- openms-streamlit-vue-component | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openms-streamlit-vue-component b/openms-streamlit-vue-component index 765b4d42..edd30782 160000 --- a/openms-streamlit-vue-component +++ b/openms-streamlit-vue-component @@ -1 +1 @@ -Subproject commit 765b4d4237734a9402318d7388f52a14800bb0f9 +Subproject commit edd3078204cc51d46878f8b50382dfde62a86c32 From 3a727ddf14f801308e7baff3650115963bcae516 Mon Sep 17 00:00:00 2001 From: Tom David Mueller Date: Wed, 3 Dec 2025 15:01:46 +0100 Subject: [PATCH 15/15] disable feature trace functionality for MS2 level Feature trace and integration are MS1-only features. When MS2 filter is active: hide feature trace, disable toggle button, clear any existing integration. --- openms-streamlit-vue-component | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openms-streamlit-vue-component b/openms-streamlit-vue-component index edd30782..93b8a7d1 160000 --- a/openms-streamlit-vue-component +++ b/openms-streamlit-vue-component @@ -1 +1 @@ -Subproject commit edd3078204cc51d46878f8b50382dfde62a86c32 +Subproject commit 93b8a7d1e80b6427ad56420df390a5d6bbe218d0