@@ -35,12 +35,12 @@ def _build_event_mask(
3535 event_db : Path | None ,
3636 mask_dir : Path ,
3737) -> Path | None :
38- """Generate and combine event/deformation masks from GeoJSON databases .
38+ """Generate and combine event/deformation masks from GeoJSON database .
3939
40- Runs ``generate_event_mask`` for each available GeoJSON, selects only
41- features whose ``frame_id`` and ``event_date`` overlap the DISP product
42- epoch, and ANDs the results into a single combined mask GeoTIFF
43- (1 = valid, 0 = masked). Returns ``None`` when no database is provided.
40+ For each available GeoJSON, selects only features whose ``frame_id``
41+ and ``event_date`` overlap the DISP product epoch, and ANDs the results
42+ into a single combined mask GeoTIFF (1 = valid, 0 = masked).
43+ Returns ``None`` when no database is provided.
4444 """
4545 from cal_disp .prep .generate_event_mask import generate_event_mask
4646
@@ -65,7 +65,6 @@ def _build_event_mask(
6565 if len (mask_paths ) == 1 :
6666 return mask_paths [0 ]
6767
68- # AND all individual masks: a pixel is valid only if valid in every mask
6968 combined = mask_dir / "combined_event_mask.tif"
7069 with rasterio .open (mask_paths [0 ]) as src :
7170 combined_mask = src .read (1 ).astype (bool )
@@ -78,6 +77,30 @@ def _build_event_mask(
7877 return combined
7978
8079
80+ def _read_wavelength_m (disp_file : Path ) -> float :
81+ """Read the radar wavelength in meters from a DISP product file.
82+
83+ Returns ~0.05546 for Sentinel-1 C-band.
84+ """
85+ from netCDF4 import Dataset # type: ignore[import-untyped]
86+
87+ try :
88+ with Dataset (disp_file , "r" ) as nc :
89+ ident_group = nc .groups ["identification" ]
90+ wl_m = float (ident_group .variables ["radar_wavelength" ][:])
91+ wl_units = getattr (ident_group .variables ["radar_wavelength" ], "units" , "m" )
92+ logger .debug (
93+ "Read radar_wavelength %.6f %s from %s" , wl_m , wl_units , disp_file .name
94+ )
95+ return wl_m
96+ except Exception as e :
97+ msg = (
98+ f"Could not read /identification/radar_wavelength from { disp_file .name } . "
99+ "Ensure the file is a valid DISP product with an /identification group."
100+ )
101+ raise RuntimeError (msg ) from e
102+
103+
81104def _date_to_decimal_year (dt : datetime ) -> float :
82105 """Convert a datetime to a decimal year (e.g. 2022.55)."""
83106 year = dt .year
@@ -162,11 +185,7 @@ def _compute_gnss_los(
162185 cache_dir : Path ,
163186 reference_frame : str ,
164187) -> np .ndarray :
165- """Return GNSS displacement projected into LOS (mm).
166-
167- For the ``constant`` grid type the velocity field is computed once and
168- cached; subsequent calls load from the cache.
169- """
188+ """Return GNSS displacement projected into LOS in meters."""
170189 if grid_type == "constant" :
171190 cache = cache_dir / f"gnss_los_velocity_{ reference_frame } .npy"
172191 if cache .exists ():
@@ -185,20 +204,24 @@ def _compute_gnss_los(
185204 )
186205 np .save (cache , gnss_velocity )
187206 logger .info ("Cached GNSS LOS velocity to %s" , cache )
188- # Scale velocity (mm/yr) to the acquisition interval (yr)
189- return gnss_velocity * (sec_decimal - ref_decimal )
207+ # Scale velocity (mm/yr) by acquisition interval (yr), then mm to m
208+ return gnss_velocity * (sec_decimal - ref_decimal ) / 1000.0
190209
191210 # variable: epoch-specific displacement
192211 logger .info (
193212 "Computing GNSS LOS displacement (%.4f → %.4f)..." , ref_decimal , sec_decimal
194213 )
195- return gnss_ref .compute_displacement_los (
196- ref_date = ref_decimal ,
197- sec_date = sec_decimal ,
198- los_east = los_east ,
199- los_north = los_north ,
200- los_up = los_up ,
201- netcdf_file = disp_file ,
214+ # mm to m
215+ return (
216+ gnss_ref .compute_displacement_los (
217+ ref_date = ref_decimal ,
218+ sec_date = sec_decimal ,
219+ los_east = los_east ,
220+ los_north = los_north ,
221+ los_up = los_up ,
222+ netcdf_file = disp_file ,
223+ )
224+ / 1000.0
202225 )
203226
204227
@@ -404,10 +427,10 @@ def run_calibration(
404427 reference_frame = cal .reference_frame ,
405428 )
406429
407- # Prepare displacement array (mm, 2-D)
430+ # Prepare displacement array.
408431 # A single DISP file encodes one (ref_date, sec_date) pair.
409432 # displacement is 2-D (y, x); time is a length-1 coordinate.
410- disp_2d = ds_disp .displacement .values * 1000.0 # m → mm
433+ disp_2d = ds_disp .displacement .values . astype ( np . float32 )
411434
412435 # Build valid-pixel mask
413436 mask = ~ np .isnan (disp_2d )
@@ -438,19 +461,20 @@ def run_calibration(
438461 if cal .unwrap_error_correction :
439462 from venti .unwrap import correct_region_offset
440463
441- _WAVELENGTH_MM = 0.0555 / 2 * 1000 # S1 C-band half-wavelength
442- logger .info ("Applying unwrap-error correction..." )
464+ wavelength_m = _read_wavelength_m (disp_file )
465+ logger .info (
466+ "Applying unwrap-error correction (wavelength=%.5f m)..." ,
467+ wavelength_m ,
468+ )
443469 disp_masked = correct_region_offset (
444470 input_disp = disp_masked ,
445471 mask = mask ,
446- wavelength = _WAVELENGTH_MM ,
472+ wavelength = wavelength_m ,
447473 )
448474 if isinstance (disp_masked , np .ma .MaskedArray ):
449475 disp_masked = disp_masked .filled (np .nan )
450476
451477 # Optional tropospheric correction
452- # Form the differential (secondary − reference) on the fly, matching the
453- # DISP-S1 sign convention used in venti's calibration workflow.
454478 _tropo_applied = False
455479 if reference_tropo_files and secondary_tropo_files :
456480 if dem_file is None :
@@ -470,11 +494,11 @@ def run_calibration(
470494 output_dir = tropo_dir ,
471495 )
472496 with rasterio .open (ref_tropo_path ) as _src :
473- ref_tropo_m = _src .read (1 ).astype (np .float32 )
497+ ref_tropo = _src .read (1 ).astype (np .float32 )
474498 with rasterio .open (sec_tropo_path ) as _src :
475- sec_tropo_m = _src .read (1 ).astype (np .float32 )
476- tropo_corr_mm = ( sec_tropo_m - ref_tropo_m ) * 1000.0 # m → mm
477- disp_masked = np .where (mask , disp_masked - tropo_corr_mm , np .nan )
499+ sec_tropo = _src .read (1 ).astype (np .float32 )
500+ tropo_corr = sec_tropo - ref_tropo # differential LOS delay (meters)
501+ disp_masked = np .where (mask , disp_masked - tropo_corr , np .nan )
478502 _tropo_applied = True
479503 logger .info ("Tropospheric correction applied." )
480504
@@ -537,8 +561,8 @@ def run_calibration(
537561 if ds_factor > 1 :
538562 cal_surface = upsample_array (cal_surface , original_shape )
539563
540- # Convert back to metres and insert time dimension
541- cal_surface_m = ( cal_surface / 1000.0 ) .astype (np .float32 )
564+ # Insert time dimension
565+ cal_surface_m = cal_surface .astype (np .float32 )
542566
543567 coords = {"time" : time , "y" : y , "x" : x }
544568 calibration = xr .DataArray (
0 commit comments