Spaces:
Running
Running
more stable options
Browse files- app.py +0 -4
- ui/result_display.py +36 -9
- utils/segmentation.py +9 -1
app.py
CHANGED
|
@@ -222,8 +222,6 @@ with st.sidebar:
|
|
| 222 |
</div>
|
| 223 |
""", unsafe_allow_html=True)
|
| 224 |
|
| 225 |
-
st.markdown('<div class="sidebar-section"><span class="section-title">Model</span></div>', unsafe_allow_html=True)
|
| 226 |
-
|
| 227 |
model_type = st.radio(
|
| 228 |
"Model type",
|
| 229 |
["single_cell", "spheroid"],
|
|
@@ -280,8 +278,6 @@ with st.sidebar:
|
|
| 280 |
except FileNotFoundError:
|
| 281 |
st.error("config/substrate_settings.json not found")
|
| 282 |
|
| 283 |
-
st.markdown('<div class="sidebar-section"><span class="section-title">Analysis</span></div>', unsafe_allow_html=True)
|
| 284 |
-
|
| 285 |
batch_mode = st.toggle(
|
| 286 |
"Batch mode",
|
| 287 |
value=False,
|
|
|
|
| 222 |
</div>
|
| 223 |
""", unsafe_allow_html=True)
|
| 224 |
|
|
|
|
|
|
|
| 225 |
model_type = st.radio(
|
| 226 |
"Model type",
|
| 227 |
["single_cell", "spheroid"],
|
|
|
|
| 278 |
except FileNotFoundError:
|
| 279 |
st.error("config/substrate_settings.json not found")
|
| 280 |
|
|
|
|
|
|
|
| 281 |
batch_mode = st.toggle(
|
| 282 |
"Batch mode",
|
| 283 |
value=False,
|
ui/result_display.py
CHANGED
|
@@ -24,6 +24,9 @@ from ui.measure_tool import (
|
|
| 24 |
|
| 25 |
# Histogram bar color (matches static/s2f_styles.css accent)
|
| 26 |
_HISTOGRAM_ACCENT = "#0d9488"
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
|
| 29 |
def render_batch_results(batch_results, colormap_name="Jet", display_mode="Default",
|
|
@@ -70,25 +73,49 @@ def render_batch_results(batch_results, colormap_name="Jet", display_mode="Defau
|
|
| 70 |
csv_rows.append([os.path.splitext(key)[0]] + row[1:])
|
| 71 |
st.markdown('<div class="result-label"><span class="result-badge input">INPUT</span> Bright-field images</div>', unsafe_allow_html=True)
|
| 72 |
n_cols = min(5, len(batch_results))
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
| 75 |
img = r["img"]
|
| 76 |
if img.ndim == 2:
|
| 77 |
img_rgb = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
|
| 78 |
else:
|
| 79 |
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 80 |
-
with bf_cols[i % n_cols]:
|
| 81 |
st.image(img_rgb, caption=r["key_img"], use_container_width=True)
|
| 82 |
is_rescale_b = display_mode == "Range" and clip_max > clip_min and not (clip_min == 0 and clip_max == 1)
|
| 83 |
st.markdown('<div class="result-label"><span class="result-badge output">OUTPUT</span> Predicted force maps</div>', unsafe_allow_html=True)
|
| 84 |
-
hm_cols = st.columns(n_cols)
|
| 85 |
-
for i, r in enumerate(
|
| 86 |
hm_rgb = heatmap_to_rgb_with_contour(
|
| 87 |
r["_display_heatmap"], colormap_name,
|
| 88 |
r.get("_cell_mask") if auto_cell_boundary else None,
|
| 89 |
)
|
| 90 |
-
with hm_cols[i % n_cols]:
|
| 91 |
st.image(hm_rgb, caption=r["key_img"], use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
render_horizontal_colorbar(
|
| 93 |
colormap_name, clip_min, clip_max, is_rescale_b,
|
| 94 |
caption=(
|
|
@@ -114,7 +141,7 @@ def render_batch_results(batch_results, colormap_name="Jet", display_mode="Defau
|
|
| 114 |
if len(vals) > 0:
|
| 115 |
fig = go.Figure(data=[go.Histogram(x=vals, nbinsx=50, marker_color=_HISTOGRAM_ACCENT)])
|
| 116 |
fig.update_layout(
|
| 117 |
-
height=
|
| 118 |
xaxis_title="Force value", yaxis_title="Count",
|
| 119 |
showlegend=False,
|
| 120 |
)
|
|
@@ -214,7 +241,7 @@ def render_result_display(img, raw_heatmap, display_heatmap, pixel_sum, force, k
|
|
| 214 |
colorbar=colorbar_cfg), row=1, col=2)
|
| 215 |
add_cell_contour_to_fig(fig_pl, cell_mask, row=1, col=2)
|
| 216 |
fig_pl.update_layout(
|
| 217 |
-
height=
|
| 218 |
margin=dict(l=10, r=10, t=10, b=10),
|
| 219 |
xaxis=dict(scaleanchor="y", scaleratio=1),
|
| 220 |
xaxis2=dict(scaleanchor="y2", scaleratio=1),
|
|
@@ -264,7 +291,7 @@ def render_result_display(img, raw_heatmap, display_heatmap, pixel_sum, force, k
|
|
| 264 |
st.markdown("**Histogram**")
|
| 265 |
hist_fig = go.Figure(data=[go.Histogram(x=vals, nbinsx=50, marker_color=_HISTOGRAM_ACCENT)])
|
| 266 |
hist_fig.update_layout(
|
| 267 |
-
height=
|
| 268 |
xaxis_title="Force value", yaxis_title="Count",
|
| 269 |
showlegend=False,
|
| 270 |
)
|
|
|
|
| 24 |
|
| 25 |
# Histogram bar color (matches static/s2f_styles.css accent)
|
| 26 |
_HISTOGRAM_ACCENT = "#0d9488"
|
| 27 |
+
_RESULT_FIG_HEIGHT = 320
|
| 28 |
+
_HISTOGRAM_HEIGHT = 180
|
| 29 |
+
_BATCH_PREVIEW_LIMIT = 3
|
| 30 |
|
| 31 |
|
| 32 |
def render_batch_results(batch_results, colormap_name="Jet", display_mode="Default",
|
|
|
|
| 73 |
csv_rows.append([os.path.splitext(key)[0]] + row[1:])
|
| 74 |
st.markdown('<div class="result-label"><span class="result-badge input">INPUT</span> Bright-field images</div>', unsafe_allow_html=True)
|
| 75 |
n_cols = min(5, len(batch_results))
|
| 76 |
+
preview_count = min(_BATCH_PREVIEW_LIMIT, len(batch_results))
|
| 77 |
+
preview_results = batch_results[:preview_count]
|
| 78 |
+
remaining_results = batch_results[preview_count:]
|
| 79 |
+
bf_cols = st.columns(min(n_cols, preview_count))
|
| 80 |
+
for i, r in enumerate(preview_results):
|
| 81 |
img = r["img"]
|
| 82 |
if img.ndim == 2:
|
| 83 |
img_rgb = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
|
| 84 |
else:
|
| 85 |
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 86 |
+
with bf_cols[i % min(n_cols, preview_count)]:
|
| 87 |
st.image(img_rgb, caption=r["key_img"], use_container_width=True)
|
| 88 |
is_rescale_b = display_mode == "Range" and clip_max > clip_min and not (clip_min == 0 and clip_max == 1)
|
| 89 |
st.markdown('<div class="result-label"><span class="result-badge output">OUTPUT</span> Predicted force maps</div>', unsafe_allow_html=True)
|
| 90 |
+
hm_cols = st.columns(min(n_cols, preview_count))
|
| 91 |
+
for i, r in enumerate(preview_results):
|
| 92 |
hm_rgb = heatmap_to_rgb_with_contour(
|
| 93 |
r["_display_heatmap"], colormap_name,
|
| 94 |
r.get("_cell_mask") if auto_cell_boundary else None,
|
| 95 |
)
|
| 96 |
+
with hm_cols[i % min(n_cols, preview_count)]:
|
| 97 |
st.image(hm_rgb, caption=r["key_img"], use_container_width=True)
|
| 98 |
+
if remaining_results:
|
| 99 |
+
with st.expander(f"Show remaining batch previews ({len(remaining_results)})", expanded=False):
|
| 100 |
+
st.markdown('<div class="result-label"><span class="result-badge input">INPUT</span> Remaining bright-field images</div>', unsafe_allow_html=True)
|
| 101 |
+
rem_bf_cols = st.columns(min(5, len(remaining_results)))
|
| 102 |
+
for i, r in enumerate(remaining_results):
|
| 103 |
+
img = r["img"]
|
| 104 |
+
if img.ndim == 2:
|
| 105 |
+
img_rgb = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
|
| 106 |
+
else:
|
| 107 |
+
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 108 |
+
with rem_bf_cols[i % min(5, len(remaining_results))]:
|
| 109 |
+
st.image(img_rgb, caption=r["key_img"], use_container_width=True)
|
| 110 |
+
st.markdown('<div class="result-label"><span class="result-badge output">OUTPUT</span> Remaining predicted force maps</div>', unsafe_allow_html=True)
|
| 111 |
+
rem_hm_cols = st.columns(min(5, len(remaining_results)))
|
| 112 |
+
for i, r in enumerate(remaining_results):
|
| 113 |
+
hm_rgb = heatmap_to_rgb_with_contour(
|
| 114 |
+
r["_display_heatmap"], colormap_name,
|
| 115 |
+
r.get("_cell_mask") if auto_cell_boundary else None,
|
| 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=(
|
|
|
|
| 141 |
if len(vals) > 0:
|
| 142 |
fig = go.Figure(data=[go.Histogram(x=vals, nbinsx=50, marker_color=_HISTOGRAM_ACCENT)])
|
| 143 |
fig.update_layout(
|
| 144 |
+
height=_HISTOGRAM_HEIGHT, margin=dict(l=40, r=20, t=10, b=40),
|
| 145 |
xaxis_title="Force value", yaxis_title="Count",
|
| 146 |
showlegend=False,
|
| 147 |
)
|
|
|
|
| 241 |
colorbar=colorbar_cfg), row=1, col=2)
|
| 242 |
add_cell_contour_to_fig(fig_pl, cell_mask, row=1, col=2)
|
| 243 |
fig_pl.update_layout(
|
| 244 |
+
height=_RESULT_FIG_HEIGHT,
|
| 245 |
margin=dict(l=10, r=10, t=10, b=10),
|
| 246 |
xaxis=dict(scaleanchor="y", scaleratio=1),
|
| 247 |
xaxis2=dict(scaleanchor="y2", scaleratio=1),
|
|
|
|
| 291 |
st.markdown("**Histogram**")
|
| 292 |
hist_fig = go.Figure(data=[go.Histogram(x=vals, nbinsx=50, marker_color=_HISTOGRAM_ACCENT)])
|
| 293 |
hist_fig.update_layout(
|
| 294 |
+
height=_HISTOGRAM_HEIGHT, margin=dict(l=40, r=20, t=20, b=40),
|
| 295 |
xaxis_title="Force value", yaxis_title="Count",
|
| 296 |
showlegend=False,
|
| 297 |
)
|
utils/segmentation.py
CHANGED
|
@@ -45,7 +45,15 @@ def estimate_cell_mask(heatmap, sigma=2, min_size=200, exclude_full_image=True,
|
|
| 45 |
# Morphological cleanup
|
| 46 |
mask = closing(mask, disk(5)).astype(np.uint8)
|
| 47 |
mask = opening(mask, disk(3)).astype(np.uint8)
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
# Select component(s): optionally exclude full-image background, then merge
|
| 51 |
# all significant components (handles multiple disconnected force regions)
|
|
|
|
| 45 |
# Morphological cleanup
|
| 46 |
mask = closing(mask, disk(5)).astype(np.uint8)
|
| 47 |
mask = opening(mask, disk(3)).astype(np.uint8)
|
| 48 |
+
mask_bool = mask.astype(bool)
|
| 49 |
+
min_size_int = max(int(min_size), 0)
|
| 50 |
+
# skimage >=0.26 deprecates `min_size` in favor of `max_size` (inclusive threshold).
|
| 51 |
+
# Use `min_size - 1` so behavior stays equivalent to the prior strict `< min_size` rule.
|
| 52 |
+
try:
|
| 53 |
+
mask_bool = remove_small_objects(mask_bool, max_size=max(min_size_int - 1, 0))
|
| 54 |
+
except TypeError:
|
| 55 |
+
mask_bool = remove_small_objects(mask_bool, min_size=min_size_int)
|
| 56 |
+
mask = mask_bool.astype(np.uint8)
|
| 57 |
|
| 58 |
# Select component(s): optionally exclude full-image background, then merge
|
| 59 |
# all significant components (handles multiple disconnected force regions)
|