osherr commited on
Commit
ec6e64a
Β·
verified Β·
1 Parent(s): 0cbae82

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +47 -65
src/streamlit_app.py CHANGED
@@ -28,10 +28,19 @@ if current_dir not in sys.path:
28
  def load_backbones(repo_id, dav_file, dino_file, m_size):
29
  device = "cuda" if torch.cuda.is_available() else "cpu"
30
 
31
- # 1. Load Depth Anything V2
32
  dav_path = hf_hub_download(repo_id=repo_id, filename=dav_file)
33
- from depth_anything_v2 import build_backbone
34
- dav_model = build_backbone(depth_size=m_size)
 
 
 
 
 
 
 
 
 
35
  dav_model.load_state_dict(torch.load(dav_path, map_location="cpu", weights_only=True))
36
  dav_model = dav_model.to(device).eval()
37
 
@@ -66,147 +75,120 @@ def extract_dino_features(encoder, img_tensor, device):
66
  for blk in encoder.blocks: tokens = blk(tokens)
67
  if hasattr(encoder, "norm"): tokens = encoder.norm(tokens)
68
 
69
- patch_tokens = tokens[:, 1:, :].squeeze(0).cpu().numpy() # [N, 1024]
70
 
71
  pca = PCA(n_components=3, whiten=True)
72
  Z = pca.fit_transform(patch_tokens)
73
  Z_img = Z.reshape(Hp, Wp, 3)
74
- # sigmoid(2*z) notebook style
75
  Z_rgb = 1.0 / (1.0 + np.exp(-2.0 * Z_img))
76
 
77
  norm_features = patch_tokens / (np.linalg.norm(patch_tokens, axis=1, keepdims=True) + 1e-6)
78
  return norm_features, Z_rgb.transpose(2, 0, 1), (Hp, Wp)
79
 
80
  # ================= UI DESIGN =================
81
- st.set_page_config(layout="wide", page_title="Prior2DSM Research", page_icon="πŸ›°οΈ")
82
 
83
- # Custom CSS
84
  st.markdown("""
85
  <style>
86
  .stTabs [data-baseweb="tab"] { background-color: #f0f2f6; border-radius: 4px; padding: 10px; }
87
  .stTabs [aria-selected="true"] { background-color: #2e7d32; color: white; }
88
- .stButton>button { border-radius: 8px; background-color: #2e7d32; color: white; width: 100%; }
89
  </style>
90
  """, unsafe_allow_html=True)
91
 
92
- st.title("πŸ›°οΈ Prior2DSM: Precision Refinement & Research")
93
 
94
  if 'points' not in st.session_state:
95
  st.session_state.points = []
96
 
97
  with st.sidebar:
98
  st.header("πŸ“‚ Data Ingestion")
99
- rgb_file = st.file_uploader("Source Image (TIF, PNG, JPG)", type=["tif", "tiff", "png", "jpg", "jpeg"])
100
  prior_file = st.file_uploader("Prior DSM (TIF)", type=["tif", "tiff"])
101
- mask_file = st.file_uploader("Optional: Change Mask (TIF)", type=["tif", "tiff"])
102
 
103
  st.divider()
104
- st.header("βš™οΈ Model Configuration")
105
- repo = st.text_input("HF Repo", "osherr/Prior2DSM")
 
106
  dav_ckpt = st.text_input("DAV2 Weights", "depth_anything_v2_vitl.pth")
107
  dino_ckpt = st.text_input("DINO Weights", "dinov3_vitl16_pretrain.pth")
108
- m_size = st.selectbox("DAV2 Size", ["vitl", "vitb", "vits"])
109
 
110
  if rgb_file and prior_file:
111
- # 1. Load Assets
112
  dav_model, dino_model, device = load_backbones(repo, dav_ckpt, dino_ckpt, m_size)
113
 
114
  with rasterio.open(BytesIO(rgb_file.read())) as src:
115
  n_bands = src.count
116
- sel_bands = st.sidebar.multiselect("Select RGB Bands", range(1, n_bands+1), default=[1, 2, 3] if n_bands >=3 else [1])
117
  rgb_raw = src.read(sel_bands)
118
 
119
  with rasterio.open(BytesIO(prior_file.read())) as src:
120
  prior_data = src.read(1).astype(np.float32)
121
- prior_meta = src.profile
122
- h_full, w_full = prior_meta['height'], prior_meta['width']
123
 
124
- # 2. Extract Features
125
  img_448 = cv2.resize(rgb_raw[:3].transpose(1, 2, 0), (448, 448))
126
  img_1024 = cv2.resize(rgb_raw[:3].transpose(1, 2, 0), (1024, 1024))
127
 
128
- with st.spinner("Processing Neural Backbones..."):
129
- # DAV2 Relative Depth
130
  dav_in = torch.from_numpy(img_448).transpose(2, 0, 1).unsqueeze(0).to(device).float() / 255.0
131
  with torch.no_grad():
132
  rel_depth = dav_model(dav_in)
133
  if isinstance(rel_depth, (list, tuple)): rel_depth = rel_depth[-1]
134
- rel_depth = F.interpolate(rel_depth.unsqueeze(1), size=(h_full, w_full), mode="bilinear").squeeze().cpu().numpy()
135
 
136
- # DINO Semantic PCA
137
  dino_in = torch.from_numpy(img_1024).transpose(2, 0, 1).unsqueeze(0).to(device).float() / 255.0
138
- dino_feats, pca_map, (Hp, Wp) = extract_dino_features(dino_model, dino_in, device)
139
- pca_viz = F.interpolate(torch.from_numpy(pca_map).unsqueeze(0), size=(h_full, w_full), mode="bilinear").squeeze().numpy()
140
 
141
- # 3. GUI Layout
142
  tab_mask, tab_diag, tab_refine = st.tabs(["πŸ–ŒοΈ Masking", "πŸ”¬ Diagnostics", "πŸ—οΈ Refinement"])
143
 
144
  with tab_mask:
145
- st.subheader("Interactive Change Masking")
146
  viz_rgb = np.clip(rgb_raw[:3].transpose(1, 2, 0) / (np.percentile(rgb_raw[:3], 98) + 1e-6), 0, 1)
147
  viz_small = (cv2.resize(viz_rgb, (640, 640)) * 255).astype(np.uint8)
148
-
149
  col_m1, col_m2 = st.columns([2, 1])
150
  with col_m1:
151
  coords = streamlit_image_coordinates(viz_small, key="mask_painter")
152
  if coords:
153
- scale_h, scale_w = h_full/640, w_full/640
154
- st.session_state.points.append((int(coords['x']*scale_w), int(coords['y']*scale_h)))
155
  st.rerun()
156
  with col_m2:
157
  if st.button("πŸ—‘οΈ Clear Mask"):
158
  st.session_state.points = []
159
  st.rerun()
160
- st.write(f"Points captured: {len(st.session_state.points)}")
161
 
162
- # Merge Masks
163
- final_mask = np.zeros((h_full, w_full), dtype=bool)
164
  if len(st.session_state.points) >= 2:
165
  for i in range(0, len(st.session_state.points)-1, 2):
166
  p1, p2 = st.session_state.points[i], st.session_state.points[i+1]
167
- final_mask[min(p1[1], p2[1]):max(p1[1], p2[1]), min(p1[0], p2[0]):max(p1[0], p2[0])] = True
168
-
169
- with tab_diag:
170
- st.subheader("DINO-Powered Semantic Analysis")
171
- col_d1, col_d2 = st.columns([2, 1])
172
- with col_d1:
173
- d_coords = streamlit_image_coordinates(viz_small, key="diag_selector")
174
-
175
- if d_coords:
176
- tx, ty = int(d_coords['x'] * (w_full/640)), int(d_coords['y'] * (h_full/640))
177
- st.info(f"Analyzing location: {tx}, {ty}")
178
- # [Research Diagnostic Logic: cKDTree search in DINO features...]
179
- st.write("Target Elevation:", prior_data[ty, tx])
180
 
181
  with tab_refine:
182
- st.subheader("Fine Surface Refinement")
183
- if st.button("πŸš€ Run Two-Stage Optimization"):
184
- with st.spinner("Executing Fine Refinement..."):
185
  D_dav = torch.from_numpy(rel_depth)[None, None].to(device)
186
- D_prior = torch.from_numpy(prior_data.astype(np.float32))[None, None].to(device)
187
- Anchor = torch.from_numpy((~final_mask).astype(np.float32))[None, None].to(device)
188
-
189
- # Step 1: Global Trend Alignment
190
  D_opt = D_prior.clone().detach().requires_grad_(True)
191
- opt1 = torch.optim.Adam([D_opt], lr=1e-2)
192
  D_dav_s = ((D_dav - D_dav.mean()) / D_dav.std()) * D_prior.std() + D_prior.mean()
193
-
194
- # Step 2: Fine Detail Injection (Fine Refinement)
195
- for _ in range(300):
196
- opt1.zero_grad()
197
  l_anchor = F.mse_loss(D_opt * Anchor, D_prior * Anchor)
198
  l_geom = F.l1_loss(D_opt - F.avg_pool2d(D_opt, 5, 1, 2),
199
  D_dav_s - F.avg_pool2d(D_dav_s, 5, 1, 2))
200
  (1200 * l_anchor + 180 * l_geom).backward()
201
- opt1.step()
202
-
203
  final = D_opt.detach().squeeze().cpu().numpy()
204
- prior_meta.update(count=1, dtype="float32")
205
  out_buf = BytesIO()
206
- with rasterio.open(out_buf, "w", **prior_meta) as dst:
 
207
  dst.write(final, 1)
208
-
209
  st.success("Refinement Complete!")
210
- st.download_button("πŸ“₯ Download Refined TIF", out_buf.getvalue(), "refined_448_fine.tif")
 
211
  else:
212
- st.info("Upload TIF assets and configure models in the sidebar.")
 
28
  def load_backbones(repo_id, dav_file, dino_file, m_size):
29
  device = "cuda" if torch.cuda.is_available() else "cpu"
30
 
31
+ # 1. Load Depth Anything V2 with error handling for config
32
  dav_path = hf_hub_download(repo_id=repo_id, filename=dav_file)
33
+ from depth_anything_v2.dpt import DepthAnythingV2
34
+
35
+ # Manual config matching the expected ViT architecture
36
+ model_configs = {
37
+ 'vits': {'encoder': 'vits', 'features': 64, 'out_channels': [48, 96, 192, 384]},
38
+ 'vitb': {'encoder': 'vitb', 'features': 128, 'out_channels': [96, 192, 384, 768]},
39
+ 'vitl': {'encoder': 'vitl', 'features': 256, 'out_channels': [256, 512, 1024, 1024]},
40
+ }
41
+
42
+ # Initialize without the problematic 'encoder_cond_dim'
43
+ dav_model = DepthAnythingV2(**model_configs[m_size])
44
  dav_model.load_state_dict(torch.load(dav_path, map_location="cpu", weights_only=True))
45
  dav_model = dav_model.to(device).eval()
46
 
 
75
  for blk in encoder.blocks: tokens = blk(tokens)
76
  if hasattr(encoder, "norm"): tokens = encoder.norm(tokens)
77
 
78
+ patch_tokens = tokens[:, 1:, :].squeeze(0).cpu().numpy()
79
 
80
  pca = PCA(n_components=3, whiten=True)
81
  Z = pca.fit_transform(patch_tokens)
82
  Z_img = Z.reshape(Hp, Wp, 3)
 
83
  Z_rgb = 1.0 / (1.0 + np.exp(-2.0 * Z_img))
84
 
85
  norm_features = patch_tokens / (np.linalg.norm(patch_tokens, axis=1, keepdims=True) + 1e-6)
86
  return norm_features, Z_rgb.transpose(2, 0, 1), (Hp, Wp)
87
 
88
  # ================= UI DESIGN =================
89
+ st.set_page_config(layout="wide", page_title="Prior2DSM Pro", page_icon="πŸ›°οΈ")
90
 
 
91
  st.markdown("""
92
  <style>
93
  .stTabs [data-baseweb="tab"] { background-color: #f0f2f6; border-radius: 4px; padding: 10px; }
94
  .stTabs [aria-selected="true"] { background-color: #2e7d32; color: white; }
95
+ .stButton>button { border-radius: 8px; background-color: #2e7d32; color: white; width: 100%; height: 3.5em; }
96
  </style>
97
  """, unsafe_allow_html=True)
98
 
99
+ st.title("πŸ›°οΈ Prior2DSM: Precision Terrain Refinement")
100
 
101
  if 'points' not in st.session_state:
102
  st.session_state.points = []
103
 
104
  with st.sidebar:
105
  st.header("πŸ“‚ Data Ingestion")
106
+ rgb_file = st.file_uploader("Source (TIF, PNG, JPG)", type=["tif", "tiff", "png", "jpg", "jpeg"])
107
  prior_file = st.file_uploader("Prior DSM (TIF)", type=["tif", "tiff"])
 
108
 
109
  st.divider()
110
+ st.header("βš™οΈ Configuration")
111
+ m_size = st.selectbox("DAV2 Size", ["vitl", "vitb", "vits"])
112
+ repo = st.text_input("Repo", "osherr/Prior2DSM")
113
  dav_ckpt = st.text_input("DAV2 Weights", "depth_anything_v2_vitl.pth")
114
  dino_ckpt = st.text_input("DINO Weights", "dinov3_vitl16_pretrain.pth")
 
115
 
116
  if rgb_file and prior_file:
 
117
  dav_model, dino_model, device = load_backbones(repo, dav_ckpt, dino_ckpt, m_size)
118
 
119
  with rasterio.open(BytesIO(rgb_file.read())) as src:
120
  n_bands = src.count
121
+ sel_bands = st.sidebar.multiselect("RGB Bands", range(1, n_bands+1), default=[1, 2, 3] if n_bands >=3 else [1])
122
  rgb_raw = src.read(sel_bands)
123
 
124
  with rasterio.open(BytesIO(prior_file.read())) as src:
125
  prior_data = src.read(1).astype(np.float32)
126
+ p_meta = src.profile
127
+ h_f, w_f = p_meta['height'], p_meta['width']
128
 
129
+ # Inference Prep
130
  img_448 = cv2.resize(rgb_raw[:3].transpose(1, 2, 0), (448, 448))
131
  img_1024 = cv2.resize(rgb_raw[:3].transpose(1, 2, 0), (1024, 1024))
132
 
133
+ with st.spinner("Processing Backbones..."):
 
134
  dav_in = torch.from_numpy(img_448).transpose(2, 0, 1).unsqueeze(0).to(device).float() / 255.0
135
  with torch.no_grad():
136
  rel_depth = dav_model(dav_in)
137
  if isinstance(rel_depth, (list, tuple)): rel_depth = rel_depth[-1]
138
+ rel_depth = F.interpolate(rel_depth.unsqueeze(1), size=(h_f, w_f), mode="bilinear").squeeze().cpu().numpy()
139
 
 
140
  dino_in = torch.from_numpy(img_1024).transpose(2, 0, 1).unsqueeze(0).to(device).float() / 255.0
141
+ _, pca_raw, _ = extract_dino_features(dino_model, dino_in, device)
142
+ pca_viz = F.interpolate(torch.from_numpy(pca_raw).unsqueeze(0), size=(h_f, w_f), mode="bilinear").squeeze().numpy()
143
 
144
+ # UI Tabs
145
  tab_mask, tab_diag, tab_refine = st.tabs(["πŸ–ŒοΈ Masking", "πŸ”¬ Diagnostics", "πŸ—οΈ Refinement"])
146
 
147
  with tab_mask:
 
148
  viz_rgb = np.clip(rgb_raw[:3].transpose(1, 2, 0) / (np.percentile(rgb_raw[:3], 98) + 1e-6), 0, 1)
149
  viz_small = (cv2.resize(viz_rgb, (640, 640)) * 255).astype(np.uint8)
 
150
  col_m1, col_m2 = st.columns([2, 1])
151
  with col_m1:
152
  coords = streamlit_image_coordinates(viz_small, key="mask_painter")
153
  if coords:
154
+ st.session_state.points.append((int(coords['x']*(w_f/640)), int(coords['y']*(h_f/640))))
 
155
  st.rerun()
156
  with col_m2:
157
  if st.button("πŸ—‘οΈ Clear Mask"):
158
  st.session_state.points = []
159
  st.rerun()
160
+ st.write(f"Points: {len(st.session_state.points)}")
161
 
162
+ mask = np.zeros((h_f, w_f), dtype=bool)
 
163
  if len(st.session_state.points) >= 2:
164
  for i in range(0, len(st.session_state.points)-1, 2):
165
  p1, p2 = st.session_state.points[i], st.session_state.points[i+1]
166
+ mask[min(p1[1], p2[1]):max(p1[1], p2[1]), min(p1[0], p2[0]):max(p1[0], p2[0])] = True
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
  with tab_refine:
169
+ if st.button("πŸš€ Start Fine Refinement"):
170
+ with st.spinner("Optimizing Surface..."):
 
171
  D_dav = torch.from_numpy(rel_depth)[None, None].to(device)
172
+ D_prior = torch.from_numpy(prior_data)[None, None].to(device)
173
+ Anchor = torch.from_numpy((~mask).astype(np.float32))[None, None].to(device)
 
 
174
  D_opt = D_prior.clone().detach().requires_grad_(True)
175
+ opt = torch.optim.Adam([D_opt], lr=8e-3)
176
  D_dav_s = ((D_dav - D_dav.mean()) / D_dav.std()) * D_prior.std() + D_prior.mean()
177
+ for _ in range(250):
178
+ opt.zero_grad()
 
 
179
  l_anchor = F.mse_loss(D_opt * Anchor, D_prior * Anchor)
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
  (1200 * l_anchor + 180 * l_geom).backward()
183
+ opt.step()
184
+
185
  final = D_opt.detach().squeeze().cpu().numpy()
 
186
  out_buf = BytesIO()
187
+ p_meta.update(count=1, dtype="float32")
188
+ with rasterio.open(out_buf, "w", **p_meta) as dst:
189
  dst.write(final, 1)
 
190
  st.success("Refinement Complete!")
191
+ st.download_button("πŸ“₯ Download Refined DSM", out_buf.getvalue(), "refined.tif")
192
+
193
  else:
194
+ st.info("Upload TIF/PNG assets to begin.")