PyGeoFetch 2.0.2__py3-none-any.whl → 2.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygeofetch/__init__.py +2 -2
- pygeofetch/cli/main.py +2 -2
- pygeofetch/insar/esd.py +319 -133
- pygeofetch/insar/interferogram.py +137 -12
- pygeofetch/insar/unwrap.py +412 -303
- pygeofetch/meta.yaml +1 -1
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.4.dist-info}/METADATA +1 -1
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.4.dist-info}/RECORD +12 -12
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.4.dist-info}/WHEEL +0 -0
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.4.dist-info}/entry_points.txt +0 -0
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.4.dist-info}/licenses/LICENSE +0 -0
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.4.dist-info}/top_level.txt +0 -0
pygeofetch/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
PyGeoFetch v2.0.
|
|
2
|
+
PyGeoFetch v2.0.4 — Universal Satellite Data Pipeline.
|
|
3
3
|
|
|
4
4
|
Unified access to 22+ satellite data providers with a consistent
|
|
5
5
|
Python API and CLI. Supports authentication management, federated search,
|
|
@@ -30,7 +30,7 @@ CLI::
|
|
|
30
30
|
pygeofetch doctor
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
__version__ = "2.0.
|
|
33
|
+
__version__ = "2.0.4"
|
|
34
34
|
__author__ = "PyGeoFetch Contributors"
|
|
35
35
|
__license__ = "MIT"
|
|
36
36
|
|
pygeofetch/cli/main.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
PyGeoFetch CLI v2.0.
|
|
2
|
+
PyGeoFetch CLI v2.0.4 main entry point.
|
|
3
3
|
|
|
4
4
|
Full command reference:
|
|
5
5
|
pygeofetch auth — credential management
|
|
@@ -71,7 +71,7 @@ def cli(
|
|
|
71
71
|
) -> None:
|
|
72
72
|
"""
|
|
73
73
|
\b
|
|
74
|
-
PyGeoFetch v2.0.
|
|
74
|
+
PyGeoFetch v2.0.4 — Universal Satellite Data Pipeline
|
|
75
75
|
===========================================================
|
|
76
76
|
Unified access to 22+ satellite data providers.
|
|
77
77
|
|
pygeofetch/insar/esd.py
CHANGED
|
@@ -18,6 +18,7 @@ for IW mode).
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
+
from datetime import datetime, timedelta
|
|
21
22
|
import logging
|
|
22
23
|
from typing import List, Optional, Tuple
|
|
23
24
|
|
|
@@ -205,157 +206,342 @@ def _estimate_shift_from_overlap(
|
|
|
205
206
|
return float(shift_seconds), median_coh
|
|
206
207
|
|
|
207
208
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
swath_timing: SwathTiming,
|
|
212
|
-
|
|
213
|
-
row_offset: int = 0,
|
|
214
|
-
delta_f_ovl_hz: float = SENTINEL1_IW_DELTA_F_OVL_HZ,
|
|
215
|
-
coherence_threshold: float = 0.3,
|
|
216
|
-
) -> Tuple[Optional[float], List[Optional[float]]]:
|
|
209
|
+
from typing import Dict, List, Optional, Tuple
|
|
210
|
+
|
|
211
|
+
def overlap_time_windows(
|
|
212
|
+
swath_timing: SwathTiming, azimuth_time_interval_s: float
|
|
213
|
+
) -> List[Tuple[int, "datetime", "datetime"]]:
|
|
217
214
|
"""
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
215
|
+
Absolute azimuth-time window of every adjacent-burst overlap -- the
|
|
216
|
+
ground area imaged by BOTH burst i (its trailing lines) and burst
|
|
217
|
+
i+1 (its leading lines) in THIS scene.
|
|
221
218
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
219
|
+
Returns:
|
|
220
|
+
List of (overlap_index, window_start, window_end) as absolute
|
|
221
|
+
azimuth datetimes.
|
|
222
|
+
"""
|
|
223
|
+
bursts = swath_timing.bursts
|
|
224
|
+
lines_per_burst = swath_timing.lines_per_burst
|
|
225
|
+
out = []
|
|
226
|
+
for i in range(len(bursts) - 1):
|
|
227
|
+
start = bursts[i + 1].azimuth_time
|
|
228
|
+
end = bursts[i].azimuth_time + timedelta(
|
|
229
|
+
seconds=(lines_per_burst - 1) * azimuth_time_interval_s
|
|
230
|
+
)
|
|
231
|
+
out.append((i, start, end))
|
|
232
|
+
return out
|
|
227
233
|
|
|
228
|
-
Memory optimization: subsamples in range to max 4096 columns per
|
|
229
|
-
overlap, reducing peak memory from ~600 MB to ~100 MB per overlap
|
|
230
|
-
while maintaining statistical robustness.
|
|
231
234
|
|
|
232
|
-
Args:
|
|
233
|
-
ref_complex, sec_complex: Real, coregistered (at least coarsely)
|
|
234
|
-
complex SLC arrays, same shape -- full burst stack, or an
|
|
235
|
-
already-cropped extract (see row_offset).
|
|
236
|
-
swath_timing: Real burst metadata from annotation.parse_burst_info().
|
|
237
|
-
azimuth_time_interval_s: Real per-line time spacing.
|
|
238
|
-
row_offset: Real full-scene row that ref_complex/sec_complex's
|
|
239
|
-
row 0 corresponds to. Default 0 assumes the arrays start at
|
|
240
|
-
the full-scene row 0.
|
|
241
|
-
delta_f_ovl_hz: Doppler centroid difference in the overlap.
|
|
242
|
-
coherence_threshold: Minimum MEDIAN windowed coherence for an
|
|
243
|
-
overlap to be considered usable.
|
|
244
235
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
where that overlap's coherence was too low or fell entirely
|
|
251
|
-
outside the given arrays).
|
|
236
|
+
def compute_common_ground_overlaps(
|
|
237
|
+
ref_timing: SwathTiming,
|
|
238
|
+
sec_timing: SwathTiming,
|
|
239
|
+
azimuth_time_interval_s: float,
|
|
240
|
+
) -> List[Dict]:
|
|
252
241
|
"""
|
|
253
|
-
|
|
242
|
+
Per-reference-overlap diagnostic of how much COMMON double-covered
|
|
243
|
+
ground (in azimuth lines) this date pair actually shares.
|
|
254
244
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
f"parse_burst_info(), not a raw bursts list."
|
|
260
|
-
)
|
|
245
|
+
For each reference overlap window, intersect it with every secondary
|
|
246
|
+
overlap window and keep the largest intersection. That intersection
|
|
247
|
+
length is the only ground on which the ESD double-difference is
|
|
248
|
+
physically meaningful for this pair.
|
|
261
249
|
|
|
262
|
-
|
|
263
|
-
|
|
250
|
+
This is the number that cleanly separates the two regimes:
|
|
251
|
+
- same-family pairs (identical burst timing): common lines ~= the
|
|
252
|
+
full overlap length (tens of lines per overlap);
|
|
253
|
+
- cross-family pairs (8 vs 9 bursts, shifted timing): common lines
|
|
254
|
+
shrink toward 0, which is exactly why ESD finds few/no usable
|
|
255
|
+
overlaps for them -- a physical limitation, not a bug.
|
|
264
256
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
257
|
+
Returns:
|
|
258
|
+
List of dicts, one per reference overlap, each with:
|
|
259
|
+
ref_overlap_index, ref_window, best_sec_overlap_index,
|
|
260
|
+
sec_window, common_lines, common_window
|
|
261
|
+
"""
|
|
262
|
+
dt = azimuth_time_interval_s
|
|
263
|
+
ref_wins = overlap_time_windows(ref_timing, dt)
|
|
264
|
+
sec_wins = overlap_time_windows(sec_timing, dt)
|
|
265
|
+
|
|
266
|
+
report = []
|
|
267
|
+
for i, r_start, r_end in ref_wins:
|
|
268
|
+
best_j, best_lines, best_win = None, 0, None
|
|
269
|
+
for j, s_start, s_end in sec_wins:
|
|
270
|
+
t0, t1 = max(r_start, s_start), min(r_end, s_end)
|
|
271
|
+
if t1 <= t0:
|
|
272
|
+
continue # no intersection for this secondary overlap
|
|
273
|
+
n_lines = int(round((t1 - t0).total_seconds() / dt)) + 1
|
|
274
|
+
if n_lines > best_lines:
|
|
275
|
+
best_j, best_lines, best_win = j, n_lines, (t0, t1)
|
|
276
|
+
report.append({
|
|
277
|
+
"ref_overlap_index": i,
|
|
278
|
+
"ref_window": (r_start, r_end),
|
|
279
|
+
"best_sec_overlap_index": best_j,
|
|
280
|
+
"sec_window": best_win,
|
|
281
|
+
"common_lines": best_lines,
|
|
282
|
+
"common_window": best_win,
|
|
283
|
+
})
|
|
284
|
+
return report
|
|
268
285
|
|
|
269
|
-
|
|
286
|
+
def estimate_esd_shift_per_burst_overlap(
|
|
287
|
+
ref_complex, sec_complex,
|
|
288
|
+
swath_timing, azimuth_time_interval_s,
|
|
289
|
+
row_offset: int = 0,
|
|
290
|
+
sec_swath_timing=None, # NEW: secondary burst timing
|
|
291
|
+
delta_f_ovl_hz=SENTINEL1_IW_DELTA_F_OVL_HZ,
|
|
292
|
+
coherence_threshold: float = 0.2,
|
|
293
|
+
min_common_lines: int = 3,
|
|
294
|
+
):
|
|
295
|
+
import numpy as np
|
|
296
|
+
from scipy.ndimage import uniform_filter
|
|
270
297
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
298
|
+
dt = azimuth_time_interval_s
|
|
299
|
+
L = swath_timing.lines_per_burst
|
|
300
|
+
ref_wins = overlap_time_windows(swath_timing, dt)
|
|
301
|
+
# If no secondary timing given, fall back to ref-only (old behaviour)
|
|
302
|
+
sec_wins = (overlap_time_windows(sec_swath_timing, dt)
|
|
303
|
+
if sec_swath_timing is not None else ref_wins)
|
|
304
|
+
|
|
305
|
+
per_overlap_shifts = []
|
|
306
|
+
skip_reasons = []
|
|
307
|
+
|
|
308
|
+
for (i, r_start, r_end) in ref_wins:
|
|
309
|
+
# Find the secondary overlap with the largest time intersection
|
|
310
|
+
best = None
|
|
311
|
+
for (j, s_start, s_end) in sec_wins:
|
|
312
|
+
t0, t1 = max(r_start, s_start), min(r_end, s_end)
|
|
313
|
+
n_lines = int((t1 - t0).total_seconds() / dt) + 1
|
|
314
|
+
if best is None or n_lines > best[0]:
|
|
315
|
+
best = (n_lines, t0, t1)
|
|
316
|
+
n_lines, t0, t1 = best
|
|
317
|
+
|
|
318
|
+
if n_lines < min_common_lines:
|
|
319
|
+
per_overlap_shifts.append(None)
|
|
320
|
+
skip_reasons.append("no_common_ground")
|
|
321
|
+
continue
|
|
277
322
|
|
|
278
|
-
#
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
323
|
+
# Map the common time window to reference rows (bw=burst i tail,
|
|
324
|
+
# fw=burst i+1 head). Secondary is coregistered to the ref grid,
|
|
325
|
+
# so it is indexed at the same rows.
|
|
326
|
+
bw_rows, fw_rows = [], []
|
|
327
|
+
for k in range(n_lines):
|
|
328
|
+
tt = t0 + timedelta(seconds=k * dt)
|
|
329
|
+
bw_rows.append(i * L + round((tt - swath_timing.bursts[i].azimuth_time).total_seconds() / dt) - row_offset)
|
|
330
|
+
fw_rows.append((i + 1) * L + round((tt - swath_timing.bursts[i + 1].azimuth_time).total_seconds() / dt) - row_offset)
|
|
331
|
+
|
|
332
|
+
bw = np.array([r for r in bw_rows if 0 <= r < ref_complex.shape[0]])
|
|
333
|
+
fw = np.array([r for r in fw_rows if 0 <= r < ref_complex.shape[0]])
|
|
334
|
+
if len(bw) < min_common_lines or len(fw) < min_common_lines:
|
|
282
335
|
per_overlap_shifts.append(None)
|
|
283
|
-
|
|
284
|
-
"Overlap %d<->%d: outside array bounds "
|
|
285
|
-
"(local rows bw=[%d,%d], fw=[%d,%d], array height=%d)",
|
|
286
|
-
i, i + 1, local_bw_start, local_bw_end,
|
|
287
|
-
local_fw_start, local_fw_end, h,
|
|
288
|
-
)
|
|
336
|
+
skip_reasons.append("outside_array_bounds")
|
|
289
337
|
continue
|
|
290
338
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
339
|
+
ref_bw, sec_bw = ref_complex[bw], sec_complex[bw]
|
|
340
|
+
ref_fw, sec_fw = ref_complex[fw], sec_complex[fw]
|
|
341
|
+
igram_bw = ref_bw * np.conj(sec_bw)
|
|
342
|
+
igram_fw = ref_fw * np.conj(sec_fw)
|
|
343
|
+
|
|
344
|
+
# Windowed coherence gate (same as before)
|
|
345
|
+
n_common = len(bw)
|
|
346
|
+
coh_window = min(5, n_common) if n_common >= 3 else 1
|
|
347
|
+
if coh_window >= 3:
|
|
348
|
+
num = np.abs(uniform_filter(igram_bw.real, size=coh_window)
|
|
349
|
+
+ 1j * uniform_filter(igram_bw.imag, size=coh_window))
|
|
350
|
+
denom = np.sqrt(uniform_filter(np.abs(ref_bw)**2, size=coh_window)
|
|
351
|
+
* uniform_filter(np.abs(sec_bw)**2, size=coh_window) + 1e-10)
|
|
352
|
+
valid = np.isfinite(num / denom) & ((num / denom) >= coherence_threshold)
|
|
353
|
+
else:
|
|
354
|
+
valid = np.ones(igram_bw.shape, dtype=bool)
|
|
303
355
|
|
|
304
|
-
if
|
|
305
|
-
n_below_coh += 1
|
|
356
|
+
if valid.mean() < 0.5:
|
|
306
357
|
per_overlap_shifts.append(None)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
i, i + 1, median_coh, coherence_threshold,
|
|
310
|
-
)
|
|
311
|
-
else:
|
|
312
|
-
n_usable += 1
|
|
313
|
-
per_overlap_shifts.append(shift_s)
|
|
314
|
-
logger.debug(
|
|
315
|
-
"Overlap %d<->%d: shift=%.6f ms, median_coh=%.3f",
|
|
316
|
-
i, i + 1, shift_s * 1000, median_coh,
|
|
317
|
-
)
|
|
358
|
+
skip_reasons.append(f"low_coherence({100*valid.mean():.0f}%)")
|
|
359
|
+
continue
|
|
318
360
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
"Real per-burst-overlap ESD found no usable burst overlaps "
|
|
324
|
-
"out of %d total: %d fell outside the given (possibly "
|
|
325
|
-
"cropped) arrays, %d were below coherence_threshold=%.2f. "
|
|
326
|
-
"A mix of both -- see the per-overlap detail at DEBUG level.",
|
|
327
|
-
len(overlaps), n_outside, n_below_coh, coherence_threshold,
|
|
328
|
-
)
|
|
329
|
-
elif n_outside > 0:
|
|
330
|
-
logger.warning(
|
|
331
|
-
"Real per-burst-overlap ESD found no usable burst overlaps "
|
|
332
|
-
"out of %d total: all %d fell outside the given (possibly "
|
|
333
|
-
"cropped) arrays.",
|
|
334
|
-
len(overlaps), n_outside,
|
|
335
|
-
)
|
|
336
|
-
else:
|
|
337
|
-
logger.warning(
|
|
338
|
-
"Real per-burst-overlap ESD found no usable burst overlaps "
|
|
339
|
-
"out of %d total: 0 fell outside the given (possibly "
|
|
340
|
-
"cropped) arrays, %d were below coherence_threshold=%.2f. "
|
|
341
|
-
"Every real, in-bounds overlap was tested and found "
|
|
342
|
-
"genuinely below the coherence threshold -- this reflects "
|
|
343
|
-
"real decorrelation in the data, not something a larger "
|
|
344
|
-
"AOI would fix.",
|
|
345
|
-
len(overlaps), n_below_coh, coherence_threshold,
|
|
346
|
-
)
|
|
347
|
-
return None, per_overlap_shifts
|
|
361
|
+
double_diff = igram_fw * np.conj(igram_bw)
|
|
362
|
+
mean_phase = np.angle(np.mean(double_diff[valid]))
|
|
363
|
+
per_overlap_shifts.append(float(mean_phase / (2 * np.pi * delta_f_ovl_hz)))
|
|
364
|
+
skip_reasons.append("used")
|
|
348
365
|
|
|
349
|
-
# Combine valid shifts via median (robust to outliers)
|
|
350
366
|
valid_shifts = [s for s in per_overlap_shifts if s is not None]
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
)
|
|
367
|
+
if not valid_shifts:
|
|
368
|
+
n_outside = skip_reasons.count("outside_array_bounds")
|
|
369
|
+
n_nocommon = skip_reasons.count("no_common_ground")
|
|
370
|
+
n_low = sum(1 for r in skip_reasons if r.startswith("low_coherence"))
|
|
371
|
+
logger.warning(
|
|
372
|
+
"ESD: no usable overlaps of %d (%d no common ground, %d outside, %d low coh)",
|
|
373
|
+
len(ref_wins), n_nocommon, n_outside, n_low)
|
|
374
|
+
return None, per_overlap_shifts
|
|
360
375
|
|
|
361
|
-
|
|
376
|
+
combined = float(np.median(valid_shifts))
|
|
377
|
+
logger.info("ESD common-ground: %d/%d usable, shift=%.6f ms (%.4f px)",
|
|
378
|
+
len(valid_shifts), len(ref_wins), combined*1000, combined/dt)
|
|
379
|
+
return combined, per_overlap_shifts
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# def estimate_esd_shift_per_burst_overlap(
|
|
383
|
+
# ref_complex,
|
|
384
|
+
# sec_complex,
|
|
385
|
+
# swath_timing: SwathTiming,
|
|
386
|
+
# azimuth_time_interval_s: float,
|
|
387
|
+
# row_offset: int = 0,
|
|
388
|
+
# delta_f_ovl_hz: float = SENTINEL1_IW_DELTA_F_OVL_HZ,
|
|
389
|
+
# coherence_threshold: float = 0.3,
|
|
390
|
+
# ) -> Tuple[Optional[float], List[Optional[float]]]:
|
|
391
|
+
# """
|
|
392
|
+
# Real per-burst-overlap ESD: estimate azimuth misregistration from
|
|
393
|
+
# the actual burst overlap regions, replacing the previous whole-image
|
|
394
|
+
# single-shift approximation.
|
|
395
|
+
|
|
396
|
+
# Uses WINDOWED coherence estimation (7x7 window) and MEDIAN coherence
|
|
397
|
+
# for the quality check, matching SNAP's approach. This is critical for
|
|
398
|
+
# full-scene processing where the overlap spans the entire range width
|
|
399
|
+
# (~23,000 pixels) including ocean, vegetation, and other low-coherence
|
|
400
|
+
# terrain that would drag a global-mean coherence below threshold.
|
|
401
|
+
|
|
402
|
+
# Memory optimization: subsamples in range to max 4096 columns per
|
|
403
|
+
# overlap, reducing peak memory from ~600 MB to ~100 MB per overlap
|
|
404
|
+
# while maintaining statistical robustness.
|
|
405
|
+
|
|
406
|
+
# Args:
|
|
407
|
+
# ref_complex, sec_complex: Real, coregistered (at least coarsely)
|
|
408
|
+
# complex SLC arrays, same shape -- full burst stack, or an
|
|
409
|
+
# already-cropped extract (see row_offset).
|
|
410
|
+
# swath_timing: Real burst metadata from annotation.parse_burst_info().
|
|
411
|
+
# azimuth_time_interval_s: Real per-line time spacing.
|
|
412
|
+
# row_offset: Real full-scene row that ref_complex/sec_complex's
|
|
413
|
+
# row 0 corresponds to. Default 0 assumes the arrays start at
|
|
414
|
+
# the full-scene row 0.
|
|
415
|
+
# delta_f_ovl_hz: Doppler centroid difference in the overlap.
|
|
416
|
+
# coherence_threshold: Minimum MEDIAN windowed coherence for an
|
|
417
|
+
# overlap to be considered usable.
|
|
418
|
+
|
|
419
|
+
# Returns:
|
|
420
|
+
# (shift_seconds, per_overlap_shifts): shift_seconds is the real,
|
|
421
|
+
# median azimuth timing shift (seconds) across all valid burst
|
|
422
|
+
# overlaps (None if no overlap gave a usable estimate);
|
|
423
|
+
# per_overlap_shifts is one entry per adjacent burst pair (None
|
|
424
|
+
# where that overlap's coherence was too low or fell entirely
|
|
425
|
+
# outside the given arrays).
|
|
426
|
+
# """
|
|
427
|
+
# import numpy as np
|
|
428
|
+
|
|
429
|
+
# if not isinstance(swath_timing, SwathTiming):
|
|
430
|
+
# raise TypeError(
|
|
431
|
+
# f"estimate_esd_shift_per_burst_overlap expects a SwathTiming "
|
|
432
|
+
# f"object, got {type(swath_timing).__name__}. Pass the result of "
|
|
433
|
+
# f"parse_burst_info(), not a raw bursts list."
|
|
434
|
+
# )
|
|
435
|
+
|
|
436
|
+
# overlaps = compute_overlap_row_ranges(swath_timing, azimuth_time_interval_s)
|
|
437
|
+
# per_overlap_shifts: List[Optional[float]] = []
|
|
438
|
+
|
|
439
|
+
# n_outside = 0
|
|
440
|
+
# n_below_coh = 0
|
|
441
|
+
# n_usable = 0
|
|
442
|
+
|
|
443
|
+
# h, w = ref_complex.shape
|
|
444
|
+
|
|
445
|
+
# for i, ((bw_start, bw_end), (fw_start, fw_end)) in enumerate(overlaps):
|
|
446
|
+
# # Map full-scene rows to local array rows
|
|
447
|
+
# local_bw_start = bw_start - row_offset
|
|
448
|
+
# local_bw_end = bw_end - row_offset
|
|
449
|
+
# local_fw_start = fw_start - row_offset
|
|
450
|
+
# local_fw_end = fw_end - row_offset
|
|
451
|
+
|
|
452
|
+
# # Check bounds
|
|
453
|
+
# if (local_bw_start < 0 or local_bw_end >= h
|
|
454
|
+
# or local_fw_start < 0 or local_fw_end >= h):
|
|
455
|
+
# n_outside += 1
|
|
456
|
+
# per_overlap_shifts.append(None)
|
|
457
|
+
# logger.debug(
|
|
458
|
+
# "Overlap %d<->%d: outside array bounds "
|
|
459
|
+
# "(local rows bw=[%d,%d], fw=[%d,%d], array height=%d)",
|
|
460
|
+
# i, i + 1, local_bw_start, local_bw_end,
|
|
461
|
+
# local_fw_start, local_fw_end, h,
|
|
462
|
+
# )
|
|
463
|
+
# continue
|
|
464
|
+
|
|
465
|
+
# # Extract overlap regions
|
|
466
|
+
# ref_bw = ref_complex[local_bw_start:local_bw_end + 1, :]
|
|
467
|
+
# sec_bw = sec_complex[local_bw_start:local_bw_end + 1, :]
|
|
468
|
+
# ref_fw = ref_complex[local_fw_start:local_fw_end + 1, :]
|
|
469
|
+
# sec_fw = sec_complex[local_fw_start:local_fw_end + 1, :]
|
|
470
|
+
|
|
471
|
+
# # Estimate shift with windowed coherence
|
|
472
|
+
# shift_s, median_coh = _estimate_shift_from_overlap(
|
|
473
|
+
# ref_bw, sec_bw, ref_fw, sec_fw,
|
|
474
|
+
# delta_f_ovl_hz=delta_f_ovl_hz,
|
|
475
|
+
# coherence_threshold=coherence_threshold,
|
|
476
|
+
# )
|
|
477
|
+
|
|
478
|
+
# if shift_s is None:
|
|
479
|
+
# n_below_coh += 1
|
|
480
|
+
# per_overlap_shifts.append(None)
|
|
481
|
+
# logger.debug(
|
|
482
|
+
# "Overlap %d<->%d: median windowed coherence %.3f < threshold %.2f",
|
|
483
|
+
# i, i + 1, median_coh, coherence_threshold,
|
|
484
|
+
# )
|
|
485
|
+
# else:
|
|
486
|
+
# n_usable += 1
|
|
487
|
+
# per_overlap_shifts.append(shift_s)
|
|
488
|
+
# logger.debug(
|
|
489
|
+
# "Overlap %d<->%d: shift=%.6f ms, median_coh=%.3f",
|
|
490
|
+
# i, i + 1, shift_s * 1000, median_coh,
|
|
491
|
+
# )
|
|
492
|
+
|
|
493
|
+
# # Report diagnostics
|
|
494
|
+
# if n_usable == 0:
|
|
495
|
+
# if n_outside > 0 and n_below_coh > 0:
|
|
496
|
+
# logger.warning(
|
|
497
|
+
# "Real per-burst-overlap ESD found no usable burst overlaps "
|
|
498
|
+
# "out of %d total: %d fell outside the given (possibly "
|
|
499
|
+
# "cropped) arrays, %d were below coherence_threshold=%.2f. "
|
|
500
|
+
# "A mix of both -- see the per-overlap detail at DEBUG level.",
|
|
501
|
+
# len(overlaps), n_outside, n_below_coh, coherence_threshold,
|
|
502
|
+
# )
|
|
503
|
+
# elif n_outside > 0:
|
|
504
|
+
# logger.warning(
|
|
505
|
+
# "Real per-burst-overlap ESD found no usable burst overlaps "
|
|
506
|
+
# "out of %d total: all %d fell outside the given (possibly "
|
|
507
|
+
# "cropped) arrays.",
|
|
508
|
+
# len(overlaps), n_outside,
|
|
509
|
+
# )
|
|
510
|
+
# else:
|
|
511
|
+
# logger.warning(
|
|
512
|
+
# "Real per-burst-overlap ESD found no usable burst overlaps "
|
|
513
|
+
# "out of %d total: 0 fell outside the given (possibly "
|
|
514
|
+
# "cropped) arrays, %d were below coherence_threshold=%.2f. "
|
|
515
|
+
# "Every real, in-bounds overlap was tested and found "
|
|
516
|
+
# "genuinely below the coherence threshold -- this reflects "
|
|
517
|
+
# "real decorrelation in the data, not something a larger "
|
|
518
|
+
# "AOI would fix.",
|
|
519
|
+
# len(overlaps), n_below_coh, coherence_threshold,
|
|
520
|
+
# )
|
|
521
|
+
# return None, per_overlap_shifts
|
|
522
|
+
|
|
523
|
+
# # Combine valid shifts via median (robust to outliers)
|
|
524
|
+
# valid_shifts = [s for s in per_overlap_shifts if s is not None]
|
|
525
|
+
# combined_shift = float(np.median(valid_shifts))
|
|
526
|
+
|
|
527
|
+
# logger.info(
|
|
528
|
+
# "Real per-burst-overlap ESD: %d/%d overlaps usable, "
|
|
529
|
+
# "combined shift=%.6f ms (%.4f px)",
|
|
530
|
+
# n_usable, len(overlaps),
|
|
531
|
+
# combined_shift * 1000,
|
|
532
|
+
# combined_shift / azimuth_time_interval_s,
|
|
533
|
+
# )
|
|
534
|
+
|
|
535
|
+
# return combined_shift, per_overlap_shifts
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
# def overlap_time_windows(swath_timing, dt):
|
|
539
|
+
# """Absolute azimuth-time windows for each adjacent-burst overlap."""
|
|
540
|
+
# bursts = swath_timing.bursts
|
|
541
|
+
# L = swath_timing.lines_per_burst
|
|
542
|
+
# out = []
|
|
543
|
+
# for i in range(len(bursts) - 1):
|
|
544
|
+
# start = bursts[i + 1].azimuth_time
|
|
545
|
+
# end = bursts[i].azimuth_time + timedelta(seconds=(L - 1) * dt)
|
|
546
|
+
# out.append((i, start, end))
|
|
547
|
+
# return out
|