Refactor sidebar layout for improved organization and styling. Grouped model selection, conditions, and force scale into distinct containers. Updated CSS for sidebar panels to enhance visual consistency and usability.
Browse files- S2FApp/app.py +86 -83
- S2FApp/static/s2f_styles.css +37 -3
- S2FApp/ui/result_display.py +1 -8
S2FApp/app.py
CHANGED
|
@@ -222,59 +222,61 @@ with st.sidebar:
|
|
| 222 |
</div>
|
| 223 |
""", unsafe_allow_html=True)
|
| 224 |
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
|
|
|
| 232 |
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
|
| 248 |
substrate_config = None
|
| 249 |
substrate_val = DEFAULT_SUBSTRATE
|
| 250 |
use_manual = True
|
| 251 |
if model_type == "single_cell":
|
| 252 |
try:
|
| 253 |
-
st.
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
)
|
| 260 |
-
from_config = conditions_source == "From config"
|
| 261 |
-
if from_config:
|
| 262 |
-
substrate_config = None
|
| 263 |
-
substrates = list_substrates()
|
| 264 |
-
substrate_val = st.selectbox(
|
| 265 |
-
"Conditions (from config)",
|
| 266 |
-
substrates,
|
| 267 |
-
help="Select a preset from config/substrate_settings.json",
|
| 268 |
label_visibility="collapsed",
|
| 269 |
)
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
except FileNotFoundError:
|
| 279 |
st.error("config/substrate_settings.json not found")
|
| 280 |
|
|
@@ -290,47 +292,48 @@ with st.sidebar:
|
|
| 290 |
help="When on: estimate cell region from force map and use it for metrics (red contour). When off: use entire map.",
|
| 291 |
)
|
| 292 |
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
display_mode = "Default"
|
| 303 |
-
clamp_only = True
|
| 304 |
-
else:
|
| 305 |
-
mn_col, mx_col = st.columns(2)
|
| 306 |
-
with mn_col:
|
| 307 |
-
clip_min = st.number_input(
|
| 308 |
-
"Min",
|
| 309 |
-
min_value=0.0,
|
| 310 |
-
max_value=1.0,
|
| 311 |
-
value=0.0,
|
| 312 |
-
step=0.01,
|
| 313 |
-
format="%.2f",
|
| 314 |
-
key="s2f_clip_min",
|
| 315 |
-
help="Lower bound of the display range (0–1).",
|
| 316 |
-
)
|
| 317 |
-
with mx_col:
|
| 318 |
-
clip_max = st.number_input(
|
| 319 |
-
"Max",
|
| 320 |
-
min_value=0.0,
|
| 321 |
-
max_value=1.0,
|
| 322 |
-
value=1.0,
|
| 323 |
-
step=0.01,
|
| 324 |
-
format="%.2f",
|
| 325 |
-
key="s2f_clip_max",
|
| 326 |
-
help="Upper bound of the display range (0–1).",
|
| 327 |
-
)
|
| 328 |
-
if clip_min >= clip_max:
|
| 329 |
-
st.warning("Min must be less than max. Using 0.00–1.00 for display.")
|
| 330 |
clip_min, clip_max = 0.0, 1.0
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
|
| 335 |
cm_col_lbl, cm_col_sb = st.columns([1, 2])
|
| 336 |
with cm_col_lbl:
|
|
|
|
| 222 |
</div>
|
| 223 |
""", unsafe_allow_html=True)
|
| 224 |
|
| 225 |
+
with st.container(border=False, key="s2f_grp_model"):
|
| 226 |
+
model_type = st.radio(
|
| 227 |
+
"Model type",
|
| 228 |
+
["single_cell", "spheroid"],
|
| 229 |
+
format_func=lambda x: MODEL_TYPE_LABELS[x],
|
| 230 |
+
horizontal=False,
|
| 231 |
+
help="Single cell: substrate-aware force prediction. Spheroid: spheroid force maps.",
|
| 232 |
+
)
|
| 233 |
|
| 234 |
+
ckp_folder = get_ckp_folder(ckp_base, model_type)
|
| 235 |
+
ckp_files = list_files_in_folder(ckp_folder, ".pth")
|
| 236 |
+
ckp_subfolder_name = model_subfolder(model_type)
|
| 237 |
|
| 238 |
+
if ckp_files:
|
| 239 |
+
checkpoint = st.selectbox(
|
| 240 |
+
"Checkpoint",
|
| 241 |
+
ckp_files,
|
| 242 |
+
key=f"checkpoint_{model_type}",
|
| 243 |
+
help=f"Select a .pth file from ckp/{ckp_subfolder_name}/",
|
| 244 |
+
)
|
| 245 |
+
else:
|
| 246 |
+
st.warning(f"No .pth files in ckp/{ckp_subfolder_name}/. Add checkpoints to load.")
|
| 247 |
+
checkpoint = None
|
| 248 |
|
| 249 |
substrate_config = None
|
| 250 |
substrate_val = DEFAULT_SUBSTRATE
|
| 251 |
use_manual = True
|
| 252 |
if model_type == "single_cell":
|
| 253 |
try:
|
| 254 |
+
with st.container(border=False, key="s2f_grp_conditions"):
|
| 255 |
+
st.markdown('<p style="font-size: 0.95rem; font-weight: 500; margin-bottom: 0.5rem;">Conditions</p>', unsafe_allow_html=True)
|
| 256 |
+
conditions_source = st.radio(
|
| 257 |
+
"Conditions",
|
| 258 |
+
["From config", "Manually"],
|
| 259 |
+
horizontal=True,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
label_visibility="collapsed",
|
| 261 |
)
|
| 262 |
+
from_config = conditions_source == "From config"
|
| 263 |
+
if from_config:
|
| 264 |
+
substrate_config = None
|
| 265 |
+
substrates = list_substrates()
|
| 266 |
+
substrate_val = st.selectbox(
|
| 267 |
+
"Conditions (from config)",
|
| 268 |
+
substrates,
|
| 269 |
+
help="Select a preset from config/substrate_settings.json",
|
| 270 |
+
label_visibility="collapsed",
|
| 271 |
+
)
|
| 272 |
+
use_manual = False
|
| 273 |
+
else:
|
| 274 |
+
manual_pixelsize = st.number_input("Pixel size (µm/px)", min_value=0.1, max_value=50.0,
|
| 275 |
+
value=3.0769, step=0.1, format="%.4f")
|
| 276 |
+
manual_young = st.number_input("Pascals", min_value=100.0, max_value=100000.0,
|
| 277 |
+
value=6000.0, step=100.0, format="%.0f")
|
| 278 |
+
substrate_config = {"pixelsize": manual_pixelsize, "young": manual_young}
|
| 279 |
+
use_manual = True
|
| 280 |
except FileNotFoundError:
|
| 281 |
st.error("config/substrate_settings.json not found")
|
| 282 |
|
|
|
|
| 292 |
help="When on: estimate cell region from force map and use it for metrics (red contour). When off: use entire map.",
|
| 293 |
)
|
| 294 |
|
| 295 |
+
with st.container(border=False, key="s2f_grp_force"):
|
| 296 |
+
force_scale_mode = st.radio(
|
| 297 |
+
"Force scale",
|
| 298 |
+
["Default", "Range"],
|
| 299 |
+
horizontal=True,
|
| 300 |
+
key="s2f_force_scale",
|
| 301 |
+
help="Default: display forces on the full 0–1 scale. Range: set a sub-range; values outside are zeroed and the rest is stretched to the colormap.",
|
| 302 |
+
)
|
| 303 |
+
if force_scale_mode == "Default":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
clip_min, clip_max = 0.0, 1.0
|
| 305 |
+
display_mode = "Default"
|
| 306 |
+
clamp_only = True
|
| 307 |
+
else:
|
| 308 |
+
mn_col, mx_col = st.columns(2)
|
| 309 |
+
with mn_col:
|
| 310 |
+
clip_min = st.number_input(
|
| 311 |
+
"Min",
|
| 312 |
+
min_value=0.0,
|
| 313 |
+
max_value=1.0,
|
| 314 |
+
value=0.0,
|
| 315 |
+
step=0.01,
|
| 316 |
+
format="%.2f",
|
| 317 |
+
key="s2f_clip_min",
|
| 318 |
+
help="Lower bound of the display range (0–1).",
|
| 319 |
+
)
|
| 320 |
+
with mx_col:
|
| 321 |
+
clip_max = st.number_input(
|
| 322 |
+
"Max",
|
| 323 |
+
min_value=0.0,
|
| 324 |
+
max_value=1.0,
|
| 325 |
+
value=1.0,
|
| 326 |
+
step=0.01,
|
| 327 |
+
format="%.2f",
|
| 328 |
+
key="s2f_clip_max",
|
| 329 |
+
help="Upper bound of the display range (0–1).",
|
| 330 |
+
)
|
| 331 |
+
if clip_min >= clip_max:
|
| 332 |
+
st.warning("Min must be less than max. Using 0.00–1.00 for display.")
|
| 333 |
+
clip_min, clip_max = 0.0, 1.0
|
| 334 |
+
display_mode = "Range"
|
| 335 |
+
clamp_only = False
|
| 336 |
+
min_percentile, max_percentile = 0, 100
|
| 337 |
|
| 338 |
cm_col_lbl, cm_col_sb = st.columns([1, 2])
|
| 339 |
with cm_col_lbl:
|
S2FApp/static/s2f_styles.css
CHANGED
|
@@ -6,6 +6,11 @@
|
|
| 6 |
--s2f-primary-rgb: 13, 148, 136;
|
| 7 |
/* Space for Streamlit’s fixed top toolbar (Deploy / menu). Set when header exists (see below). */
|
| 8 |
--s2f-streamlit-header-offset: 0px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
/* Streamlit renders a fixed header; without this offset, the sidebar and first main block sit underneath it. */
|
| 11 |
.stApp:has(header[data-testid="stHeader"]) {
|
|
@@ -185,9 +190,8 @@ section[data-testid="stSidebar"] [data-testid="stWidgetLabel"] p {
|
|
| 185 |
display: flex;
|
| 186 |
align-items: center;
|
| 187 |
gap: 10px;
|
| 188 |
-
padding-bottom: 0.
|
| 189 |
-
margin-bottom: 0.
|
| 190 |
-
border-bottom: 1px solid #e2e8f0;
|
| 191 |
}
|
| 192 |
.sidebar-brand .brand-icon {
|
| 193 |
font-size: 1.6rem;
|
|
@@ -200,6 +204,36 @@ section[data-testid="stSidebar"] [data-testid="stWidgetLabel"] p {
|
|
| 200 |
letter-spacing: -0.01em;
|
| 201 |
}
|
| 202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
/* === Metric cards === */
|
| 204 |
[data-testid="stMetric"] {
|
| 205 |
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%);
|
|
|
|
| 6 |
--s2f-primary-rgb: 13, 148, 136;
|
| 7 |
/* Space for Streamlit’s fixed top toolbar (Deploy / menu). Set when header exists (see below). */
|
| 8 |
--s2f-streamlit-header-offset: 0px;
|
| 9 |
+
/* Sidebar grouped blocks: faint surface, no heavy chrome */
|
| 10 |
+
--s2f-sidebar-panel-bg: rgba(255, 255, 255, 0.42);
|
| 11 |
+
--s2f-sidebar-panel-border: rgba(203, 213, 225, 0.34);
|
| 12 |
+
/* Match Streamlit’s sidebar horizontal padding so panels can full-bleed the track */
|
| 13 |
+
--s2f-sidebar-inline-inset: 1rem;
|
| 14 |
}
|
| 15 |
/* Streamlit renders a fixed header; without this offset, the sidebar and first main block sit underneath it. */
|
| 16 |
.stApp:has(header[data-testid="stHeader"]) {
|
|
|
|
| 190 |
display: flex;
|
| 191 |
align-items: center;
|
| 192 |
gap: 10px;
|
| 193 |
+
padding-bottom: 0.65rem;
|
| 194 |
+
margin-bottom: 0.35rem;
|
|
|
|
| 195 |
}
|
| 196 |
.sidebar-brand .brand-icon {
|
| 197 |
font-size: 1.6rem;
|
|
|
|
| 204 |
letter-spacing: -0.01em;
|
| 205 |
}
|
| 206 |
|
| 207 |
+
/*
|
| 208 |
+
* Sidebar panels (model+ckp, conditions, force scale): horizontal bands — faded fill
|
| 209 |
+
* with top/bottom dividers; stretch edge-to-edge in the sidebar (bleed past content padding).
|
| 210 |
+
*/
|
| 211 |
+
section[data-testid="stSidebar"] .st-key-s2f_grp_model,
|
| 212 |
+
section[data-testid="stSidebar"] .st-key-s2f_grp_conditions,
|
| 213 |
+
section[data-testid="stSidebar"] .st-key-s2f_grp_force {
|
| 214 |
+
box-sizing: border-box !important;
|
| 215 |
+
margin-left: calc(-1 * var(--s2f-sidebar-inline-inset)) !important;
|
| 216 |
+
margin-right: calc(-1 * var(--s2f-sidebar-inline-inset)) !important;
|
| 217 |
+
margin-bottom: 0.45rem !important;
|
| 218 |
+
width: calc(100% + 2 * var(--s2f-sidebar-inline-inset)) !important;
|
| 219 |
+
max-width: none !important;
|
| 220 |
+
padding: 0.58rem var(--s2f-sidebar-inline-inset) 0.62rem !important;
|
| 221 |
+
border-radius: 0 !important;
|
| 222 |
+
background: var(--s2f-sidebar-panel-bg) !important;
|
| 223 |
+
border: none !important;
|
| 224 |
+
border-top: 1px solid var(--s2f-sidebar-panel-border) !important;
|
| 225 |
+
border-bottom: 1px solid var(--s2f-sidebar-panel-border) !important;
|
| 226 |
+
box-shadow: none !important;
|
| 227 |
+
}
|
| 228 |
+
/* Inner layout flex — no inner frame */
|
| 229 |
+
section[data-testid="stSidebar"] .st-key-s2f_grp_model > div,
|
| 230 |
+
section[data-testid="stSidebar"] .st-key-s2f_grp_conditions > div,
|
| 231 |
+
section[data-testid="stSidebar"] .st-key-s2f_grp_force > div {
|
| 232 |
+
border: none !important;
|
| 233 |
+
box-shadow: none !important;
|
| 234 |
+
background: transparent !important;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
/* === Metric cards === */
|
| 238 |
[data-testid="stMetric"] {
|
| 239 |
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%);
|
S2FApp/ui/result_display.py
CHANGED
|
@@ -116,14 +116,7 @@ def render_batch_results(batch_results, colormap_name="Jet", display_mode="Defau
|
|
| 116 |
)
|
| 117 |
with rem_hm_cols[i % min(5, len(remaining_results))]:
|
| 118 |
st.image(hm_rgb, caption=r["key_img"], use_container_width=True)
|
| 119 |
-
render_horizontal_colorbar(
|
| 120 |
-
colormap_name, clip_min, clip_max, is_rescale_b,
|
| 121 |
-
caption=(
|
| 122 |
-
"Ticks = force values in your selected [min, max] range; the strip uses the full colormap for the rescaled image (low → high)."
|
| 123 |
-
if is_rescale_b
|
| 124 |
-
else None
|
| 125 |
-
),
|
| 126 |
-
)
|
| 127 |
# Table
|
| 128 |
st.dataframe(
|
| 129 |
{h: [r[i] for r in rows] for i, h in enumerate(headers)},
|
|
|
|
| 116 |
)
|
| 117 |
with rem_hm_cols[i % min(5, len(remaining_results))]:
|
| 118 |
st.image(hm_rgb, caption=r["key_img"], use_container_width=True)
|
| 119 |
+
render_horizontal_colorbar(colormap_name, clip_min, clip_max, is_rescale_b)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
# Table
|
| 121 |
st.dataframe(
|
| 122 |
{h: [r[i] for r in rows] for i, h in enumerate(headers)},
|