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 .github/workflows/build-windows-executable-app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ env:
OPENMS_VERSION: 3.2.0
PYTHON_VERSION: 3.11.0
# Name of the installer
APP_NAME: FLASHApp-0.7.4
APP_NAME: FLASHApp-0.8.0
APP_UpgradeCode: "69ae44ad-d554-4e3c-8715-7c4daf60f8bb"

jobs:
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ clean-up-workspaces.log
gdpr_consent/node_modules/

# FLASHApp
workspaces
Pipfile
workspaces-flashtaggerviewer

Expand Down
11 changes: 10 additions & 1 deletion content/FLASHDeconv/FLASHDeconvLayoutManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@
Path(st.session_state['workspace'], 'flashdeconv', 'cache')
)

def get_sequence():
# Check if layout has been set
if not file_manager.result_exists('sequence', 'sequence'):
return None
# fetch layout from cache
sequence = file_manager.get_results('sequence', 'sequence')['sequence']

return sequence['input_sequence'], sequence['fixed_mod_cysteine'], sequence['fixed_mod_methionine']
Comment on lines +39 to +46
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

Add error handling for cache operations.

The function should handle potential exceptions from file manager operations to prevent runtime errors if the cache is corrupted or inaccessible.

 def get_sequence():
     # Check if layout has been set
     if not file_manager.result_exists('sequence', 'sequence'):
         return None
-    # fetch layout from cache
-    sequence = file_manager.get_results('sequence', 'sequence')['sequence']
-
-    return sequence['input_sequence'], sequence['fixed_mod_cysteine'], sequence['fixed_mod_methionine']
+    try:
+        # fetch sequence from cache
+        sequence = file_manager.get_results('sequence', 'sequence')['sequence']
+        return sequence['input_sequence'], sequence['fixed_mod_cysteine'], sequence['fixed_mod_methionine']
+    except (KeyError, TypeError) as e:
+        # Handle corrupted cache data
+        return None
📝 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
def get_sequence():
# Check if layout has been set
if not file_manager.result_exists('sequence', 'sequence'):
return None
# fetch layout from cache
sequence = file_manager.get_results('sequence', 'sequence')['sequence']
return sequence['input_sequence'], sequence['fixed_mod_cysteine'], sequence['fixed_mod_methionine']
def get_sequence():
# Check if layout has been set
if not file_manager.result_exists('sequence', 'sequence'):
return None
try:
# fetch sequence from cache
sequence = file_manager.get_results('sequence', 'sequence')['sequence']
return sequence['input_sequence'], sequence['fixed_mod_cysteine'], sequence['fixed_mod_methionine']
except (KeyError, TypeError) as e:
# Handle corrupted cache data
return None
🤖 Prompt for AI Agents
In content/FLASHDeconv/FLASHDeconvLayoutManager.py around lines 39 to 46, the
get_sequence function lacks error handling for file_manager operations, which
may raise exceptions if the cache is corrupted or inaccessible. Wrap the calls
to file_manager.result_exists and file_manager.get_results in a try-except block
to catch potential exceptions, and handle them gracefully by returning None or
logging the error as appropriate to prevent runtime crashes.


def set_layout(layout, side_by_side=False):
file_manager.store_data('layout', 'layout',
{
Expand Down Expand Up @@ -205,7 +214,7 @@ def handleSettingButtons():


def setSequenceView():
if 'input_sequence' in st.session_state and st.session_state.input_sequence:
if get_sequence() is not None:
global COMPONENT_OPTIONS
COMPONENT_OPTIONS = COMPONENT_OPTIONS + ['Sequence view (Mass table needed)',
'Internal fragment map (Mass table needed)']
Expand Down
84 changes: 58 additions & 26 deletions content/FLASHDeconv/FLASHDeconvSequenceInput.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
import streamlit as st
import re
from src.common.common import page_setup, save_params, v_space
from src.workflow.FileManager import FileManager
from pathlib import Path


# Setup cache access
file_manager = FileManager(
st.session_state["workspace"],
Path(st.session_state['workspace'], 'flashdeconv', 'cache')
)

def set_sequence(input_sequence, fixed_mod_cysteine=None, fixed_mod_methionine=None):
file_manager.store_data('sequence', 'sequence',
{
'input_sequence' : input_sequence,
'fixed_mod_cysteine' : fixed_mod_cysteine,
'fixed_mod_methionine' : fixed_mod_methionine
}
)

def get_sequence():
# Check if layout has been set
if not file_manager.result_exists('sequence', 'sequence'):
return None
# fetch layout from cache
sequence = file_manager.get_results('sequence', 'sequence')['sequence']

return sequence['input_sequence'], sequence['fixed_mod_cysteine'], sequence['fixed_mod_methionine']

def emptySequenceInput():
if file_manager.result_exists('sequence', 'sequence'):
file_manager.remove_results('sequence')
st.session_state['reset_sequence_input'] = True

fixed_mod_cysteine = ['No modification',
'Carbamidomethyl (+57)',
Expand All @@ -26,14 +58,6 @@ def validateSequenceInput(input_seq):
return False
return True


def emptySequenceInput():
for key in ['input_sequence', 'fixed_mod_cysteine', 'fixed_mod_methionine']:
if key in st.session_state:
del st.session_state[key]
st.session_state['reset_sequence_input'] = True


# page initialization
params = page_setup()

Expand All @@ -48,17 +72,22 @@ def emptySequenceInput():
if c2.button('Reset'):
emptySequenceInput()

# if any sequence was submitted before
if 'input_sequence' in st.session_state and st.session_state.input_sequence \
and 'sequence_text' not in st.session_state:
st.session_state['sequence_text'] = st.session_state.input_sequence
# if any modification was submitted before
if 'fixed_mod_cysteine' in st.session_state and st.session_state.fixed_mod_cysteine \
and 'selected_fixed_mod_cysteine' not in st.session_state:
st.session_state['selected_fixed_mod_cysteine'] = st.session_state.fixed_mod_cysteine
if 'fixed_mod_methionine' in st.session_state and st.session_state.fixed_mod_methionine \
and 'selected_fixed_mod_methionine' not in st.session_state:
st.session_state['selected_fixed_mod_methionine'] = st.session_state.fixed_mod_methionine
cached_data = get_sequence()
if cached_data is not None:
seq, cys_mod, met_mod = cached_data

if 'sequence_text' not in st.session_state:
st.session_state['sequence_text'] = seq
if (
(cys_mod is not None)
and ('selected_fixed_mod_cysteine' not in st.session_state)
):
st.session_state['selected_fixed_mod_cysteine'] = cys_mod
if (
(met_mod is not None)
and ('selected_fixed_mod_methionine' not in st.session_state)
):
st.session_state['selected_fixed_mod_methionine'] = met_mod

# clean up the entries of form, if needed
if st.session_state['reset_sequence_input']:
Expand All @@ -80,23 +109,26 @@ def emptySequenceInput():
_, c2 = st.columns([8, 1])
submitted = c2.form_submit_button("Save")
if submitted:
if 'input_sequence' in st.session_state: # initialize
del st.session_state['input_sequence']
if st.session_state['sequence_text'] == '':
emptySequenceInput()
st.rerun()
elif validateSequenceInput(st.session_state['sequence_text']):

st.success('Proteoform sequence is submitted: ' + st.session_state['sequence_text'])
# save information for sequence view
st.session_state['input_sequence'] = ''.join(st.session_state['sequence_text'].split()).upper()

st.session_state['fixed_mod_cysteine'], st.session_state['fixed_mod_methionine'] = '', ''
# save information for sequence view
seq = ''.join(st.session_state['sequence_text'].split()).upper()
cys_mod = None
met_mod = None
if 'selected_fixed_mod_cysteine' in st.session_state \
and st.session_state['selected_fixed_mod_cysteine'] != 'No modification':
st.session_state['fixed_mod_cysteine'] = st.session_state.selected_fixed_mod_cysteine
cys_mod = st.session_state.selected_fixed_mod_cysteine
if 'selected_fixed_mod_methionine' in st.session_state \
and st.session_state['selected_fixed_mod_methionine'] != 'No modification':
st.session_state['fixed_mod_methionine'] = st.session_state.selected_fixed_mod_methionine
met_mod = st.session_state.selected_fixed_mod_methionine

set_sequence(seq, cys_mod, met_mod)

else:
st.error('Error: sequence input is not valid')

Expand Down
32 changes: 26 additions & 6 deletions content/FLASHDeconv/FLASHDeconvViewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

DEFAULT_LAYOUT = [['ms1_deconv_heat_map'], ['scan_table', 'mass_table'],
['anno_spectrum', 'deconv_spectrum'], ['3D_SN_plot']]
if 'input_sequence' in st.session_state and st.session_state.input_sequence:
DEFAULT_LAYOUT = DEFAULT_LAYOUT + [['sequence_view']]

def select_experiment():
st.session_state.selected_experiment0 = st.session_state.selected_experiment_dropdown
Expand All @@ -19,6 +17,15 @@ def select_experiment():
continue
st.session_state[f"selected_experiment{exp_index}"] = st.session_state[f'selected_experiment_dropdown_{exp_index}']

def validate_selected_index(file_manager, selected_experiment):
results = file_manager.get_results_list(['deconv_dfs', 'anno_dfs'])
if selected_experiment in st.session_state:
if st.session_state[selected_experiment] in results:
return name_to_index[st.session_state[selected_experiment]]
else:
del st.session_state[selected_experiment]
return None

# page initialization
params = page_setup()

Expand All @@ -27,6 +34,19 @@ def select_experiment():
st.session_state["workspace"],
Path(st.session_state['workspace'], 'flashdeconv', 'cache')
)

def get_sequence():
# Check if layout has been set
if not file_manager.result_exists('sequence', 'sequence'):
return None
# fetch layout from cache
sequence = file_manager.get_results('sequence', 'sequence')['sequence']

return sequence['input_sequence'], sequence['fixed_mod_cysteine'], sequence['fixed_mod_methionine']

if get_sequence() is not None:
DEFAULT_LAYOUT = DEFAULT_LAYOUT + [['sequence_view']]

results = file_manager.get_results_list(['deconv_dfs', 'anno_dfs'])

if file_manager.result_exists('layout', 'layout'):
Expand All @@ -52,7 +72,7 @@ def select_experiment():
st.selectbox(
"choose experiment", results,
key="selected_experiment_dropdown",
index=name_to_index[st.session_state.selected_experiment0] if 'selected_experiment0' in st.session_state else None,
index=validate_selected_index(file_manager, 'selected_experiment0'),
on_change=select_experiment
)
if 'selected_experiment0' in st.session_state:
Expand All @@ -64,7 +84,7 @@ def select_experiment():
st.selectbox(
"choose experiment", results,
key=f'selected_experiment_dropdown_1',
index = name_to_index[st.session_state[f'selected_experiment1']] if f'selected_experiment1' in st.session_state else None,
index=validate_selected_index(file_manager, 'selected_experiment1'),
on_change=select_experiment
)
if f"selected_experiment1" in st.session_state:
Expand All @@ -80,7 +100,7 @@ def select_experiment():
st.selectbox(
"choose experiment", results,
key="selected_experiment_dropdown",
index=name_to_index[st.session_state.selected_experiment0] if 'selected_experiment0' in st.session_state else None,
index=validate_selected_index(file_manager, 'selected_experiment0'),
on_change=select_experiment
)

Expand All @@ -102,7 +122,7 @@ def select_experiment():
st.selectbox(
"choose experiment", results,
key=f'selected_experiment_dropdown_{exp_index}',
index = name_to_index[st.session_state[f'selected_experiment{exp_index}']] if f'selected_experiment{exp_index}' in st.session_state else None,
index=validate_selected_index(file_manager, f'selected_experiment{exp_index}'),
on_change=select_experiment
)
# if #experiment input files are less than #layouts, all the pre-selection will be the first experiment
Expand Down
72 changes: 25 additions & 47 deletions content/FLASHDeconv/FLASHDeconvWorkflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,57 +85,35 @@ def process_uploaded_files(uploaded_files):
for k, v in parsed_data.items():
wf.file_manager.store_data(unparsed_dataset, k, v)

# make directory to store deconv and anno mzML files & initialize data storage
tabs = st.tabs(["File Upload", "Example Data"])
st.subheader("**Upload FLASHDeconv output files (\*_annotated.mzML & \*_deconv.mzML) or spec1/2 TSV files (Qscore Density Plot only)**")
st.info(
"""
**💡 How to upload files**

# Load Example Data
with tabs[1]:
st.markdown("An example truncated file from the ThermoFisher Pierce Intact Protein Standard Mix dataset.")
_, c2, _ = st.columns(3)
if c2.button("Load Example Data", type="primary"):
# loading and copying example files into default workspace
for filename_postfix, name_tag in zip(
['*_deconv.mzML', '*_annotated.mzML', '*_spec1.tsv'],
["out_deconv_mzML", "anno_annotated_mzML", "spec1_tsv"]
):
for file in Path("example-data", "flashdeconv").glob(filename_postfix):
wf.file_manager.store_file(
file.name.replace(filename_postfix[1:], ''),
name_tag, file, remove=False
)
process_uploaded_files([])
st.success("Example files loaded!")

with tabs[0]:
st.subheader("**Upload FLASHDeconv output files (\*_annotated.mzML & \*_deconv.mzML) or spec1/2 TSV files (Qscore Density Plot only)**")
st.info(
"""
**💡 How to upload files**

1. Browse files on your computer or drag and drops files
2. Click the **Add the uploaded files** button to use them in the workflows
1. Browse files on your computer or drag and drops files
2. Click the **Add the uploaded files** button to use them in the workflows

Select data for analysis from the uploaded files shown below.
Select data for analysis from the uploaded files shown below.

**💡 Make sure that the same number of deconvolved and annotated mzML files are uploaded!**
"""
**💡 Make sure that the same number of deconvolved and annotated mzML files are uploaded!**
"""
)
with st.form('input_files', clear_on_submit=True):
uploaded_files = st.file_uploader(
"FLASHDeconv output mzML files or TSV files", accept_multiple_files=True, type=["mzML", "tsv"]
)
with st.form('input_files', clear_on_submit=True):
uploaded_files = st.file_uploader(
"FLASHDeconv output mzML files or TSV files", accept_multiple_files=True, type=["mzML", "tsv"]
)
_, c2, _ = st.columns(3)
if c2.form_submit_button("Add files to workspace", type="primary"):
if uploaded_files:
# A list of files is required, since online allows only single upload, create a list
if type(uploaded_files) != list:
uploaded_files = [uploaded_files]

# opening file dialog and closing without choosing a file results in None upload
process_uploaded_files(uploaded_files)
st.success("Successfully added uploaded files!")
else:
st.warning("Upload some files before adding them.")
_, c2, _ = st.columns(3)
if c2.form_submit_button("Add files to workspace", type="primary"):
if uploaded_files:
# A list of files is required, since online allows only single upload, create a list
if type(uploaded_files) != list:
uploaded_files = [uploaded_files]

# opening file dialog and closing without choosing a file results in None upload
process_uploaded_files(uploaded_files)
st.success("Successfully added uploaded files!")
else:
st.warning("Upload some files before adding them.")

Comment on lines +101 to 117
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix type comparison and approve form logic

The form-based file upload logic is well-structured with proper validation and user feedback. However, there's a type comparison issue that should be addressed.

Apply this diff to fix the type comparison:

-                if type(uploaded_files) != list:
+                if not isinstance(uploaded_files, list):

The rest of the form submission logic handles file processing correctly with appropriate success/warning messages.

📝 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 st.form('input_files', clear_on_submit=True):
uploaded_files = st.file_uploader(
"FLASHDeconv output mzML files or TSV files", accept_multiple_files=True, type=["mzML", "tsv"]
)
with st.form('input_files', clear_on_submit=True):
uploaded_files = st.file_uploader(
"FLASHDeconv output mzML files or TSV files", accept_multiple_files=True, type=["mzML", "tsv"]
)
_, c2, _ = st.columns(3)
if c2.form_submit_button("Add files to workspace", type="primary"):
if uploaded_files:
# A list of files is required, since online allows only single upload, create a list
if type(uploaded_files) != list:
uploaded_files = [uploaded_files]
# opening file dialog and closing without choosing a file results in None upload
process_uploaded_files(uploaded_files)
st.success("Successfully added uploaded files!")
else:
st.warning("Upload some files before adding them.")
_, c2, _ = st.columns(3)
if c2.form_submit_button("Add files to workspace", type="primary"):
if uploaded_files:
# A list of files is required, since online allows only single upload, create a list
if type(uploaded_files) != list:
uploaded_files = [uploaded_files]
# opening file dialog and closing without choosing a file results in None upload
process_uploaded_files(uploaded_files)
st.success("Successfully added uploaded files!")
else:
st.warning("Upload some files before adding them.")
with st.form('input_files', clear_on_submit=True):
uploaded_files = st.file_uploader(
"FLASHDeconv output mzML files or TSV files", accept_multiple_files=True, type=["mzML", "tsv"]
)
_, c2, _ = st.columns(3)
if c2.form_submit_button("Add files to workspace", type="primary"):
if uploaded_files:
# A list of files is required, since online allows only single upload, create a list
- if type(uploaded_files) != list:
+ if not isinstance(uploaded_files, list):
uploaded_files = [uploaded_files]
# opening file dialog and closing without choosing a file results in None upload
process_uploaded_files(uploaded_files)
st.success("Successfully added uploaded files!")
else:
st.warning("Upload some files before adding them.")
🧰 Tools
🪛 Ruff (0.11.9)

109-109: Use is and is not for type comparisons, or isinstance() for isinstance checks

(E721)

🤖 Prompt for AI Agents
In content/FLASHDeconv/FLASHDeconvWorkflow.py around lines 101 to 117, the type
comparison for uploaded_files uses 'type(uploaded_files) != list', which is not
the recommended way to check types in Python. Replace this with 'not
isinstance(uploaded_files, list)' to correctly check if uploaded_files is not a
list before wrapping it in a list. This change ensures proper type checking and
maintains the existing form submission logic and user feedback.

# File Upload Table
experiments = (
Expand Down
Loading