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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build.log
run_app.spec
clean-up-workspaces.log

# FLASHViewer
# FLASHApp
workspaces
Pipfile
workspaces-flashtaggerviewer
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ ARG PORT=8501
ARG GITHUB_TOKEN
ENV GH_TOKEN=${GITHUB_TOKEN}
# Streamlit app Gihub user name (to download artifact from).
ARG GITHUB_USER=t0mdavid-m
ARG GITHUB_USER=OpenMS
# Streamlit app Gihub repository name (to download artifact from).
ARG GITHUB_REPO=FLASHViewer
ARG GITHUB_REPO=FLASHApp
# Name of the zip file containing the windows executable
ARG ASSET_NAME=OpenMS-App.zip

Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FLASHViewer
# FLASHApp

FLASHViewer for visualizing FLASHDeconv's results \
FLASHApp for visualizing FLASHDeconv's results \
This app is based on [OpenMS streamlit template project](https://github.com/OpenMS/streamlit-template).

![Overview](https://github.com/user-attachments/assets/35fe2c24-7dbc-40cd-b8b5-b7504768ade1)
Expand Down Expand Up @@ -28,7 +28,7 @@ for more documentation on submodules

## Build

To build FLASHViewer, you first need to build the `openms-streamlit-vue-component`
To build FLASHApp, you first need to build the `openms-streamlit-vue-component`
and copy the build from `./openms-streamlit-vue-component/dist` to
`./js-component/dist`.

Expand All @@ -37,7 +37,7 @@ You then should set streamlit to production in two locations:
* in `./.streamlit/config.toml` set `developmentMode` to `false`
* in `./src/components.py` set `_RELEASE` to `True`

These steps should be done before building any version of FLASHViewer.
These steps should be done before building any version of FLASHApp.

### Docker

Expand All @@ -46,14 +46,14 @@ First you need to build an image locally.
Prerequisite: `src/components.py` has `RELEASE=True` and `dist/` contains a build of the Vue
component. These should be the settings on the `main` branch.

build image with: `docker build -f Dockerfile --no-cache -t flashviewer:latest --build-arg GITHUB_TOKEN=<your-github-token> .`
build image with: `docker build -f Dockerfile --no-cache -t flashapp:latest --build-arg GITHUB_TOKEN=<your-github-token> .`

You should see a successful output, but you can check if an image is built with:

`docker image ls`

After it has been built you can run the image with:

`docker run -p 8501:8501 flashviewer:latest`
`docker run -p 8501:8501 flashapp:latest`

Navigate to `http://localhost:8501` in your browser.
117 changes: 91 additions & 26 deletions content/FLASHDeconv/FLASHDeconvLayoutManager.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import json

import streamlit as st

from src.common.common import page_setup, v_space, save_params
import json
from src.workflow.FileManager import FileManager
from pathlib import Path

COMPONENT_OPTIONS=[
'MS1 raw heatmap',
'MS1 deconvolved heatmap',
'Scan table',
'Deconvolved spectrum (Scan table needed)',
'Annotated spectrum (Scan table needed)',
'Raw spectrum (Scan table needed)',
'Mass table (Scan table needed)',
'3D S/N plot (Mass table needed)',
'QScore ECDF Plot (report_FDR must be enabled)'
'Score Distribution Plot'
# "Sequence view" and "Internal fragment map" is added when "input_sequence" is submitted
]

Expand All @@ -26,12 +30,37 @@
# "sequence view" and "internal fragment map" added when "input_sequence" is submitted
]

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

def set_layout(layout, side_by_side=False):
file_manager.store_data('layout', 'layout',
{
'layout': layout,
'side_by_side': side_by_side
}
)

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

return layout['layout'], layout['side_by_side']

Comment on lines +47 to 55
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Broken return logic – will raise TypeError on first call

get_layout() extracts the inner layout list and then treats that list as a dict on the next line (layout['layout']).
When a saved layout exists this will immediately explode, preventing the whole page from loading.

 def get_layout():
     # Check if layout has been set
     if not file_manager.result_exists('layout', 'layout'):
         return None
     # fetch layout from cache
-    layout = file_manager.get_results('layout', 'layout')['layout']
-
-    return layout['layout'], layout['side_by_side'] 
+    data = file_manager.get_results('layout', 'layout')
+    return data['layout'], data['side_by_side']
📝 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_layout():
# Check if layout has been set
if not file_manager.result_exists('layout', 'layout'):
return None
# fetch layout from cache
layout = file_manager.get_results('layout', 'layout')['layout']
return layout['layout'], layout['side_by_side']
def get_layout():
# Check if layout has been set
if not file_manager.result_exists('layout', 'layout'):
return None
# fetch layout from cache
data = file_manager.get_results('layout', 'layout')
return data['layout'], data['side_by_side']

def resetSettingsToDefault(num_of_exp=1):
st.session_state["layout_setting"] = [[['']]] # 1D: experiment, 2D: row, 3D: column, element=component name
st.session_state["num_of_experiment_to_show"] = num_of_exp
for index in range(1, num_of_exp):
st.session_state.layout_setting.append([['']])
if file_manager.result_exists('layout', 'layout'):
file_manager.remove_results('layout')
st.session_state["edit_mode"] = True


def containerForNewComponent(exp_index, row_index, col_index):
Expand Down Expand Up @@ -122,33 +151,45 @@ def getTrimmedLayoutSetting():
trimmed_layout_setting.append(rows)
return trimmed_layout_setting

def getExpandedLayoutSetting(trimmed_layout_setting):
expanded_layout_setting = []
for exp in trimmed_layout_setting:
rows = []
for row in exp:
cols = []
for col in row:
if col:
cols.append(COMPONENT_OPTIONS[COMPONENT_NAMES.index(col)])
if cols:
rows.append(cols)
if rows:
expanded_layout_setting.append(rows)
return expanded_layout_setting

def handleEditAndSaveButtons():
# if "Edit" button was clicked,
if "edit_btn_clicked" in st.session_state and st.session_state["edit_btn_clicked"]:
# reset variables based on "saved_layout_setting"
st.session_state["num_of_experiment_to_show"] = len(st.session_state["saved_layout_setting"])
st.session_state["edit_mode"] = True
# reset variables based on saved layout setting
st.session_state["num_of_experiment_to_show"] = len(get_layout()[0]) if get_layout() is not None else 1
st.session_state["layout_setting"] = [[[COMPONENT_OPTIONS[COMPONENT_NAMES.index(col)]
for col in row if col]
for row in exp if row]
for exp in st.session_state.saved_layout_setting]
# remove saved state, if any
del st.session_state["saved_layout_setting"]
for exp in get_layout()[0]]

# if "Save" button was clicked,
if "layout_saved" in st.session_state and st.session_state["layout_saved"]:
got_error = validateSubmittedLayout()
st.session_state['save_btn_error_message'] = got_error # to show error msg at the end
if not got_error:
# get only submitted info from "layout_setting"
st.session_state["saved_layout_setting"] = getTrimmedLayoutSetting()
set_layout(getTrimmedLayoutSetting(), side_by_side=st.session_state['side_by_side_view'])
st.session_state["edit_mode"] = False


def handleSettingButtons():
if "reset_btn_clicked" in st.session_state and st.session_state.reset_btn_clicked:
resetSettingsToDefault()
if "saved_layout_setting" in st.session_state:
del st.session_state["saved_layout_setting"]

if "uploaded_json_file" in st.session_state and st.session_state.uploaded_json_file is not None:
uploaded_layout = json.load(st.session_state.uploaded_json_file)
Expand Down Expand Up @@ -179,28 +220,54 @@ def setSequenceView():
setSequenceView()

# handles "onclick" of buttons
if st.session_state.get("edit_mode") is None:
st.session_state["edit_mode"] = True
handleSettingButtons()
handleEditAndSaveButtons()

# initialize setting information
if "layout_setting" not in st.session_state:
resetSettingsToDefault()
if get_layout() is not None:
# load layout setting from cache
st.session_state['layout_setting'] = getExpandedLayoutSetting(get_layout()[0])
st.session_state['num_of_experiment_to_show'] = len(st.session_state.layout_setting)
st.session_state['side_by_side_view'] = get_layout()[1]
st.session_state["edit_mode"] = False
else:
resetSettingsToDefault()
# the "num_of_experiment_to_show" changed
elif "num_of_experiment_to_show" in st.session_state and \
len(st.session_state.layout_setting) != st.session_state.num_of_experiment_to_show:
resetSettingsToDefault(st.session_state.num_of_experiment_to_show)

### title and setting buttons
c1, c2, c3, c4 = st.columns([6, 1, 1, 1])
c1, c2, c3, c4, c5 = st.columns([6, 1, 1, 1, 1])
c1.title("Layout Manager")

# side-by-side view option for 2 experiments
if 'side_by_side_view' not in st.session_state:
st.session_state['side_by_side_view'] = False
if (
('num_of_experiment_to_show' in st.session_state
and st.session_state.num_of_experiment_to_show == 2)
or
(not st.session_state.edit_mode
and (get_layout() is not None and len(get_layout()[0]) == 2))
):
v_space(1, c2)
st.session_state['side_by_side_view'] = c2.checkbox(
"Side-by-Side View", value=st.session_state['side_by_side_view'],
help="If checked, experiments will be shown side-by-side",
disabled=(not st.session_state.edit_mode)
)

# Load existing layout setting file
v_space(1, c2)
c2.button("Load Setting", key="load_btn_clicked")
v_space(1, c3)
c3.button("Load Setting", key="load_btn_clicked")

# Save current layout setting (only after "Saved" button)
v_space(1, c3)
c3.download_button(
v_space(1, c4)
c4.download_button(
label="Save Setting",
data=json.dumps(getTrimmedLayoutSetting()),
file_name='FLASHViewer_layout_settings.json',
Expand All @@ -209,18 +276,18 @@ def setSequenceView():
)

# Reset settings to default
v_space(1, c4)
c4.button("Reset Setting", key="reset_btn_clicked")
v_space(1, c5)
c5.button("Reset Setting", key="reset_btn_clicked")

### space for File Uploader, when "Load Setting" button is clicked
if "load_btn_clicked" in st.session_state and st.session_state.load_btn_clicked:
st.file_uploader("Choose a json file", type="json", key="uploaded_json_file")

### Main part
if "saved_layout_setting" in st.session_state:
if (not st.session_state.edit_mode) and (get_layout() is not None):
# show saved-mode
for index_of_experiment in range(len(st.session_state.saved_layout_setting)):
layout_info_per_experiment = st.session_state.saved_layout_setting[index_of_experiment]
for index_of_experiment in range(len(get_layout()[0])):
layout_info_per_experiment = get_layout()[0][index_of_experiment]
with st.expander("Experiment #%d"%(index_of_experiment+1), expanded=True):
for row_index, row in enumerate(layout_info_per_experiment):
st_cols = st.columns(len(row))
Expand All @@ -238,10 +305,8 @@ def setSequenceView():

### buttons for edit/save
_, edit_btn_col, save_btn_col = st.columns([9, 1, 1])
edit_btn_col.button("Edit", key="edit_btn_clicked",
disabled=False if "saved_layout_setting" in st.session_state else True)
save_btn_col.button("Save", key="layout_saved",
disabled=True if "saved_layout_setting" in st.session_state else False)
edit_btn_col.button("Edit", key="edit_btn_clicked", disabled=st.session_state.edit_mode)
save_btn_col.button("Save", key="layout_saved", disabled=(not st.session_state.edit_mode))

### showing error/success message
if "save_btn_error_message" in st.session_state and st.session_state.layout_saved:
Expand Down
91 changes: 60 additions & 31 deletions content/FLASHDeconv/FLASHDeconvViewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ def setSequenceViewInDefaultView():

def select_experiment():
st.session_state.selected_experiment0 = st.session_state.selected_experiment_dropdown
if "saved_layout_setting" in st.session_state and len(st.session_state["saved_layout_setting"]) > 1:
for exp_index in range(1, len(st.session_state["saved_layout_setting"])):
if len(layout) > 1:
for exp_index in range(1, len(layout)):
if st.session_state[f'selected_experiment_dropdown_{exp_index}'] is None:
continue
st.session_state[f"selected_experiment{exp_index}"] = st.session_state[f'selected_experiment_dropdown_{exp_index}']
Expand All @@ -163,6 +163,15 @@ def select_experiment():
)
results = file_manager.get_results_list(['deconv_dfs', 'anno_dfs'])

if file_manager.result_exists('layout', 'layout'):
layout = file_manager.get_results('layout', 'layout')['layout']
side_by_side = layout['side_by_side']
layout = layout['layout']

else:
layout = [DEFAULT_LAYOUT]
side_by_side = False

### if no input file is given, show blank page
if len(results) == 0:
st.error('No results to show yet. Please run a workflow first!')
Expand All @@ -171,40 +180,60 @@ def select_experiment():
# Map names to index
name_to_index = {n : i for i, n in enumerate(results)}

### for only single experiment on one view
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,
on_change=select_experiment
)
if len(layout) == 2 and side_by_side:
c1, c2 = st.columns(2)
with c1:
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,
on_change=select_experiment
)
if 'selected_experiment0' in st.session_state:
with st.spinner('Loading component...'):
sendDataToJS(st.session_state.selected_experiment0, layout[0])
with c2:
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,
on_change=select_experiment
)
if f"selected_experiment1" in st.session_state:
with st.spinner('Loading component...'):
sendDataToJS(st.session_state["selected_experiment1"], layout[1], 'flash_viewer_grid_1')

else:
### for only single experiment on one view
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,
on_change=select_experiment
)

if 'selected_experiment0' in st.session_state:
layout_info = DEFAULT_LAYOUT
if "saved_layout_setting" in st.session_state: # when layout manager was used
layout_info = st.session_state["saved_layout_setting"][0]
with st.spinner('Loading component...'):
sendDataToJS(st.session_state.selected_experiment0, layout_info)

if 'selected_experiment0' in st.session_state:
with st.spinner('Loading component...'):
sendDataToJS(st.session_state.selected_experiment0, layout[0])

### for multiple experiments on one view
if "saved_layout_setting" in st.session_state and len(st.session_state["saved_layout_setting"]) > 1:
### for multiple experiments on one view
if len(layout) > 1:

for exp_index, exp_layout in enumerate(st.session_state["saved_layout_setting"]):
if exp_index == 0: continue # skip the first experiment
for exp_index, exp_layout in enumerate(layout):
if exp_index == 0: continue # skip the first experiment

st.divider() # horizontal line
st.divider() # horizontal line

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,
on_change=select_experiment
)
# if #experiment input files are less than #layouts, all the pre-selection will be the first experiment
if f"selected_experiment{exp_index}" in st.session_state:
layout_info = st.session_state["saved_layout_setting"][exp_index]
with st.spinner('Loading component...'):
sendDataToJS(st.session_state["selected_experiment%d" % exp_index], layout_info, 'flash_viewer_grid_%d' % exp_index)
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,
on_change=select_experiment
)
# if #experiment input files are less than #layouts, all the pre-selection will be the first experiment
if f"selected_experiment{exp_index}" in st.session_state:
with st.spinner('Loading component...'):
sendDataToJS(st.session_state["selected_experiment%d" % exp_index], layout[exp_index], 'flash_viewer_grid_%d' % exp_index)

save_params(params)
Loading