DariusGiannoli
refactor: tab-based routing with two pipelines (Stereo+Depth & Generalisation)
a51a1a7
"""Generalisation Feature Lab β€” Stage 2 of the Generalisation pipeline."""
import streamlit as st
import cv2
import numpy as np
import plotly.graph_objects as go
from src.detectors.rce.features import REGISTRY
from src.models import BACKBONES
def render():
pipe = st.session_state.get("gen_pipeline")
if not pipe or "crop" not in pipe:
st.error("Please complete the **Data Lab** first!")
st.stop()
obj = pipe.get("crop_aug", pipe.get("crop"))
if obj is None:
st.error("No crop found. Go back to Data Lab and define a ROI.")
st.stop()
gray = cv2.cvtColor(obj, cv2.COLOR_BGR2GRAY)
st.title("πŸ”¬ Feature Lab: Physical Module Selection")
col_rce, col_cnn = st.columns([3, 2])
with col_rce:
st.header("🧬 RCE: Modular Physics Engine")
st.subheader("Select Active Modules")
active = {}
items = list(REGISTRY.items())
for row_start in range(0, len(items), 4):
row_items = items[row_start:row_start + 4]
m_cols = st.columns(4)
for col, (key, meta) in zip(m_cols, row_items):
active[key] = col.checkbox(meta["label"],
value=(key in ("intensity", "sobel", "spectral")),
key=f"gen_fl_{key}")
final_vector = []
viz_images = []
for key, meta in REGISTRY.items():
if active[key]:
vec, viz = meta["fn"](gray)
final_vector.extend(vec)
viz_images.append((meta["viz_title"], viz))
st.divider()
if viz_images:
for row_start in range(0, len(viz_images), 3):
row = viz_images[row_start:row_start + 3]
v_cols = st.columns(3)
for col, (title, img) in zip(v_cols, row):
col.image(img, caption=title, use_container_width=True)
else:
st.warning("No modules selected β€” vector is empty.")
st.write(f"### Current DNA Vector Size: **{len(final_vector)}**")
fig_vec = go.Figure(data=[go.Bar(y=final_vector, marker_color="#00d4ff")])
fig_vec.update_layout(title="Active Feature Vector (RCE Input)",
template="plotly_dark", height=300)
st.plotly_chart(fig_vec, use_container_width=True)
with col_cnn:
st.header("🧠 CNN: Static Architecture")
selected_cnn = st.selectbox("Compare against Model", list(BACKBONES.keys()),
key="gen_fl_cnn")
st.info("CNN features are fixed by pre-trained weights.")
with st.spinner(f"Loading {selected_cnn} and extracting activations..."):
try:
bmeta = BACKBONES[selected_cnn]
backbone = bmeta["loader"]()
layer_name = bmeta["hook_layer"]
act_maps = backbone.get_activation_maps(obj, n_maps=6)
st.caption(f"Hooked layer: `{layer_name}` β€” showing 6 of "
f"{len(act_maps)} channels")
act_cols = st.columns(3)
for i, amap in enumerate(act_maps):
act_cols[i % 3].image(amap, caption=f"Channel {i}",
use_container_width=True)
except Exception as e:
st.error(f"Could not load model: {e}")
st.divider()
st.markdown(f"""
**Analysis:**
- **Modularity:** RCE is **High** | CNN is **Zero**
- **Explainability:** RCE is **High** | CNN is **Low**
- **Compute Cost:** {len(final_vector)} floats | 512+ floats
""")
if st.button("πŸš€ Lock Modular Configuration", key="gen_fl_lock"):
if not final_vector:
st.error("Please select at least one module!")
else:
pipe["final_vector"] = np.array(final_vector)
pipe["active_modules"] = {k: v for k, v in active.items()}
st.session_state["gen_pipeline"] = pipe
st.success("Modular DNA Locked! Ready for Model Tuning.")