Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion content/FLASHDeconv/FLASHDeconvDownload.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
zip_buffer.seek(0)
file_manager.store_file(
experiment, 'download_archive', zip_buffer,
file_name=f'{experiment}.zip'
file_name='download_archive.zip'
)
out_zip = file_manager.get_results(
experiment, ['download_archive']
Expand Down
4 changes: 4 additions & 0 deletions content/FLASHDeconv/FLASHDeconvLayoutManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

COMPONENT_OPTIONS=[
'MS1 raw heatmap',
'MS2 raw heatmap',
'MS1 deconvolved heatmap',
'MS2 deconvolved heatmap',
'Scan table',
'Deconvolved spectrum (Scan table needed)',
'Raw spectrum (Scan table needed)',
Expand All @@ -20,7 +22,9 @@

COMPONENT_NAMES=[
'ms1_raw_heatmap',
'ms2_raw_heatmap',
'ms1_deconv_heat_map',
'ms2_deconv_heat_map',
'scan_table',
'deconv_spectrum',
'anno_spectrum',
Expand Down
2 changes: 1 addition & 1 deletion content/FLASHTnT/FLASHTnTDownload.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
zip_buffer.seek(0)
file_manager.store_file(
experiment, 'download_archive', zip_buffer,
file_name=f'{experiment}.zip'
file_name='download_archive.zip'
)
out_zip = file_manager.get_results(
experiment, ['download_archive']
Expand Down
225 changes: 159 additions & 66 deletions content/quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,159 @@

page_setup(page="main")

def render_download_box(container_key, title, description, icon="", subtitle=""):
"""Render a styled download box container with consistent styling."""
with st.container(key=container_key):
st.markdown(
f"""
<h4 style="color: #6c757d; margin-bottom: 0.75rem; font-size: 1.1rem; font-weight: 600;">
{icon} {title}
</h4>
<p style="color: #6c757d; margin-bottom: 1rem;">
{description}
</p>
""",
unsafe_allow_html=True,
)

# Return context for button placement
return st.columns([1, 2, 1])

def render_windows_download_box():
"""Render the Windows app download box."""
if not Path("OpenMS-App.zip").exists():
return False

cols = render_download_box(
container_key="windows_download_container",
title="FLASHApp for Windows",
description="FLASHApp is best enjoyed online but you can download an offline version for Windows systems below.",
icon=""
)

# Center the download button
with cols[1]:
with open("OpenMS-App.zip", "rb") as file:
st.download_button(
label="📥 Download for Windows",
data=file,
file_name="OpenMS-App.zip",
mime="archive/zip",
type="secondary",
use_container_width=True,
help="Download FLASHApp for Windows systems"
)
Comment on lines +39 to +49
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Combine nested context managers and fix MIME type for ZIP

  • Address Ruff SIM117 by combining the nested with-statements.
  • Use the correct MIME type for ZIP files: application/zip (archive/zip is non-standard).
-    # Center the download button
-    with cols[1]:
-        with open("OpenMS-App.zip", "rb") as file:
-            st.download_button(
-                label="📥 Download for Windows",
-                data=file,
-                file_name="OpenMS-App.zip",
-                mime="archive/zip",
-                type="secondary",
-                use_container_width=True,
-                help="Download FLASHApp for Windows systems"
-            )
+    # Center the download button
+    with cols[1], open("OpenMS-App.zip", "rb") as file:
+        st.download_button(
+            label="📥 Download for Windows",
+            data=file,
+            file_name="OpenMS-App.zip",
+            mime="application/zip",
+            type="secondary",
+            use_container_width=True,
+            help="Download FLASHApp for Windows systems"
+        )
📝 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
with cols[1]:
with open("OpenMS-App.zip", "rb") as file:
st.download_button(
label="📥 Download for Windows",
data=file,
file_name="OpenMS-App.zip",
mime="archive/zip",
type="secondary",
use_container_width=True,
help="Download FLASHApp for Windows systems"
)
# Center the download button
with cols[1], open("OpenMS-App.zip", "rb") as file:
st.download_button(
label="📥 Download for Windows",
data=file,
file_name="OpenMS-App.zip",
mime="application/zip",
type="secondary",
use_container_width=True,
help="Download FLASHApp for Windows systems"
)
🧰 Tools
🪛 Ruff (0.12.2)

39-40: Use a single with statement with multiple contexts instead of nested with statements

Combine with statements

(SIM117)

🤖 Prompt for AI Agents
In content/quickstart.py around lines 39 to 49, combine the nested
with-statements into a single context manager (e.g., "with cols[1],
open('OpenMS-App.zip', 'rb') as file:") to resolve Ruff SIM117, and change the
MIME type from the non-standard "archive/zip" to the correct "application/zip"
in the st.download_button call; keep the same parameters and help text.


# Add subtitle text
st.markdown(
"""
<div style="text-align: center; margin-top: 1rem; color: #6c757d;">
Extract the zip file and run the installer (.msi) to install the app.
Launch using the desktop icon after installation.<br>
Even offline, it's still a web app - just packaged so you can use it without an internet connection.
</div>
""",
unsafe_allow_html=True,
)

return True

def render_flash_cli_tools_box():
"""Render the FLASH* command line tools box."""
cols = render_download_box(
container_key="flash_cli_tools_container",
title="FLASH* Command Line Tools",
description="Access FLASH* tools (FLASHDeconv, FLASHTnT, etc.) through the command line interface as part of OpenMS.",
icon=""
)

# Center the link button using Streamlit button with custom styling
with cols[1]:

st.link_button(
"📥 Download Command Line Tools",
"https://abibuilder.cs.uni-tuebingen.de/archive/openms/OpenMSInstaller/experimental/FVdeploy/",
use_container_width=True,
type="secondary", # matches the solid/primary button look
)


# Add subtitle text
st.markdown(
"""
<div style="text-align: center; margin-top: 1rem; color: #6c757d;">
FLASH* tools are a part of the OpenMS TOPP tools and can be used via command line interface for advanced custom automated workflows and scripting.
</div>
""",
unsafe_allow_html=True,
)

def apply_download_box_styling(container_key):
"""Apply consistent styling to a download box container."""
st.markdown(
f"""
<style>
.st-key-{container_key} {{
background: linear-gradient(135deg, #f8f9fa 0%, #f1f3f4 100%) !important;
border: 1px solid #e0e0e0 !important;
border-radius: 8px !important;
padding: 1.5rem !important;
margin: 1rem 0 !important;
text-align: center !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.05) !important;
}}

.st-key-{container_key} > div {{
background: transparent !important;
}}
</style>
""",
unsafe_allow_html=True,
)

def render_download_boxes_responsive():
"""Render download boxes in a responsive layout."""
# Collect available boxes
available_boxes = []

# Check if Windows download is available
if Path("OpenMS-App.zip").exists():
available_boxes.append("windows")

# Always include FLASH* CLI tools
available_boxes.append("flash_cli")

num_boxes = len(available_boxes)

if num_boxes == 0:
return

# Section header
st.markdown(
"""
<h4 style="color: #6c757d; margin-bottom: 1.5rem; font-size: 1.3rem; font-weight: 600; text-align: center;">
Want to use FLASHApp offline?
</h4>
""",
unsafe_allow_html=True,
)

spacer1, center_col, spacer2 = st.columns([2, 4, 2])
# Render boxes based on count
if num_boxes == 1:
# Single box: centered
with center_col:
render_flash_cli_tools_box()
apply_download_box_styling("flash_cli_tools_container")

elif num_boxes == 2:
with center_col:
render_windows_download_box()
apply_download_box_styling("windows_download_container")
render_flash_cli_tools_box()
apply_download_box_styling("flash_cli_tools_container")


def inject_workflow_button_css():
"""Inject CSS for custom workflow button styling with responsive design."""
st.markdown(
Expand Down Expand Up @@ -399,72 +552,12 @@ def render_workflow_selection():
)

def render_enhanced_download_section():
if Path("OpenMS-App.zip").exists():
# Add spacing
st.markdown("<br><hr><br>", unsafe_allow_html=True)

# Create Streamlit container with key for styling (similar to button approach)
container_key = "windows_download_container"

with st.container(key=container_key):
st.markdown(
"""
<h4 style="color: #6c757d; margin-bottom: 0.75rem; font-size: 1.1rem; font-weight: 600;">
Want to use FLASHApp offline?
</h4>
<p style="color: #6c757d; margin-bottom: 1rem;">
FLASHApp is best enjoyed online but you can download an offline version for Windows systems below.
</p>
""",
unsafe_allow_html=True,
)

# Center the Windows download button
col1, col2, col3 = st.columns([2, 2, 2])
with col2:
with open("OpenMS-App.zip", "rb") as file:
st.download_button(
label="📥 Download for Windows",
data=file,
file_name="OpenMS-App.zip",
mime="archive/zip",
type="secondary",
use_container_width=True,
help="Download FLASHApp for Windows systems"
)

st.markdown(
"""
<div style="text-align: center; margin-top: 1rem; color: #6c757d;">
Extract the zip file and run the installer (.msi) to install the app.<br>
Launch using the desktop icon after installation.
</div>
""",
unsafe_allow_html=True,
)

# Apply container styling using key-based CSS targeting (similar to button styling approach)
st.markdown(
f"""
<style>
/* Target the specific container using key-based selector */
.st-key-{container_key} {{
background: linear-gradient(135deg, #f8f9fa 0%, #f1f3f4 100%) !important;
border: 1px solid #e0e0e0 !important;
border-radius: 8px !important;
padding: 1.5rem !important;
margin: 1rem 0 !important;
text-align: center !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.05) !important;
}}

.st-key-{container_key} > div {{
background: transparent !important;
}}
</style>
""",
unsafe_allow_html=True,
)
"""Render the enhanced download section with modular components."""
# Add spacing
st.markdown("<br><hr><br>", unsafe_allow_html=True)

# Render download boxes using responsive layout
render_download_boxes_responsive()

# Main execution
def main():
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions js-component/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<link rel="icon" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>openms-streamlit-vue-component</title>
<script type="module" crossorigin src="./assets/index-16b367b2.js"></script>
<link rel="stylesheet" href="./assets/index-97c297ad.css">
<script type="module" crossorigin src="./assets/index-1a66aa50.js"></script>
<link rel="stylesheet" href="./assets/index-36755211.css">
</head>
<body>
<div id="app"></div>
Expand Down
30 changes: 17 additions & 13 deletions src/parse/deconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,26 @@ def parseDeconv(
# Create full sized version
heatmap = getMSSignalDF(df)

# Store full sized version
file_manager.store_data(
dataset_id, f'ms1_{descriptor}_heatmap', heatmap
)

# Store compressed versions
for size in reversed(compute_compression_levels(20000, len(heatmap), logger=logger)):

for ms_level in [1, 2]:

# Downsample iteratively
heatmap = downsample_heatmap(heatmap, max_datapoints=size)
# Store compressed version
relevant_heatmap = heatmap[heatmap['MSLevel'] == ms_level].drop(columns=['MSLevel'])

# Store full sized version
file_manager.store_data(
dataset_id, f'ms1_{descriptor}_heatmap_{size}', heatmap
dataset_id, f'ms{ms_level}_{descriptor}_heatmap', relevant_heatmap
)


Comment on lines +25 to +33
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Guard against missing MSLevel column and empty partitions

If MSLevel isn’t present (backward compatibility, older caches) the current code raises a KeyError on the filter. Also, empty per-level partitions should be handled explicitly to avoid downstream issues.

-        for ms_level in [1, 2]:
-            
-            relevant_heatmap = heatmap[heatmap['MSLevel'] == ms_level].drop(columns=['MSLevel'])
+        for ms_level in [1, 2]:
+            if 'MSLevel' in heatmap.columns:
+                relevant_heatmap = heatmap.loc[heatmap['MSLevel'] == ms_level].drop(columns=['MSLevel'])
+            else:
+                # Backward compatibility: treat all as MS1, make MS2 empty
+                relevant_heatmap = heatmap.copy() if ms_level == 1 else heatmap.iloc[0:0].copy()

Additionally, consider short-circuiting when relevant_heatmap is empty to avoid unnecessary work (compression loop will be skipped already, but this improves clarity).


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository root: $(pwd)"
echo
echo "Checking for src/parse/deconv.py and nearby context..."
if [ -f src/parse/deconv.py ]; then
  echo "=== src/parse/deconv.py (first 240 lines) ==="
  nl -ba src/parse/deconv.py | sed -n '1,240p'
else
  echo "src/parse/deconv.py not found. Searching for files named deconv.py..."
  rg -n "deconv.py" || true
fi

echo
echo "Searching the repo for occurrences of MSLevel, heatmap, and relevant_heatmap..."
rg -n "MSLevel" || true
echo
rg -n "relevant_heatmap" || true
echo
rg -n "heatmap" || true

Length of output: 506


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository root: $(pwd)"
echo

FILE="src/parse/deconv.py"
echo "Looking for $FILE..."
if [ -f "$FILE" ]; then
  echo "=== $FILE (first 240 lines) ==="
  sed -n '1,240p' "$FILE" || true
else
  echo "$FILE not found. Searching for deconv.py..."
  rg -n "deconv.py" || true
fi

echo
echo "Searching the repo for occurrences of MSLevel, relevant_heatmap, and heatmap..."
rg -n "MSLevel" || true
echo
rg -n "relevant_heatmap" || true
echo
rg -n "heatmap" || true

Length of output: 293913


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/parse/masstable.py"
echo "Showing $FILE (first 400 lines):"
if [ -f "$FILE" ]; then
  sed -n '1,400p' "$FILE" || true
else
  echo "$FILE not found"
fi

echo
echo "Searching for getMSSignalDF and MSLevel occurrences:"
rg -n "def getMSSignalDF|getMSSignalDF|MSLevel" || true

Length of output: 80766


Guard MSLevel usage in src/parse/deconv.py (add defensive check and skip empty partitions)

The pipeline normally adds MSLevel in src/parse/masstable.py (parseFLASHDeconvOutput / getMSSignalDF), but older caches or external inputs may omit it — add a defensive check and skip empty per-level partitions to avoid KeyError and needless storage.

Files to update

  • src/parse/deconv.py — loop that partitions heatmap by MSLevel (around the for ms_level in [1, 2]: block).
  • (reference) src/parse/masstable.py — MSLevel is set there (parseFLASHDeconvOutput / getMSSignalDF, around lines ~170–180), so the guard is only for backward compatibility.

Suggested change (replace the existing per-level loop with this):

-        for ms_level in [1, 2]:
-            
-            relevant_heatmap = heatmap[heatmap['MSLevel'] == ms_level].drop(columns=['MSLevel'])
-
-            # Store full sized version
-            file_manager.store_data(
-                dataset_id, f'ms{ms_level}_{descriptor}_heatmap', relevant_heatmap
-            )
+        for ms_level in [1, 2]:
+            # Defensive: some inputs/caches may not have MSLevel (backward compatibility)
+            if 'MSLevel' in heatmap.columns:
+                relevant_heatmap = heatmap.loc[heatmap['MSLevel'] == ms_level]
+                if 'MSLevel' in relevant_heatmap.columns:
+                    relevant_heatmap = relevant_heatmap.drop(columns=['MSLevel'])
+            else:
+                # Treat all data as MS1; leave MS2 empty
+                relevant_heatmap = heatmap.copy() if ms_level == 1 else heatmap.iloc[0:0].copy()
+
+            # Short-circuit empty partitions to avoid storing/processing them
+            if relevant_heatmap.empty:
+                continue
+
+            # Store full sized version
+            file_manager.store_data(
+                dataset_id, f'ms{ms_level}_{descriptor}_heatmap', relevant_heatmap
+            )

Tag:

📝 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
for ms_level in [1, 2]:
# Downsample iteratively
heatmap = downsample_heatmap(heatmap, max_datapoints=size)
# Store compressed version
relevant_heatmap = heatmap[heatmap['MSLevel'] == ms_level].drop(columns=['MSLevel'])
# Store full sized version
file_manager.store_data(
dataset_id, f'ms1_{descriptor}_heatmap_{size}', heatmap
dataset_id, f'ms{ms_level}_{descriptor}_heatmap', relevant_heatmap
)
for ms_level in [1, 2]:
# Defensive: some inputs/caches may not have MSLevel (backward compatibility)
if 'MSLevel' in heatmap.columns:
relevant_heatmap = heatmap.loc[heatmap['MSLevel'] == ms_level]
if 'MSLevel' in relevant_heatmap.columns:
relevant_heatmap = relevant_heatmap.drop(columns=['MSLevel'])
else:
# Treat all data as MS1; leave MS2 empty
relevant_heatmap = heatmap.copy() if ms_level == 1 else heatmap.iloc[0:0].copy()
# Short-circuit empty partitions to avoid storing/processing them
if relevant_heatmap.empty:
continue
# Store full sized version
file_manager.store_data(
dataset_id, f'ms{ms_level}_{descriptor}_heatmap', relevant_heatmap
)
🤖 Prompt for AI Agents
In src/parse/deconv.py around lines 25 to 33, the loop that partitions heatmap
by MSLevel must be made defensive: first check if the heatmap has an 'MSLevel'
column and skip the per-level partitioning entirely if it's missing; inside the
loop, after selecting rows for each ms_level, skip storing when the resulting
relevant_heatmap is empty (continue) to avoid KeyError and needless storage;
ensure you only call file_manager.store_data for non-empty partitions and do not
change behavior otherwise.

# Store compressed versions
for size in reversed(compute_compression_levels(20000, len(relevant_heatmap), logger=logger)):


# Downsample iteratively
relevant_heatmap = downsample_heatmap(relevant_heatmap, max_datapoints=size)
# Store compressed version
file_manager.store_data(
dataset_id, f'ms{ms_level}_{descriptor}_heatmap_{size}', relevant_heatmap
)

spectra_df = getSpectraTableDF(deconv_df)

# scan_table
Expand Down
9 changes: 9 additions & 0 deletions src/parse/masstable.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def parseFLASHDeconvOutput(annotated, deconvolved, logger=None):
df['NoisyPeaks'] = noisyPeaks
df['CombinedPeaks'] = noisyPeaks
df['MSLevel'] = msLevels
annotateddf['MSLevel'] = msLevels
df['Scan'] = scans
return df, annotateddf, tolerance, massoffset, chargemass

Expand Down Expand Up @@ -214,6 +215,13 @@ def getMSSignalDF(anno_df: pd.DataFrame):
],
dtype=np.float32
)
levels = np.concatenate(
[
[anno_df.loc[index, 'MSLevel']]*len(anno_df.loc[index, "intarray"])
for index in anno_df.index
],
dtype=np.int32
)
mzs = np.concatenate(
[
anno_df.loc[index, "mzarray"]
Expand All @@ -232,6 +240,7 @@ def getMSSignalDF(anno_df: pd.DataFrame):
ms_df = pd.DataFrame({
'mass': mzs, 'rt': rts, 'intensity': ints,
'scan_idx': scan_idxs, 'mass_idx': mass_idxs,
'MSLevel' : levels
})

ms_df.dropna(subset=['intensity'], inplace=True) # remove Nan
Expand Down
Loading
Loading