osherr commited on
Commit
5a74339
Β·
verified Β·
1 Parent(s): ee3bfb9

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +67 -46
src/streamlit_app.py CHANGED
@@ -25,10 +25,50 @@ st.set_page_config(layout="wide", page_title="Prior2DSM Pro", page_icon="πŸ›°οΈ
25
  if 'bbox_points' not in st.session_state:
26
  st.session_state.bbox_points = []
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  @st.cache_resource
29
  def load_backbones(repo_id, dav_file, dino_file, m_size):
30
  device = "cuda" if torch.cuda.is_available() else "cpu"
31
- # 1. Depth Anything V2
32
  dav_path = hf_hub_download(repo_id=repo_id, filename=dav_file)
33
  from depth_anything_v2.dpt import DepthAnythingV2
34
  configs = {'vits': {'encoder': 'vits', 'features': 64, 'out_channels': [48, 96, 192, 384]},
@@ -38,17 +78,15 @@ def load_backbones(repo_id, dav_file, dino_file, m_size):
38
  dav_model.load_state_dict(torch.load(dav_path, map_location="cpu", weights_only=True))
39
  dav_model = dav_model.to(device).eval()
40
 
41
- # 2. DINOv3 Encoder (ViT-L/16)
42
  dino_path = hf_hub_download(repo_id=repo_id, filename=dino_file)
43
  from dinov3.models.vision_transformer import DinoVisionTransformer
44
  dino_model = DinoVisionTransformer(img_size=1024, patch_size=16, embed_dim=1024, depth=24, num_heads=16, qkv_bias=True).to(device).eval()
45
  ckpt = torch.load(dino_path, map_location="cpu")
46
- if "state_dict" in ckpt: ckpt = ckpt["state_dict"]
47
- elif "model" in ckpt: ckpt = ckpt["model"]
48
- elif "teacher" in ckpt: ckpt = ckpt["teacher"]
49
  clean_ckpt = {k.replace("module.", "").replace("backbone.", "").replace("teacher.backbone.", ""): v for k, v in ckpt.items()}
50
  dino_model.load_state_dict(clean_ckpt, strict=False)
51
-
52
  return dav_model, dino_model, device
53
 
54
  # ================= SIDEBAR =================
@@ -73,53 +111,36 @@ if rgb_file and prior_file:
73
  prior_data = src.read(1).astype(np.float32)
74
  h_f, w_f = prior_data.shape
75
 
76
- # Pre-processing at specific grid resolutions
77
  img_448 = cv2.resize(rgb_raw.transpose(1, 2, 0), (448, 448))
78
- img_1024 = cv2.resize(rgb_raw.transpose(1, 2, 0), (1024, 1024))
79
-
80
- with st.spinner("Processing Backbones..."):
81
- # 1. Depth (DAV2)
82
  dav_in = torch.from_numpy(img_448).permute(2, 0, 1).unsqueeze(0).to(device).float() / 255.0
83
  with torch.no_grad():
84
  rel_depth = dav_model(dav_in)
85
  if isinstance(rel_depth, (list, tuple)): rel_depth = rel_depth[-1]
86
  rel_depth = F.interpolate(rel_depth.unsqueeze(1), size=(h_f, w_f), mode="bilinear").squeeze().cpu().numpy()
87
-
88
- # 2. DINO Features (DINOv3) - Using high-level forward_features to avoid AssertionErrors
 
 
89
  dino_in = (torch.from_numpy(img_1024).permute(2, 0, 1).unsqueeze(0).to(device).float() / 255.0 - torch.tensor(IMAGENET_MEAN, device=device).view(1,3,1,1)) / torch.tensor(IMAGENET_STD, device=device).view(1,3,1,1)
90
-
91
- with torch.no_grad():
92
- # forward_features handles RoPE and masks correctly
93
- features = dino_model.forward_features(dino_in)
94
- if isinstance(features, (list, tuple)):
95
- tokens = features[0] # Typical DINOv3 returns (tokens, metadata)
96
- else:
97
- tokens = features
98
-
99
- # Extract only the patch tokens (ignoring CLS/Registers)
100
- # DINOv3 often uses 1 CLS + N patches
101
- patch_tokens = tokens[:, 1:, :].squeeze(0).cpu().numpy()
102
- Hp, Wp = 1024 // 16, 1024 // 16
103
-
104
- # PCA Generation
105
- pca = PCA(n_components=3, whiten=True)
106
- Z = pca.fit_transform(patch_tokens).reshape(Hp, Wp, 3)
107
- # Notebook Sigmoid 2*Z Style
108
- Z_rgb = 1.0 / (1.0 + np.exp(-2.0 * Z))
109
- pca_full = F.interpolate(torch.from_numpy(Z_rgb.transpose(2,0,1)).unsqueeze(0), size=(h_f, w_f), mode="bilinear").squeeze().numpy()
110
-
111
- # --- UI ---
112
- tab_point, tab_refine = st.tabs(["πŸ”¬ Point Diagnostic (Coarse)", "πŸš€ BBOX Refinement (Full)"])
113
 
 
114
  viz_rgb = np.clip(rgb_raw.transpose(1, 2, 0) / (np.percentile(rgb_raw, 98) + 1e-6), 0, 1)
115
  viz_small = (cv2.resize(viz_rgb, (640, 640)) * 255).astype(np.uint8)
116
 
 
 
 
117
  with tab_point:
 
118
  p_diag = streamlit_image_coordinates(viz_small, key="point_diag")
119
  if p_diag:
120
  tx, ty = int(p_diag['x']*(w_f/640)), int(p_diag['y']*(h_f/640))
121
  valid_logic = np.isfinite(prior_data)
122
- feat_norm = patch_tokens / (np.linalg.norm(patch_tokens, axis=1, keepdims=True) + 1e-6)
 
123
  tree = cKDTree(feat_norm[valid_logic.flatten()])
124
  target_feat = feat_norm[int(ty/h_f*Hp)*Wp + int(tx/w_f*Wp)]
125
  _, idx = tree.query(target_feat, k=min(100, tree.n))
@@ -128,28 +149,29 @@ if rgb_file and prior_file:
128
  k_rel, k_elev = c_rel[np.argsort(spatial_dists)[:60]], c_elev[np.argsort(spatial_dists)[:60]]
129
  reg = LinearRegression().fit(k_rel.reshape(-1, 1), k_elev)
130
  pred_h = reg.predict([[rel_depth[ty, tx]]])[0]
131
- st.metric("Estimated Elevation (Coarse)", f"{pred_h:.2f} m")
132
  fig, ax = plt.subplots(); ax.scatter(k_rel, k_elev, color='lawngreen'); ax.plot(k_rel, reg.predict(k_rel.reshape(-1,1)), 'b-'); st.pyplot(fig)
133
 
134
  with tab_refine:
 
135
  b_coords = streamlit_image_coordinates(viz_small, key="bbox_painter")
136
  if b_coords:
137
  st.session_state.bbox_points.append((int(b_coords['x']*(w_f/640)), int(b_coords['y']*(h_f/640))))
138
  st.rerun()
139
  if len(st.session_state.bbox_points) >= 2:
140
- if st.button("πŸš€ Start 4-Stage Optimization"):
141
- with st.spinner("Processing Stage 3 (Gradient) & Stage 4 (SSI)..."):
142
  mask = np.zeros((h_f, w_f), dtype=bool)
143
  for i in range(0, len(st.session_state.bbox_points)-1, 2):
144
  p1, p2 = st.session_state.bbox_points[i], st.session_state.bbox_points[i+1]
145
  mask[min(p1[1], p2[1]):max(p1[1], p2[1]), min(p1[0], p2[0]):max(p1[0], p2[0])] = True
146
 
147
  D_dav_t = torch.from_numpy(rel_depth)[None, None].to(device)
148
- D_prior_t = torch.from_numpy(np.nan_to_num(prior_data))[None, None].to(device)
149
- Anchor = torch.from_numpy((~mask).astype(np.float32))[None, None].to(device)
150
  D_opt = D_prior_t.clone().detach().requires_grad_(True)
151
  opt = torch.optim.Adam([D_opt], lr=8e-3)
152
- D_dav_s = ((D_dav_t - D_dav_t.mean()) / D_dav_t.std()) * D_prior_t.std() + D_prior_t.mean()
153
 
154
  for _ in range(300):
155
  opt.zero_grad()
@@ -160,7 +182,6 @@ if rgb_file and prior_file:
160
  res = D_opt.detach().squeeze().cpu().numpy()
161
  buf = BytesIO(); meta_full.update(count=1, dtype="float32")
162
  with rasterio.open(buf, "w", **meta_full) as dst: dst.write(res, 1)
163
- st.success("DSM Refinement Complete!")
164
- st.download_button("πŸ“₯ Download Final DSM", buf.getvalue(), "refined_full_res.tif")
165
  else:
166
- st.info("Upload Assets to begin.")
 
25
  if 'bbox_points' not in st.session_state:
26
  st.session_state.bbox_points = []
27
 
28
+ # ================= YOUR RESEARCH HELPERS (DUPLICATED) =================
29
+ @torch.no_grad()
30
+ def _final_patch_tokens_postnorm(encoder, x):
31
+ """ Direct duplicate from your research code to handle ViT-L dimensioning. """
32
+ B, _, H, W = x.shape
33
+ D = int(getattr(encoder, "embed_dim"))
34
+ ps = getattr(encoder, "patch_embed").patch_size
35
+ if isinstance(ps, (tuple, list)): ps = ps[0]
36
+
37
+ pe = encoder.patch_embed(x)
38
+ if pe.ndim == 4:
39
+ if pe.shape[1] == D:
40
+ Hp, Wp = pe.shape[-2], pe.shape[-1]
41
+ tokens = pe.flatten(2).transpose(1, 2)
42
+ elif pe.shape[-1] == D:
43
+ Hp, Wp = pe.shape[1], pe.shape[2]
44
+ tokens = pe.view(B, Hp * Wp, D)
45
+ else: raise RuntimeError(f"Unexpected patch_embed output shape {pe.shape}")
46
+ elif pe.ndim == 3:
47
+ Hp, Wp = H // ps, W // ps
48
+ tokens = pe if pe.shape[-1] == D else pe.transpose(1, 2).contiguous()
49
+ else: raise RuntimeError(f"patch_embed returned {pe.ndim}D tensor.")
50
+
51
+ cls = encoder.cls_token.expand(B, 1, D)
52
+ tokens = torch.cat([cls, tokens], dim=1)
53
+
54
+ if hasattr(encoder, "interpolate_pos_encoding"):
55
+ tokens = tokens + encoder.interpolate_pos_encoding(tokens, H, W)
56
+ if hasattr(encoder, "pos_drop"):
57
+ tokens = encoder.pos_drop(tokens)
58
+
59
+ out = tokens
60
+ for blk in encoder.blocks: out = blk(out)
61
+ if hasattr(encoder, "norm") and encoder.norm is not None: out = encoder.norm(out)
62
+
63
+ out = out.squeeze(0)
64
+ T, N = out.shape[0], Hp * Wp
65
+ n_special = T - N
66
+ return out[n_special:, :], Hp, Wp
67
+
68
  @st.cache_resource
69
  def load_backbones(repo_id, dav_file, dino_file, m_size):
70
  device = "cuda" if torch.cuda.is_available() else "cpu"
71
+ # Load Depth Anything V2
72
  dav_path = hf_hub_download(repo_id=repo_id, filename=dav_file)
73
  from depth_anything_v2.dpt import DepthAnythingV2
74
  configs = {'vits': {'encoder': 'vits', 'features': 64, 'out_channels': [48, 96, 192, 384]},
 
78
  dav_model.load_state_dict(torch.load(dav_path, map_location="cpu", weights_only=True))
79
  dav_model = dav_model.to(device).eval()
80
 
81
+ # Load DINOv3 (ViT-L/16)
82
  dino_path = hf_hub_download(repo_id=repo_id, filename=dino_file)
83
  from dinov3.models.vision_transformer import DinoVisionTransformer
84
  dino_model = DinoVisionTransformer(img_size=1024, patch_size=16, embed_dim=1024, depth=24, num_heads=16, qkv_bias=True).to(device).eval()
85
  ckpt = torch.load(dino_path, map_location="cpu")
86
+ for k in ["state_dict", "model", "teacher"]:
87
+ if k in ckpt: ckpt = ckpt[k]
 
88
  clean_ckpt = {k.replace("module.", "").replace("backbone.", "").replace("teacher.backbone.", ""): v for k, v in ckpt.items()}
89
  dino_model.load_state_dict(clean_ckpt, strict=False)
 
90
  return dav_model, dino_model, device
91
 
92
  # ================= SIDEBAR =================
 
111
  prior_data = src.read(1).astype(np.float32)
112
  h_f, w_f = prior_data.shape
113
 
114
+ # 1. Generate Neural Depth (Scaled to Input Size)
115
  img_448 = cv2.resize(rgb_raw.transpose(1, 2, 0), (448, 448))
116
+ with st.spinner("Processing Stage 1: Neural Depth..."):
 
 
 
117
  dav_in = torch.from_numpy(img_448).permute(2, 0, 1).unsqueeze(0).to(device).float() / 255.0
118
  with torch.no_grad():
119
  rel_depth = dav_model(dav_in)
120
  if isinstance(rel_depth, (list, tuple)): rel_depth = rel_depth[-1]
121
  rel_depth = F.interpolate(rel_depth.unsqueeze(1), size=(h_f, w_f), mode="bilinear").squeeze().cpu().numpy()
122
+
123
+ # 2. Generate DINO Features (1024px Grid)
124
+ img_1024 = cv2.resize(rgb_raw.transpose(1, 2, 0), (1024, 1024))
125
+ with st.spinner("Processing Stage 2: Semantic Tokens..."):
126
  dino_in = (torch.from_numpy(img_1024).permute(2, 0, 1).unsqueeze(0).to(device).float() / 255.0 - torch.tensor(IMAGENET_MEAN, device=device).view(1,3,1,1)) / torch.tensor(IMAGENET_STD, device=device).view(1,3,1,1)
127
+ patch_tokens, Hp, Wp = _final_patch_tokens_postnorm(dino_model, dino_in)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
+ # UI Visual Setup
130
  viz_rgb = np.clip(rgb_raw.transpose(1, 2, 0) / (np.percentile(rgb_raw, 98) + 1e-6), 0, 1)
131
  viz_small = (cv2.resize(viz_rgb, (640, 640)) * 255).astype(np.uint8)
132
 
133
+ # --- TABS ---
134
+ tab_point, tab_refine = st.tabs(["πŸ”¬ Point Diagnostic (Coarse)", "πŸš€ BBOX Refinement (Full)"])
135
+
136
  with tab_point:
137
+ st.write("Click for a **Stage 2 (Coarse)** metric estimation.")
138
  p_diag = streamlit_image_coordinates(viz_small, key="point_diag")
139
  if p_diag:
140
  tx, ty = int(p_diag['x']*(w_f/640)), int(p_diag['y']*(h_f/640))
141
  valid_logic = np.isfinite(prior_data)
142
+ feat_norm = patch_tokens.cpu().numpy()
143
+ feat_norm /= (np.linalg.norm(feat_norm, axis=1, keepdims=True) + 1e-6)
144
  tree = cKDTree(feat_norm[valid_logic.flatten()])
145
  target_feat = feat_norm[int(ty/h_f*Hp)*Wp + int(tx/w_f*Wp)]
146
  _, idx = tree.query(target_feat, k=min(100, tree.n))
 
149
  k_rel, k_elev = c_rel[np.argsort(spatial_dists)[:60]], c_elev[np.argsort(spatial_dists)[:60]]
150
  reg = LinearRegression().fit(k_rel.reshape(-1, 1), k_elev)
151
  pred_h = reg.predict([[rel_depth[ty, tx]]])[0]
152
+ st.metric("Estimated Elevation", f"{pred_h:.2f} m")
153
  fig, ax = plt.subplots(); ax.scatter(k_rel, k_elev, color='lawngreen'); ax.plot(k_rel, reg.predict(k_rel.reshape(-1,1)), 'b-'); st.pyplot(fig)
154
 
155
  with tab_refine:
156
+ st.write("Draw BBOX for **Stage 3 & 4** optimization.")
157
  b_coords = streamlit_image_coordinates(viz_small, key="bbox_painter")
158
  if b_coords:
159
  st.session_state.bbox_points.append((int(b_coords['x']*(w_f/640)), int(b_coords['y']*(h_f/640))))
160
  st.rerun()
161
  if len(st.session_state.bbox_points) >= 2:
162
+ if st.button("πŸš€ Execute Full Refinement (Original Size)"):
163
+ with st.spinner("Optimizing..."):
164
  mask = np.zeros((h_f, w_f), dtype=bool)
165
  for i in range(0, len(st.session_state.bbox_points)-1, 2):
166
  p1, p2 = st.session_state.bbox_points[i], st.session_state.bbox_points[i+1]
167
  mask[min(p1[1], p2[1]):max(p1[1], p2[1]), min(p1[0], p2[0]):max(p1[0], p2[0])] = True
168
 
169
  D_dav_t = torch.from_numpy(rel_depth)[None, None].to(device)
170
+ D_prior_t = torch.from_numpy(np.nan_to_num(prior_data)).to(device).unsqueeze(0).unsqueeze(0)
171
+ Anchor = torch.from_numpy((~mask).astype(np.float32)).to(device).unsqueeze(0).unsqueeze(0)
172
  D_opt = D_prior_t.clone().detach().requires_grad_(True)
173
  opt = torch.optim.Adam([D_opt], lr=8e-3)
174
+ D_dav_s = ((D_dav_t - D_dav_t.mean()) / D_dav_t.std()) * D_opt.std() + D_opt.mean()
175
 
176
  for _ in range(300):
177
  opt.zero_grad()
 
182
  res = D_opt.detach().squeeze().cpu().numpy()
183
  buf = BytesIO(); meta_full.update(count=1, dtype="float32")
184
  with rasterio.open(buf, "w", **meta_full) as dst: dst.write(res, 1)
185
+ st.download_button("πŸ“₯ Download Full-Res DSM", buf.getvalue(), "refined.tif")
 
186
  else:
187
+ st.info("Upload imagery to begin.")