osherr commited on
Commit
ffb4a88
Β·
verified Β·
1 Parent(s): 937970d

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +134 -99
src/streamlit_app.py CHANGED
@@ -14,20 +14,26 @@ from scipy.spatial import cKDTree
14
  from huggingface_hub import hf_hub_download
15
  from streamlit_image_coordinates import streamlit_image_coordinates
16
 
17
- # --- Χ§Χ•Χ Χ€Χ™Χ’Χ•Χ¨Χ¦Χ™Χ” ---
18
  IMAGENET_MEAN = (0.485, 0.456, 0.406)
19
  IMAGENET_STD = (0.229, 0.224, 0.225)
20
 
21
- st.set_page_config(layout="wide", page_title="Prior2DSM Research Pro")
22
 
 
 
 
 
23
  @st.cache_resource
24
  def load_models(repo_id, dav_file, dino_file, m_size):
25
  device = "cuda" if torch.cuda.is_available() else "cpu"
26
  dav_path = hf_hub_download(repo_id=repo_id, filename=dav_file)
27
  from depth_anything_v2.dpt import DepthAnythingV2
28
- configs = {'vits': {'encoder': 'vits', 'features': 64, 'out_channels': [48, 96, 192, 384]},
29
- 'vitb': {'encoder': 'vitb', 'features': 128, 'out_channels': [96, 192, 384, 768]},
30
- 'vitl': {'encoder': 'vitl', 'features': 256, 'out_channels': [256, 512, 1024, 1024]}}
 
 
31
  dav_model = DepthAnythingV2(**configs[m_size])
32
  dav_model.load_state_dict(torch.load(dav_path, map_location="cpu", weights_only=True))
33
  dav_model = dav_model.to(device).eval()
@@ -39,11 +45,12 @@ def load_models(repo_id, dav_file, dino_file, m_size):
39
  if "state_dict" in ckpt: ckpt = ckpt["state_dict"]
40
  clean_ckpt = {k.replace("module.", "").replace("backbone.", "").replace("teacher.backbone.", ""): v for k, v in ckpt.items()}
41
  dino_model.load_state_dict(clean_ckpt, strict=False)
 
42
  return dav_model, dino_model, device
43
 
44
  @st.cache_data(show_spinner=False)
45
- def run_backbone_inference(_dav, _dino, rgb_raw, h_f, w_f, device):
46
- # 1. Depth Inference (Scaling to Input Size)
47
  img_448 = cv2.resize(rgb_raw.transpose(1, 2, 0), (448, 448))
48
  dav_in = torch.from_numpy(img_448).permute(2, 0, 1).unsqueeze(0).to(device).float() / 255.0
49
  with torch.no_grad():
@@ -51,142 +58,170 @@ def run_backbone_inference(_dav, _dino, rgb_raw, h_f, w_f, device):
51
  if isinstance(rel_depth, (list, tuple)): rel_depth = rel_depth[-1]
52
  rel_depth = F.interpolate(rel_depth.unsqueeze(1), size=(h_f, w_f), mode="bilinear").squeeze().cpu().numpy()
53
 
54
- # 2. DINO Features & PCA
55
  img_1024 = cv2.resize(rgb_raw.transpose(1, 2, 0), (1024, 1024))
56
  dino_in = (torch.from_numpy(img_1024).permute(2, 0, 1).unsqueeze(0).to(device).float() / 255.0 - \
57
  torch.tensor(IMAGENET_MEAN, device=device).view(1,3,1,1)) / torch.tensor(IMAGENET_STD, device=device).view(1,3,1,1)
58
-
59
  with torch.no_grad():
60
  features = _dino.forward_features(dino_in)
61
- # ΧͺΧ™Χ§Χ•ΧŸ ΧœΧ—Χ™ΧœΧ•Χ₯ Χ˜Χ•Χ§Χ Χ™Χ אם ΧžΧ•Χ—Χ–Χ¨ ΧžΧ™ΧœΧ•ΧŸ (dict)
62
  tokens = features.get('x_norm_patchtokens', features.get('main')) if isinstance(features, dict) else features[0]
63
- if tokens.shape[1] > 4096: # If CLS is present
64
- patch_tokens = tokens[:, 1:, :].squeeze(0).cpu().numpy()
65
- else:
66
- patch_tokens = tokens.squeeze(0).cpu().numpy()
67
 
68
- Hp, Wp = 1024 // 16, 1024 // 16
69
-
70
- # Generate PCA for visualization
71
  pca_obj = PCA(n_components=3, whiten=True)
72
- Z = pca_obj.fit_transform(patch_tokens).reshape(Hp, Wp, 3)
73
- Z_rgb = 1.0 / (1.0 + np.exp(-2.0 * Z)) # Sigmoid PCA
74
- pca_viz = F.interpolate(torch.from_numpy(Z_rgb.transpose(2,0,1)).unsqueeze(0).float(), size=(h_f, w_f), mode="bilinear").squeeze().numpy()
75
 
76
- return rel_depth, patch_tokens, pca_viz, Hp, Wp
77
 
78
- # ================= UI Sidebar =================
79
  with st.sidebar:
80
- st.header("πŸ“‚ Data Ingestion")
81
- rgb_file = st.file_uploader("Source RGB (TIF)", type=["tif", "tiff"])
82
- prior_file = st.file_uploader("Prior DSM (TIF)", type=["tif", "tiff"])
83
- mask_file = st.file_uploader("Change Mask (TIF)", type=["tif", "tiff"])
84
- mask_band = st.selectbox("Mask Band Index", [1, 2, 3], index=1)
85
  st.divider()
 
86
  repo = st.text_input("Repo ID", "osherr/Prior2DSM")
87
  dav_ckpt = st.text_input("DAV2 Weights", "depth_anything_v2_vitl.pth")
88
  dino_ckpt = st.text_input("DINO Weights", "dinov3_vitl16_pretrain_sat493m-eadcf0ff.pth")
89
- m_size = st.selectbox("DAV2 Encoder", ["vitl", "vitb", "vits"])
90
 
91
- # ================= Main App =================
92
  if rgb_file and prior_file and mask_file:
93
- dav_m, dino_m, dev = load_models(repo, dav_ckpt, dino_ckpt, m_size)
94
 
95
  with rasterio.open(BytesIO(rgb_file.read())) as src:
96
  rgb_raw = src.read([1, 2, 3])
 
97
  meta_full = src.profile
98
  with rasterio.open(BytesIO(prior_file.read())) as src:
99
  prior_data = src.read(1).astype(np.float32)
100
- h_f, w_f = prior_data.shape
101
  with rasterio.open(BytesIO(mask_file.getvalue())) as src:
102
  change_mask = src.read(mask_band) > 0
103
 
104
- # Running Stage 1 & 2
105
- rel_depth, patch_tokens, pca_viz, Hp, Wp = run_backbone_inference(dav_m, dino_m, rgb_raw, h_f, w_f, dev)
106
 
107
- # --- DIAGNOSTIC TABS ---
108
- st.header("πŸ”¬ Research Diagnostics")
109
- tab_vis, tab_knn, tab_refine = st.tabs(["πŸ–ΌοΈ Visual Maps", "🎯 KNN Analysis", "πŸš€ SSI Refinement"])
110
 
111
- with tab_vis:
112
- col1, col2, col3 = st.columns(3)
 
113
  viz_rgb = np.clip(rgb_raw.transpose(1, 2, 0) / (np.percentile(rgb_raw, 98) + 1e-6), 0, 1)
114
-
115
- with col1:
116
- st.subheader("Semantic PCA (DINO)")
117
- st.image(pca_viz.transpose(1, 2, 0), use_container_width=True)
118
- with col2:
119
- st.subheader("Relative Depth (DAV2)")
120
- fig_d, ax_d = plt.subplots()
121
- ax_d.imshow(rel_depth, cmap='magma')
122
- ax_d.axis('off')
123
- st.pyplot(fig_d)
124
- with col3:
125
- st.subheader("Change Mask")
126
- st.image(change_mask.astype(float), use_container_width=True)
127
-
128
- with tab_knn:
129
  viz_small = (cv2.resize(viz_rgb, (640, 640)) * 255).astype(np.uint8)
130
- col_c, col_r = st.columns([1, 1])
131
- with col_c:
132
- st.write("Click a point to see KNN consistency:")
133
  p_click = streamlit_image_coordinates(viz_small, key="diag_click")
134
 
135
  if p_click:
136
  tx, ty = int(p_click['x']*(w_f/640)), int(p_click['y']*(h_f/640))
137
- valid_logic = np.isfinite(prior_data) & (~change_mask)
138
- # Match PCA map to full resolution for retrieval
139
- pca_full = F.normalize(torch.from_numpy(pca_viz), p=2, dim=0).numpy()
140
- valid_pca = pca_full[:, valid_logic].T
141
- tree = cKDTree(valid_pca)
142
 
143
- target_pca = pca_full[:, ty, tx]
144
- _, idx = tree.query(target_pca, k=100)
 
 
 
145
 
146
- c_rel, c_elev = rel_depth[valid_logic][idx], prior_data[valid_logic][idx]
147
- reg = LinearRegression().fit(c_rel.reshape(-1, 1), c_elev)
148
- pred_h = reg.predict([[rel_depth[ty, tx]]])[0]
149
 
150
- with col_r:
151
- st.metric("KNN Predicted Elevation", f"{pred_h:.2f} m")
152
- fig_knn, ax_knn = plt.subplots()
153
- ax_knn.scatter(c_rel, c_elev, alpha=0.3, label='Semantic Anchors')
154
- ax_knn.plot(c_rel, reg.predict(c_rel.reshape(-1,1)), 'r-', label='Local Fit')
155
- ax_knn.scatter([rel_depth[ty, tx]], [pred_h], color='cyan', marker='*', s=200, label='Prediction')
156
- ax_knn.legend()
157
- st.pyplot(fig_knn)
158
-
159
- with tab_refine:
160
- if st.button("πŸš€ Run SSI Final Optimization"):
161
- with st.spinner("Injecting Fine Details..."):
162
- # Χ”ΧžΧ¨Χ” ΧœΧ˜Χ Χ–Χ•Χ¨Χ™Χ Χ•Χ Χ™ΧͺΧ•Χ§ ΧžΧ”Χ’Χ¨Χ£ (DETACH) ΧœΧžΧ Χ™Χ’Χͺ השגיאה
163
- D_dav_t = torch.from_numpy(rel_depth).to(dev).unsqueeze(0).unsqueeze(0).detach()
164
- D_prior_t = torch.from_numpy(np.nan_to_num(prior_data)).to(dev).unsqueeze(0).unsqueeze(0).detach()
165
- Anchor = torch.from_numpy((~change_mask).astype(np.float32)).to(dev).unsqueeze(0).unsqueeze(0).detach()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- # Χ”ΧžΧ©ΧͺΧ Χ” Χ”Χ™Χ—Χ™Χ“ Χ©Χ“Χ•Χ¨Χ© Χ’Χ¨Χ“Χ™ΧΧ Χ˜
168
- D_opt = D_prior_t.clone().requires_grad_(True)
169
- optimizer = torch.optim.Adam([D_opt], lr=8e-3)
 
 
170
 
171
- # Χ Χ¨ΧžΧ•Χœ Χ’Χ•ΧžΧ§ Χ™Χ—Χ‘Χ™ ΧœΧžΧ¨Χ—Χ‘ Χ”ΧžΧ˜Χ¨Χ™ (Alignment)
 
 
172
  D_dav_s = ((D_dav_t - D_dav_t.mean()) / (D_dav_t.std() + 1e-6)) * D_prior_t.std() + D_prior_t.mean()
173
- D_dav_s = D_dav_s.detach() # Χ—Χ©Χ•Χ‘ ΧžΧΧ•Χ“ ΧœΧžΧ Χ™Χ’Χͺ Backward שגיאה
174
 
175
  for _ in range(350):
176
- optimizer.zero_grad()
177
- # Loss 1: Anchor (Χ Χ’Χ™ΧœΧͺ אזורים יציבים)
178
- l_anchor = F.mse_loss(D_opt * Anchor, D_prior_t * Anchor)
179
- # Loss 2: Laplacian Detail (Χ”Χ–Χ¨Χ§Χͺ Χ˜Χ§Χ‘Χ˜Χ•Χ¨Χ”)
180
- l_geom = F.l1_loss(D_opt - F.avg_pool2d(D_opt, 5, 1, 2),
181
- D_dav_s - F.avg_pool2d(D_dav_s, 5, 1, 2))
182
-
183
- (1500 * l_anchor + 200 * l_geom).backward()
184
- optimizer.step()
185
 
186
  res = D_opt.detach().squeeze().cpu().numpy()
187
  buf = BytesIO(); meta_full.update(count=1, dtype="float32")
188
  with rasterio.open(buf, "w", **meta_full) as dst: dst.write(res, 1)
189
- st.success("Optimization Complete!")
190
- st.download_button("πŸ“₯ Download Refined DSM", buf.getvalue(), "refined_final.tif")
191
  else:
192
- st.info("אנא Χ”Χ’ΧœΧ” קבצים Χ›Χ“Χ™ ΧœΧ”ΧͺΧ—Χ™Χœ.")
 
14
  from huggingface_hub import hf_hub_download
15
  from streamlit_image_coordinates import streamlit_image_coordinates
16
 
17
+ # --- CONFIG & INITIALIZATION ---
18
  IMAGENET_MEAN = (0.485, 0.456, 0.406)
19
  IMAGENET_STD = (0.229, 0.224, 0.225)
20
 
21
+ st.set_page_config(layout="wide", page_title="Prior2DSM Research Suite")
22
 
23
+ if 'bbox_coords' not in st.session_state:
24
+ st.session_state.bbox_coords = []
25
+
26
+ # --- MODEL LOADING (Safe Init) ---
27
  @st.cache_resource
28
  def load_models(repo_id, dav_file, dino_file, m_size):
29
  device = "cuda" if torch.cuda.is_available() else "cpu"
30
  dav_path = hf_hub_download(repo_id=repo_id, filename=dav_file)
31
  from depth_anything_v2.dpt import DepthAnythingV2
32
+ configs = {
33
+ 'vits': {'encoder': 'vits', 'features': 64, 'out_channels': [48, 96, 192, 384]},
34
+ 'vitb': {'encoder': 'vitb', 'features': 128, 'out_channels': [96, 192, 384, 768]},
35
+ 'vitl': {'encoder': 'vitl', 'features': 256, 'out_channels': [256, 512, 1024, 1024]}
36
+ }
37
  dav_model = DepthAnythingV2(**configs[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()
 
45
  if "state_dict" in ckpt: ckpt = ckpt["state_dict"]
46
  clean_ckpt = {k.replace("module.", "").replace("backbone.", "").replace("teacher.backbone.", ""): v for k, v in ckpt.items()}
47
  dino_model.load_state_dict(clean_ckpt, strict=False)
48
+
49
  return dav_model, dino_model, device
50
 
51
  @st.cache_data(show_spinner=False)
52
+ def run_full_inference(_dav, _dino, rgb_raw, h_f, w_f, device):
53
+ # 1. DAV2 Depth
54
  img_448 = cv2.resize(rgb_raw.transpose(1, 2, 0), (448, 448))
55
  dav_in = torch.from_numpy(img_448).permute(2, 0, 1).unsqueeze(0).to(device).float() / 255.0
56
  with torch.no_grad():
 
58
  if isinstance(rel_depth, (list, tuple)): rel_depth = rel_depth[-1]
59
  rel_depth = F.interpolate(rel_depth.unsqueeze(1), size=(h_f, w_f), mode="bilinear").squeeze().cpu().numpy()
60
 
61
+ # 2. DINO PCA
62
  img_1024 = cv2.resize(rgb_raw.transpose(1, 2, 0), (1024, 1024))
63
  dino_in = (torch.from_numpy(img_1024).permute(2, 0, 1).unsqueeze(0).to(device).float() / 255.0 - \
64
  torch.tensor(IMAGENET_MEAN, device=device).view(1,3,1,1)) / torch.tensor(IMAGENET_STD, device=device).view(1,3,1,1)
 
65
  with torch.no_grad():
66
  features = _dino.forward_features(dino_in)
 
67
  tokens = features.get('x_norm_patchtokens', features.get('main')) if isinstance(features, dict) else features[0]
68
+ patch_tokens = tokens[:, 1:, :].squeeze(0).cpu().numpy()
 
 
 
69
 
 
 
 
70
  pca_obj = PCA(n_components=3, whiten=True)
71
+ Z = pca_obj.fit_transform(patch_tokens).reshape(64, 64, 3).transpose(2, 0, 1)
72
+ pca_full = F.interpolate(torch.from_numpy(Z).unsqueeze(0).float(), size=(h_f, w_f), mode="bilinear").squeeze(0)
73
+ pca_map = F.normalize(pca_full, p=2, dim=0).numpy()
74
 
75
+ return rel_depth, pca_map
76
 
77
+ # --- SIDEBAR ---
78
  with st.sidebar:
79
+ st.header("πŸ“‚ Research Data")
80
+ rgb_file = st.file_uploader("Source RGB", type=["tif", "tiff"])
81
+ prior_file = st.file_uploader("Prior DSM", type=["tif", "tiff"])
82
+ mask_file = st.file_uploader("Change Mask", type=["tif", "tiff"])
83
+ mask_band = st.selectbox("Mask Band", [1, 2, 3], index=1)
84
  st.divider()
85
+ K_val = st.slider("K Neighbors (Diagnostic)", 5, 50, 15)
86
  repo = st.text_input("Repo ID", "osherr/Prior2DSM")
87
  dav_ckpt = st.text_input("DAV2 Weights", "depth_anything_v2_vitl.pth")
88
  dino_ckpt = st.text_input("DINO Weights", "dinov3_vitl16_pretrain_sat493m-eadcf0ff.pth")
 
89
 
90
+ # --- MAIN LOGIC ---
91
  if rgb_file and prior_file and mask_file:
92
+ dav_m, dino_m, dev = load_models(repo, dav_ckpt, dino_ckpt, "vitl")
93
 
94
  with rasterio.open(BytesIO(rgb_file.read())) as src:
95
  rgb_raw = src.read([1, 2, 3])
96
+ h_f, w_f = src.height, src.width
97
  meta_full = src.profile
98
  with rasterio.open(BytesIO(prior_file.read())) as src:
99
  prior_data = src.read(1).astype(np.float32)
 
100
  with rasterio.open(BytesIO(mask_file.getvalue())) as src:
101
  change_mask = src.read(mask_band) > 0
102
 
103
+ rel_depth, pca_map = run_full_inference(dav_m, dino_m, rgb_raw, h_f, w_f, dev)
 
104
 
105
+ tab_diag, tab_bbox, tab_ssi = st.tabs(["πŸ”¬ Point Diagnostic", "⊞ Grid/BBOX Update", "πŸš€ Final SSI"])
 
 
106
 
107
+ # --- TAB 1: INTERACTIVE DIAGNOSTIC (YOUR LOGIC) ---
108
+ with tab_diag:
109
+ st.subheader("Spatial + Semantic Metric Diagnostic")
110
  viz_rgb = np.clip(rgb_raw.transpose(1, 2, 0) / (np.percentile(rgb_raw, 98) + 1e-6), 0, 1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  viz_small = (cv2.resize(viz_rgb, (640, 640)) * 255).astype(np.uint8)
112
+
113
+ col_map, col_res = st.columns([1, 1])
114
+ with col_map:
115
  p_click = streamlit_image_coordinates(viz_small, key="diag_click")
116
 
117
  if p_click:
118
  tx, ty = int(p_click['x']*(w_f/640)), int(p_click['y']*(h_f/640))
 
 
 
 
 
119
 
120
+ # Logic: valid anchors
121
+ valid_logic = np.isfinite(prior_data) & (~change_mask)
122
+ valid_coords = np.argwhere(valid_logic)
123
+ valid_pca = pca_map[:, valid_logic].T
124
+ pca_tree = cKDTree(valid_pca)
125
 
126
+ # 1. Search candidates by PCA (Semantic match)
127
+ target_pca = pca_map[:, ty, tx]
128
+ dist, idx = pca_tree.query(target_pca, k=100) # Start with 100
129
 
130
+ c_pred = rel_depth[valid_logic][idx]
131
+ c_metric = prior_data[valid_logic][idx]
132
+ c_coords = valid_coords[idx]
133
+
134
+ # 2. Filter by Physical distance (Spatial match) - Use 20
135
+ spatial_dists = np.sqrt((c_coords[:, 0] - ty)**2 + (c_coords[:, 1] - tx)**2)
136
+ spatial_indices = np.argsort(spatial_dists)[:20]
137
+ s_pred, s_metric, s_coords = c_pred[spatial_indices], c_metric[spatial_indices], c_coords[spatial_indices]
138
+
139
+ # 3. Final Geometric filter - Use 10 (K=10)
140
+ target_rel = rel_depth[ty, tx]
141
+ depth_diff = np.abs(s_pred - target_rel)
142
+ final_indices = np.argsort(depth_diff)[:10]
143
+ rejected_indices = np.argsort(depth_diff)[10:]
144
+
145
+ k_pred, k_metric, k_coords = s_pred[final_indices], s_metric[final_indices], s_coords[final_indices]
146
+ r_coords = s_coords[rejected_indices]
147
+
148
+ # Local Fit
149
+ local_model = LinearRegression().fit(k_pred.reshape(-1, 1), k_metric)
150
+ pred_h = local_model.predict([[target_rel]])[0]
151
+
152
+ with col_res:
153
+ st.metric("Estimated Local Elevation", f"{pred_h:.2f} m")
154
+ fig, ax = plt.subplots(1, 2, figsize=(12, 5))
155
+ # Map view with points
156
+ ax[0].imshow(viz_rgb)
157
+ ax[0].scatter(r_coords[:, 1], r_coords[:, 0], color='red', marker='x', s=10, label='Rejected')
158
+ ax[0].scatter(k_coords[:, 1], k_coords[:, 0], color='lawngreen', s=25, label='Anchors')
159
+ ax[0].scatter([tx], [ty], color='cyan', marker='P', s=80, label='Target')
160
+ ax[0].set_title("Anchor Spatial Selection")
161
+ ax[0].legend()
162
+
163
+ # Scatter view
164
+ ax[1].scatter(s_pred, s_metric, color='red', marker='x', alpha=0.3)
165
+ ax[1].scatter(k_pred, k_metric, color='lawngreen', s=50, edgecolors='black')
166
+ xr = np.linspace(s_pred.min(), s_pred.max(), 100).reshape(-1, 1)
167
+ ax[1].plot(xr, local_model.predict(xr), 'b-', label='Local Fit')
168
+ ax[1].scatter([target_rel], [pred_h], color='cyan', marker='*', s=150)
169
+ ax[1].set_title("Metric Regression")
170
+ st.pyplot(fig)
171
+
172
+ # --- TAB 2: BBOX / GRID UPDATE ---
173
+ with tab_bbox:
174
+ st.subheader("Select Coordinate Range for Update")
175
+ # Draw prior with grid
176
+ fig_g, ax_g = plt.subplots(figsize=(10, 10))
177
+ ax_g.imshow(prior_data, cmap='terrain')
178
+ # Add grid
179
+ grid_space = 64
180
+ for x in range(0, w_f, grid_space): ax_g.axvline(x, color='white', alpha=0.2, lw=0.5)
181
+ for y in range(0, h_f, grid_space): ax_g.axhline(y, color='white', alpha=0.2, lw=0.5)
182
+
183
+ viz_grid = (cv2.resize(prior_data, (640, 640)) - prior_data.min()) / (prior_data.max() - prior_data.min())
184
+ p_bbox = streamlit_image_coordinates((viz_grid * 255).astype(np.uint8), key="bbox_sel")
185
+
186
+ if p_bbox:
187
+ bx, by = int(p_bbox['x']*(w_f/640)), int(p_bbox['y']*(h_f/640))
188
+ st.session_state.bbox_coords.append((bx, by))
189
+
190
+ if st.session_state.bbox_coords:
191
+ st.write(f"Points Selected: {st.session_state.bbox_coords}")
192
+ if st.button("Reset BBOX"):
193
+ st.session_state.bbox_coords = []
194
+ st.rerun()
195
+
196
+ # --- TAB 3: SSI OPTIMIZATION ---
197
+ with tab_ssi:
198
+ if st.button("πŸš€ Run SSI Final Refinement"):
199
+ with st.spinner("Optimizing Surface..."):
200
+ D_dav_t = torch.from_numpy(rel_depth)[None, None].to(dev)
201
+ D_prior_t = torch.from_numpy(np.nan_to_num(prior_data)).to(dev).unsqueeze(0).unsqueeze(0)
202
 
203
+ # If BBOX selected, update mask
204
+ final_mask = change_mask.copy()
205
+ if len(st.session_state.bbox_coords) >= 2:
206
+ c1, c2 = st.session_state.bbox_coords[-2], st.session_state.bbox_coords[-1]
207
+ final_mask[min(c1[1], c2[1]):max(c1[1], c2[1]), min(c1[0], c2[0]):max(c1[0], c2[0])] = True
208
 
209
+ Anchor = torch.from_numpy((~final_mask).astype(np.float32)).to(dev).unsqueeze(0).unsqueeze(0)
210
+ D_opt = D_prior_t.clone().detach().requires_grad_(True)
211
+ opt = torch.optim.Adam([D_opt], lr=8e-3)
212
  D_dav_s = ((D_dav_t - D_dav_t.mean()) / (D_dav_t.std() + 1e-6)) * D_prior_t.std() + D_prior_t.mean()
 
213
 
214
  for _ in range(350):
215
+ opt.zero_grad()
216
+ l_a = F.mse_loss(D_opt * Anchor, D_prior_t * Anchor)
217
+ l_l = F.l1_loss(D_opt - F.avg_pool2d(D_opt, 5, 1, 2), D_dav_s.detach() - F.avg_pool2d(D_dav_s.detach(), 5, 1, 2))
218
+ (1500 * l_a + 200 * l_l).backward()
219
+ opt.step()
 
 
 
 
220
 
221
  res = D_opt.detach().squeeze().cpu().numpy()
222
  buf = BytesIO(); meta_full.update(count=1, dtype="float32")
223
  with rasterio.open(buf, "w", **meta_full) as dst: dst.write(res, 1)
224
+ st.success("Refinement Complete!")
225
+ st.download_button("Download Refined DSM", buf.getvalue(), "refined.tif")
226
  else:
227
+ st.info("Upload Assets to begin.")