Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
0beac64
Add Phase 0 migration harness (FLASHApp page rebuild)
claude Jun 2, 2026
c80b782
Phase 3.0: build plan (template grid SSOT + FLASHApp schema/rebuild +…
claude Jun 3, 2026
17c3bad
Phase 3: freeze + vendor template grid.py into FLASHApp (no submodule)
claude Jun 3, 2026
c4bc001
Phase 3 Stage B: rebuild FLASHApp viewers on OpenMS-Insight via the f…
claude Jun 3, 2026
753e2f1
Phase 3: strengthen the phase-3 gate to real construct-smoke
claude Jun 3, 2026
dd89428
Phase 3 r1 fixes: value-based cross-link parity for mass + protein->scan
claude Jun 3, 2026
bbcee74
Phase 3 r1: wire TnT tagger overlay + residue->selectedAA cross-link
claude Jun 3, 2026
58f563e
Phase 3 r2 fixes: 3D mass drill-down, quant 3D axes, residue->tag nar…
claude Jun 3, 2026
13e9e06
Phase 3: record round 3 (all 11 units clean, gate green) — 1st consec…
claude Jun 3, 2026
2e74076
Phase 3: record round 4 (all 11 units clean, gate green) — 2nd consec…
claude Jun 3, 2026
7034a1a
Phase 3 r5 fixes: precursor-3D x = mz*charge (oracle 'Mass'); re-vend…
claude Jun 3, 2026
a45ee78
Phase 3 r6 fixes: 3D parity (empty until mass), axis titles, quant el…
claude Jun 3, 2026
e2b7e44
Phase 3 r7: oracle table chrome + quant 3D isotope breaks/legend (mat…
claude Jun 3, 2026
0539031
Phase 3: record round-8 review (presentation-parity sweep)
claude Jun 3, 2026
77fae60
Phase 3 r8 schema: anno highlight linkage + per-trace id + best-per-s…
claude Jun 3, 2026
47f7cc4
Phase 3 r8 fixes: wire full spectrum/3D interaction model + remaining…
claude Jun 4, 2026
0dc8271
Phase 3 r9 fix: deconv selected-mass MonoMass value label (3-deconv-001)
claude Jun 4, 2026
a914134
Phase 3 r10 fix: best-per-spectrum passes through missing-Scan proteo…
claude Jun 4, 2026
30b3582
Phase 3: record round-10 review (convergence round)
claude Jun 4, 2026
f0f5711
Phase 3 r10 fix: protein selection cascade-clears aa/tag (3-cascade-001)
claude Jun 4, 2026
fb8680e
Phase 3: round-11 machine gate GREEN (round-10 fixes verified)
claude Jun 4, 2026
d3e94e1
Phase 3 r11 fixes: best-per-scan NaN-Score + scan->mass reset cascade
claude Jun 4, 2026
414a7d4
Phase 3: round-12 machine gate GREEN (round-11 fixes verified)
claude Jun 4, 2026
a7fb7a6
Phase 3: record round-12 review
claude Jun 4, 2026
758608f
Phase 3 r12 fix: wire SequenceView two-path residue click (3-seqview-…
claude Jun 4, 2026
0531cdc
Phase 3: round-13 machine gate GREEN (SequenceView two-path fix verif…
claude Jun 4, 2026
9251ea4
Phase 3 r13 fix: deconv SequenceView residue click drives mass (3-seq…
claude Jun 4, 2026
6caeabf
Phase 3: record round-13 review (SequenceView gap cluster)
claude Jun 4, 2026
51178ac
Phase 3 r13 fix: wire SequenceView mass-info header + inbound mass hi…
claude Jun 4, 2026
a392da0
Phase 3: round-14 machine gate GREEN (SequenceView header + inbound h…
claude Jun 4, 2026
c62f716
Phase 3 r14 fix: SequenceView proteoform header labels (3-seqview-005)
claude Jun 4, 2026
50abbd4
Phase 3: round-15 machine gate GREEN (SequenceView header label fix v…
claude Jun 4, 2026
337cfc0
Phase 3 r15 fix: deconv Precursor mass-info header (3-seqview-006)
claude Jun 4, 2026
9f3e9fb
Phase 3: round-16 machine gate GREEN (X-residue + deconv precursor he…
claude Jun 4, 2026
1e96a26
Phase 3 r16 fix: raw heatmap y-axis label "m/z" (3-heatmap-001)
claude Jun 4, 2026
3843bc2
Phase 3: record round-16 review (raw heatmap label + terminal fragmen…
claude Jun 4, 2026
377b654
Phase 3: round-17 machine gate GREEN (terminal fragment ion + raw hea…
claude Jun 4, 2026
5c1f2c4
Phase 3: record round-17 review (proteoform-region fragment handling)
claude Jun 4, 2026
6f0a4b3
Phase 3 r17: tests for proteoform-region SequenceView fragment handli…
claude Jun 4, 2026
67c437e
Phase 3: round-18 machine gate GREEN (proteoform-region fragment hand…
claude Jun 4, 2026
b655565
Phase 3 r18 fix: heatmap click->scan/mass selection (3-heatmap-002)
claude Jun 4, 2026
c6ee4af
Phase 3: round-19 machine gate GREEN (heatmap interactivity + ion-pri…
claude Jun 4, 2026
de7837d
Phase 3: round 19 ALL UNITS CLEAN (streak 1/3) + resolve all 53 findings
claude Jun 4, 2026
93195d8
Phase 3: round-20 machine gate GREEN
claude Jun 4, 2026
14cfece
Phase 3: round 20 ALL UNITS CLEAN (streak 2/3)
claude Jun 4, 2026
340444b
Phase 3: round-21 machine gate GREEN (final convergence round)
claude Jun 4, 2026
9bf0978
Phase 3: CONVERGED — round 21 all 11 units clean (3 consecutive clean…
claude Jun 4, 2026
02752cb
Phase 3 packaging cleanup (plan 5.5): drop local js-component build, …
claude Jun 4, 2026
9c4bd6e
Docker: build + install openms-insight from the migration branch (no …
claude Jun 5, 2026
dcd9b49
Docker: drop js-component/node_modules from the insight-build COPY (s…
claude Jun 5, 2026
de4b7d5
Docker: mkdir -p the package js-component dir before syncing dist (fr…
claude Jun 5, 2026
4b882bb
Address review: per-panel grid keys, value-based dataset selection, c…
claude Jun 5, 2026
94ed99a
CI: build openms-insight from source before pytest (mirrors Dockerfile)
claude Jun 5, 2026
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
15 changes: 15 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,24 @@ jobs:
cache: pip
cache-dependency-path: requirements.txt

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "21"
Comment on lines +27 to +30

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 | 🟠 Major

Pin actions/setup-node to a full commit SHA.

.github/workflows/unit-tests.yml uses the mutable action tag actions/setup-node@v4, which weakens CI supply-chain integrity—pin it to a specific commit SHA instead of v4.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/unit-tests.yml around lines 27 - 30, The workflow is using
the mutable tag actions/setup-node@v4 which weakens CI supply-chain security;
update the uses line in .github/workflows/unit-tests.yml to pin to a specific
commit SHA for actions/setup-node (replace actions/setup-node@v4 with
actions/setup-node@<full-commit-sha>), keeping the existing node-version setting
intact and using the commit SHA from the official actions/setup-node repository
release you trust.


- name: Install dependencies
run: |
python -m pip install --upgrade pip
# OpenMS-Insight (Phase-3, 0.1.15) is not on PyPI: build it from the
# migration branch (Vue bundle + Python) and install from source so the
# requirements.txt pin resolves -- mirrors the Dockerfile insight-build stage.
git clone -b claude/kind-heisenberg-u6dVm --single-branch --depth 1 \
https://github.com/t0mdavid-m/openms-insight.git /tmp/openms-insight
Comment on lines +38 to +39

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and print the relevant section of the workflow around the cited lines
FILE=".github/workflows/unit-tests.yml"
if [ ! -f "$FILE" ]; then
  echo "Missing $FILE"
  exit 1
fi

nl -ba "$FILE" | sed -n '1,120p'
echo "----"
nl -ba "$FILE" | sed -n '20,80p'
echo "----"

# Also search for any other openms-insight clone/install logic in the repo
rg -n "openms-insight|/tmp/openms-insight|npm run build|js-component/dist|requirements\.txt" .github/workflows/unit-tests.yml .claude/hooks/session-start.sh Dockerfile* . || true

Repository: OpenMS/FLASHApp

Length of output: 103


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/unit-tests.yml"
if [ ! -f "$FILE" ]; then
  echo "Missing $FILE"
  exit 1
fi

echo "---- $FILE (lines ~20-80) ----"
awk 'NR>=20 && NR<=80 {printf "%5d %s\n", NR, $0}' "$FILE"

echo "---- matches for openms-insight related logic ----"
rg -n "openms-insight|/tmp/openms-insight|js-component/dist|npm run build|requirements\.txt" .github/workflows .claude/hooks Dockerfile* 2>/dev/null || true

echo "---- any checkout/pin patterns in the workflow ----"
rg -n "git clone|checkout .*sha|checkout --detach|fetch --depth|--depth 1" .github/workflows/unit-tests.yml 2>/dev/null || true

Repository: OpenMS/FLASHApp

Length of output: 5956


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in Dockerfile Dockerfile.arm; do
  echo "===== $f (around insight clone) ====="
  [ -f "$f" ] || continue
  # print likely relevant region
  awk 'NR>=60 && NR<=160 {printf "%5d %s\n", NR, $0}' "$f"
  echo
done

echo "===== search for insight-ref.json usage ====="
rg -n "insight-ref\.json|INSIGHT_BRANCH|openms-insight" Dockerfile Dockerfile.arm

Repository: OpenMS/FLASHApp

Length of output: 12567


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "insight-ref\.json|insight-ref|INSIGHT_BRANCH=.*|git.*checkout.*openms-insight" -S . \
  || true

# Specifically ensure there is no follow-up that extracts a SHA from insight-ref.json
rg -n "jq |\.object\.sha|\"sha\"|grep .*insight-ref\.json|cat .*insight-ref\.json" Dockerfile Dockerfile.arm .github/workflows .claude/hooks 2>/dev/null || true

# List any other workflows that clone openms-insight
rg -n "openms-insight\.git|/tmp/openms-insight|openms_insight/js-component/dist" .github/workflows .claude/hooks Dockerfile* 2>/dev/null || true

Repository: OpenMS/FLASHApp

Length of output: 2552


Use an immutable openms-insight revision in CI checkout.

.github/workflows/unit-tests.yml clones claude/kind-heisenberg-u6dVm via git clone -b ... --depth 1 with no commit SHA pinning, so CI results can silently drift as the branch moves. The same mutable-branch pattern is also used in Dockerfile/Dockerfile.arm (the insight-ref.json fetch is not followed by a SHA checkout).

Suggested hardening
-          git clone -b claude/kind-heisenberg-u6dVm --single-branch --depth 1 \
-            https://github.com/t0mdavid-m/openms-insight.git /tmp/openms-insight
+          git clone https://github.com/t0mdavid-m/openms-insight.git /tmp/openms-insight
+          git -C /tmp/openms-insight checkout <pinned-openms-insight-commit-sha>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/unit-tests.yml around lines 38 - 39, The CI job currently
clones the mutable branch reference "claude/kind-heisenberg-u6dVm" via the git
clone command and can drift; change the workflow to fetch and checkout a
specific immutable commit SHA instead of relying on -b branch (or add an
explicit git checkout <COMMIT_SHA> after clone), and apply the same hardening to
the Dockerfile/Dockerfile.arm flow that consumes insight-ref.json by resolving
and pinning the referenced commit SHA before building; update references to the
git clone invocation and the insight-ref.json fetch so all checkouts use a
concrete commit SHA rather than a branch name.

( cd /tmp/openms-insight/js-component && npm install && npm run build )
mkdir -p /tmp/openms-insight/openms_insight/js-component
cp -r /tmp/openms-insight/js-component/dist \
/tmp/openms-insight/openms_insight/js-component/dist
pip install /tmp/openms-insight
# Pinned runtime deps (pyopenms is needed so ParameterManager imports
# cleanly at collection time) plus test-only deps. fakeredis backs the
# QueueManager/WorkflowManager tests, which pytest.importorskip it.
Expand Down
4 changes: 0 additions & 4 deletions .gitmodules

This file was deleted.

40 changes: 21 additions & 19 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@ RUN mkdir /thirdparty && \
chmod -R +x /thirdparty
ENV PATH="/thirdparty/LuciPHOr2:/thirdparty/MSGFPlus:/thirdparty/ThermoRawFileParser:/thirdparty/Comet:/thirdparty/Percolator:/thirdparty/Sage:${PATH}"

# Build the OpenMS-Insight package (Python + Vue bundle) from the migration branch.
# Insight's Vue dist is gitignored and it has no pip build hook, so build the bundle
# here and sync it into the package tree; the compile-openms stage pip-installs it.
FROM node:21 AS insight-build
ARG INSIGHT_REPO=https://github.com/t0mdavid-m/openms-insight.git
ARG INSIGHT_BRANCH=claude/kind-heisenberg-u6dVm
ADD https://api.github.com/repos/t0mdavid-m/openms-insight/git/refs/heads/${INSIGHT_BRANCH} insight-ref.json
RUN git clone -b ${INSIGHT_BRANCH} --single-branch ${INSIGHT_REPO} /openms-insight
WORKDIR /openms-insight/js-component
RUN npm install && npm run build
RUN mkdir -p /openms-insight/openms_insight/js-component \
&& rm -rf /openms-insight/openms_insight/js-component/dist \
&& cp -r /openms-insight/js-component/dist /openms-insight/openms_insight/js-component/dist \
&& rm -rf /openms-insight/js-component/node_modules

# Build OpenMS and pyOpenMS.
FROM setup-build-system AS compile-openms
WORKDIR /
Expand Down Expand Up @@ -107,6 +122,10 @@ RUN pip install dist/*.whl
# Install other dependencies (excluding pyopenms)
COPY requirements.txt ./requirements.txt
RUN grep -Ev '^pyopenms([=<>!~].*)?$' requirements.txt > requirements_cleaned.txt && mv requirements_cleaned.txt requirements.txt
# OpenMS-Insight: install from the migration branch built in the insight-build stage
# (with its Vue bundle), before requirements so the pin resolves from source not PyPI.
COPY --from=insight-build /openms-insight /tmp/openms-insight
RUN pip install /tmp/openms-insight && rm -rf /tmp/openms-insight
RUN pip install -r requirements.txt

WORKDIR /
Expand All @@ -128,23 +147,9 @@ ENV OPENMS_DATA_PATH="/openms/share/"
# Remove build directory.
RUN rm -rf openms-build

# Build JS-component (quick). Placed after the slow OpenMS build so that changes
# to the Vue component do not invalidate the OpenMS compile cache; its output is
# copied into the final image in the run-app stage below.
FROM node:21 AS js-build

# JS Component
ARG VUE_REPO=https://github.com/t0mdavid-m/openms-streamlit-vue-component.git
ARG VUE_BRANCH=FVdeploy

ADD https://api.github.com/repos/t0mdavid-m/openms-streamlit-vue-component/git/refs/heads/$VUE_BRANCH version.json

RUN git clone -b ${VUE_BRANCH} --single-branch ${VUE_REPO}
WORKDIR /openms-streamlit-vue-component
RUN npm install
RUN npm run build

# Prepare and run streamlit app.
# (The legacy local Vue component build stage was removed in the OpenMS-Insight
# migration -- Insight ships its own Vue bundle via the openms-insight package.)
FROM compile-openms AS run-app

# Install Redis server for job queue and nginx for load balancing.
Expand Down Expand Up @@ -187,9 +192,6 @@ COPY settings.json /app/settings.json
COPY default-parameters.json /app/default-parameters.json
COPY presets.json /app/presets.json

# Copy the pre-built Vue/JS component (built in the js-build stage above).
COPY --from=js-build openms-streamlit-vue-component/dist /app/js-component/dist

# add cron job to the crontab
RUN echo "0 3 * * * /root/miniforge3/envs/streamlit-env/bin/python /app/clean-up-workspaces.py >> /app/clean-up-workspaces.log 2>&1" | crontab -

Expand Down
40 changes: 21 additions & 19 deletions Dockerfile.arm
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ RUN mkdir /thirdparty && \
chmod -R +x /thirdparty
ENV PATH="/thirdparty/LuciPHOr2:/thirdparty/MSGFPlus:/thirdparty/ThermoRawFileParser:/thirdparty/Comet:/thirdparty/Percolator:/thirdparty/Sage:${PATH}"

# Build the OpenMS-Insight package (Python + Vue bundle) from the migration branch.
# Insight's Vue dist is gitignored and it has no pip build hook, so build the bundle
# here and sync it into the package tree; the compile-openms stage pip-installs it.
FROM node:21 AS insight-build
ARG INSIGHT_REPO=https://github.com/t0mdavid-m/openms-insight.git
ARG INSIGHT_BRANCH=claude/kind-heisenberg-u6dVm
ADD https://api.github.com/repos/t0mdavid-m/openms-insight/git/refs/heads/${INSIGHT_BRANCH} insight-ref.json
RUN git clone -b ${INSIGHT_BRANCH} --single-branch ${INSIGHT_REPO} /openms-insight
WORKDIR /openms-insight/js-component
RUN npm install && npm run build
RUN mkdir -p /openms-insight/openms_insight/js-component \
&& rm -rf /openms-insight/openms_insight/js-component/dist \
&& cp -r /openms-insight/js-component/dist /openms-insight/openms_insight/js-component/dist \
&& rm -rf /openms-insight/js-component/node_modules

# Build OpenMS and pyOpenMS.
FROM setup-build-system AS compile-openms
WORKDIR /
Expand Down Expand Up @@ -102,6 +117,10 @@ RUN pip install dist/*.whl
# Install other dependencies (excluding pyopenms)
COPY requirements.txt ./requirements.txt
RUN grep -Ev '^pyopenms([=<>!~].*)?$' requirements.txt > requirements_cleaned.txt && mv requirements_cleaned.txt requirements.txt
# OpenMS-Insight: install from the migration branch built in the insight-build stage
# (with its Vue bundle), before requirements so the pin resolves from source not PyPI.
COPY --from=insight-build /openms-insight /tmp/openms-insight
RUN pip install /tmp/openms-insight && rm -rf /tmp/openms-insight
RUN pip install -r requirements.txt

WORKDIR /
Expand All @@ -123,23 +142,9 @@ ENV OPENMS_DATA_PATH="/openms/share/"
# Remove build directory.
RUN rm -rf openms-build

# Build JS-component (quick). Placed after the slow OpenMS build so that changes
# to the Vue component do not invalidate the OpenMS compile cache; its output is
# copied into the final image in the run-app stage below.
FROM node:21 AS js-build

# JS Component
ARG VUE_REPO=https://github.com/t0mdavid-m/openms-streamlit-vue-component.git
ARG VUE_BRANCH=FVdeploy

ADD https://api.github.com/repos/t0mdavid-m/openms-streamlit-vue-component/git/refs/heads/$VUE_BRANCH version.json

RUN git clone -b ${VUE_BRANCH} --single-branch ${VUE_REPO}
WORKDIR /openms-streamlit-vue-component
RUN npm install
RUN npm run build

# Prepare and run streamlit app.
# (The legacy local Vue component build stage was removed in the OpenMS-Insight
# migration -- Insight ships its own Vue bundle via the openms-insight package.)
FROM compile-openms AS run-app

# Install Redis server for job queue and nginx for load balancing
Expand Down Expand Up @@ -168,9 +173,6 @@ COPY settings.json /app/settings.json
COPY default-parameters.json /app/default-parameters.json
COPY presets.json /app/presets.json

# Copy the pre-built Vue/JS component (built in the js-build stage above).
COPY --from=js-build openms-streamlit-vue-component/dist /app/js-component/dist

# add cron job to the crontab
RUN echo "0 3 * * * /root/miniforge3/envs/streamlit-env/bin/python /app/clean-up-workspaces.py >> /app/clean-up-workspaces.log 2>&1" | crontab -

Expand Down
179 changes: 57 additions & 122 deletions content/FLASHDeconv/FLASHDeconvViewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,144 +2,79 @@

from pathlib import Path

from src.common.common import page_setup, save_params
from src.common.common import page_setup, save_params, show_linked_grid
from src.workflow.FileManager import FileManager
from src.render.render import render_grid

DEFAULT_LAYOUT = [['ms1_deconv_heat_map'], ['scan_table', 'mass_table'],
['anno_spectrum', 'deconv_spectrum'], ['3D_SN_plot']]

def select_experiment():
# Map display name back to experiment ID
st.session_state.selected_experiment0 = display_name_to_id[st.session_state.selected_experiment_dropdown]
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}"] = display_name_to_id[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:
# Map experiment ID to display name for the dropdown index
exp_id = st.session_state[selected_experiment]
display_name = file_manager.get_display_name(exp_id)
return display_name_to_index[display_name]
else:
del st.session_state[selected_experiment]
return None
from src.render.render import make_builders
from src.render.schema import build_insight_caches

# Default panel layout (one experiment): heatmap on top, scan->mass tables,
# annotated + deconvolved spectra, then the precursor-signal 3D plot. Cross-links
# (scan -> mass -> spectrum -> 3D) are carried by the shared StateManager via each
# component's filters/interactivity.
DEFAULT_LAYOUT = [
["ms1_deconv_heat_map"],
["scan_table", "mass_table"],
["anno_spectrum", "deconv_spectrum"],
["3D_SN_plot"],
]

# page initialization
params = page_setup()

# Get available results
file_manager = FileManager(
st.session_state["workspace"],
Path(st.session_state['workspace'], 'cache')
Path(st.session_state["workspace"], "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(['threedim_SN_plot'])

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
# Gate: need at least one processed FLASHDeconv result.
results = file_manager.get_results_list(["threedim_SN_plot"])
if len(results) == 0:
st.error('No results to show yet. Please run a workflow first!')
st.error("No results to show yet. Please run a workflow first!")
st.stop()

# Create display names and mappings
display_names = [file_manager.get_display_name(exp_id) for exp_id in results]
display_name_to_id = {file_manager.get_display_name(exp_id): exp_id for exp_id in results}
display_name_to_index = {n : i for i, n in enumerate(display_names)}
# Keep backward compatibility mapping for experiment IDs
name_to_index = {n : i for i, n in enumerate(results)}

if len(layout) == 2 and side_by_side:
c1, c2 = st.columns(2)
with c1:
st.selectbox(
"choose experiment", display_names,
key="selected_experiment_dropdown",
index=validate_selected_index(file_manager, 'selected_experiment0'),
on_change=select_experiment
)
if 'selected_experiment0' in st.session_state:
render_grid(
st.session_state.selected_experiment0, layout[0], file_manager,
'flashdeconv', "selected_experiment0", 'flash_viewer_grid_0'
)
with c2:
st.selectbox(
"choose experiment", display_names,
key=f'selected_experiment_dropdown_1',
index=validate_selected_index(file_manager, 'selected_experiment1'),
on_change=select_experiment
)
if f"selected_experiment1" in st.session_state:
with st.spinner('Loading component...'):
render_grid(
st.session_state["selected_experiment1"], layout[1],
file_manager, 'flashdeconv', 'selected_experiment1',
'flash_viewer_grid_1'
)
# A global input sequence enables the Sequence View panel (oracle parity).
has_sequence = file_manager.result_exists("sequence", "sequence")

# Saved layout (trimmed nested list + side_by_side) or the default.
if file_manager.result_exists("layout", "layout"):
saved = file_manager.get_results("layout", "layout")["layout"]
layout, side_by_side = saved["layout"], saved["side_by_side"]
else:
### for only single experiment on one view
st.selectbox(
"choose experiment", display_names,
key="selected_experiment_dropdown",
index=validate_selected_index(file_manager, 'selected_experiment0'),
on_change=select_experiment
)


if 'selected_experiment0' in st.session_state:
render_grid(
st.session_state.selected_experiment0, layout[0], file_manager,
'flashdeconv', 'selected_experiment0'
default = DEFAULT_LAYOUT + [["sequence_view"]] if has_sequence else DEFAULT_LAYOUT
layout, side_by_side = [default], False

# Experiments are selected by their stable dataset id; the display name is shown
# via format_func so duplicate display names can't collapse distinct datasets.


def _render_experiment(exp_idx, exp_layout, container):
"""One experiment selector + its linked grid (tool/data-specific, so in-page)."""
with container:
# Oracle parity: start blank (nothing selected) and render nothing until the
# user picks an experiment -- the old viewer used validate_selected_index
# (initially None), which also avoided eagerly building caches on page load.
sel = st.selectbox(
"choose experiment", results, index=None,
format_func=file_manager.get_display_name,
placeholder="Choose an experiment", key=f"deconv_exp_{exp_idx}",
)
if sel is None:
return
ds = sel
# Lazily build the Insight tidy caches for this dataset (idempotent).
build_insight_caches(file_manager, ds, "flashdeconv")
builders = make_builders(file_manager, ds, "flashdeconv")
show_linked_grid([exp_layout], builders, tool=f"flashdeconv_{exp_idx}_{ds}")

### for multiple experiments on one view
if len(layout) > 1:

for exp_index, exp_layout in enumerate(layout):
if exp_index == 0: continue # skip the first experiment

st.divider() # horizontal line

st.selectbox(
"choose experiment", display_names,
key=f'selected_experiment_dropdown_{exp_index}',
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
if f"selected_experiment{exp_index}" in st.session_state:
render_grid(
st.session_state["selected_experiment%d" % exp_index],
layout[exp_index], file_manager, 'flashdeconv',
"selected_experiment%d" % exp_index,
'flash_viewer_grid_%d' % exp_index
)
if len(layout) == 2 and side_by_side:
c1, c2 = st.columns(2)
_render_experiment(0, layout[0], c1)
_render_experiment(1, layout[1], c2)
else:
for i, exp_layout in enumerate(layout):
if i:
st.divider()
_render_experiment(i, exp_layout, st.container())

save_params(params)
Loading
Loading