PyGeoFetch 2.0.2__py3-none-any.whl → 2.0.3__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/meta.yaml +1 -1
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.3.dist-info}/METADATA +1 -1
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.3.dist-info}/RECORD +11 -11
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.3.dist-info}/WHEEL +0 -0
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.3.dist-info}/entry_points.txt +0 -0
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.3.dist-info}/licenses/LICENSE +0 -0
- {pygeofetch-2.0.2.dist-info → pygeofetch-2.0.3.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.3 — 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.3"
|
|
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.3 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.3 — 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
|
|
@@ -372,8 +372,28 @@ class InterferogramGenerator:
|
|
|
372
372
|
except ImportError:
|
|
373
373
|
raise ImportError('rasterio required: pip install "pygeofetch[geo]"')
|
|
374
374
|
|
|
375
|
+
# with rasterio.open(path) as src:
|
|
376
|
+
# profile = src.profile.copy()
|
|
377
|
+
# dtype = src.dtypes[0]
|
|
378
|
+
|
|
375
379
|
with rasterio.open(path) as src:
|
|
376
380
|
profile = src.profile.copy()
|
|
381
|
+
|
|
382
|
+
# FIX: Full sub-swath files are GCP-georeferenced and lack an affine
|
|
383
|
+
# transform. Downstream steps (flat-earth, topo phase, AOI crop)
|
|
384
|
+
# require a transform. Fit an approximate one from the GCPs.
|
|
385
|
+
if profile.get("transform") is None or getattr(profile.get("transform"), "is_identity", False):
|
|
386
|
+
gcps, gcp_crs = src.gcps
|
|
387
|
+
if gcps and len(gcps) >= 4:
|
|
388
|
+
from rasterio.transform import from_gcps
|
|
389
|
+
profile["transform"] = from_gcps(gcps)
|
|
390
|
+
if gcp_crs:
|
|
391
|
+
profile["crs"] = gcp_crs
|
|
392
|
+
logger.info(
|
|
393
|
+
"Fitted approximate affine transform from %d GCPs for %s",
|
|
394
|
+
len(gcps), path.name
|
|
395
|
+
)
|
|
396
|
+
|
|
377
397
|
dtype = src.dtypes[0]
|
|
378
398
|
height = src.height
|
|
379
399
|
width = src.width
|
|
@@ -481,9 +501,15 @@ class InterferogramGenerator:
|
|
|
481
501
|
|
|
482
502
|
# Convert AOI corners to pixel coordinates
|
|
483
503
|
# Note: rowcol expects (x, y) = (lon, lat)
|
|
504
|
+
# Convert AOI corners to pixel coordinates
|
|
505
|
+
# FIX: Sentinel-1 grids are often rotated. We must check all 4 corners
|
|
506
|
+
# to find the true pixel bounding box, not just two opposite corners.
|
|
484
507
|
try:
|
|
485
|
-
|
|
486
|
-
|
|
508
|
+
lons = [aoi_bbox.min_lon, aoi_bbox.max_lon, aoi_bbox.min_lon, aoi_bbox.max_lon]
|
|
509
|
+
lats = [aoi_bbox.min_lat, aoi_bbox.min_lat, aoi_bbox.max_lat, aoi_bbox.max_lat]
|
|
510
|
+
rows, cols = rowcol(transform, lons, lats)
|
|
511
|
+
row_min, row_max = min(rows), max(rows)
|
|
512
|
+
col_min, col_max = min(cols), max(cols)
|
|
487
513
|
except Exception as exc:
|
|
488
514
|
logger.warning(
|
|
489
515
|
"Failed to convert AOI to pixel coordinates (%s) — "
|
|
@@ -499,9 +525,9 @@ class InterferogramGenerator:
|
|
|
499
525
|
|
|
500
526
|
# Add margin
|
|
501
527
|
row_min = max(0, row_min - margin_px)
|
|
502
|
-
row_max = min(ref_debursted.shape[0], row_max + margin_px)
|
|
528
|
+
row_max = min(ref_debursted.shape[0], row_max + margin_px + 1) # +1 because slice end is exclusive
|
|
503
529
|
col_min = max(0, col_min - margin_px)
|
|
504
|
-
col_max = min(ref_debursted.shape[1], col_max + margin_px)
|
|
530
|
+
col_max = min(ref_debursted.shape[1], col_max + margin_px + 1) # +1 because slice end is exclusive
|
|
505
531
|
|
|
506
532
|
n_rows = row_max - row_min
|
|
507
533
|
n_cols = col_max - col_min
|
|
@@ -850,6 +876,51 @@ class InterferogramGenerator:
|
|
|
850
876
|
f"parse_burst_info returned {type(sec_burst_info).__name__} "
|
|
851
877
|
f"instead of SwathTiming for secondary — check annotation.py"
|
|
852
878
|
)
|
|
879
|
+
|
|
880
|
+
# FIX: Define this BEFORE the common-ground diagnostic uses it
|
|
881
|
+
azimuth_time_interval_s = ref_geom.azimuth_time_interval_s
|
|
882
|
+
|
|
883
|
+
# ── Common-ground diagnostic ─────────────────────────────────
|
|
884
|
+
|
|
885
|
+
# ── Common-ground diagnostic ─────────────────────────────────
|
|
886
|
+
# Report, per pair, how many azimuth lines of double-covered
|
|
887
|
+
# ground each reference burst-overlap actually shares with the
|
|
888
|
+
# secondary. This is the unambiguous answer to "how much common
|
|
889
|
+
# ground does this pair really have for ESD?" -- the thing the
|
|
890
|
+
# old "outside the array / below coherence" message could not
|
|
891
|
+
# distinguish.
|
|
892
|
+
from pygeofetch.insar.esd import compute_common_ground_overlaps
|
|
893
|
+
|
|
894
|
+
common_report = compute_common_ground_overlaps(
|
|
895
|
+
ref_burst_info, sec_burst_info, azimuth_time_interval_s
|
|
896
|
+
)
|
|
897
|
+
common_lines = [r["common_lines"] for r in common_report]
|
|
898
|
+
total_common = sum(common_lines)
|
|
899
|
+
|
|
900
|
+
if total_common == 0:
|
|
901
|
+
logger.warning(
|
|
902
|
+
"ESD common-ground diagnostic: common double-covered "
|
|
903
|
+
"lines per ref overlap: [%s] (total %d) -- this pair "
|
|
904
|
+
"shares NO double-covered ground; ESD is physically "
|
|
905
|
+
"unmeasurable here (cross-family burst timing), not a "
|
|
906
|
+
"processing bug.",
|
|
907
|
+
", ".join(str(n) for n in common_lines), total_common,
|
|
908
|
+
)
|
|
909
|
+
else:
|
|
910
|
+
logger.info(
|
|
911
|
+
"ESD common-ground diagnostic: common double-covered "
|
|
912
|
+
"lines per ref overlap: [%s] (total %d)",
|
|
913
|
+
", ".join(str(n) for n in common_lines), total_common,
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
metadata["esd_common_ground_lines"] = common_lines
|
|
917
|
+
metadata["esd_common_ground_total"] = total_common
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
# ── ESD shift per burst overlap ────────────────────────────────────────
|
|
921
|
+
# Compute the ESD shift per reference overlap, using the
|
|
922
|
+
# per-reference-overlap diagnostic of how much COMMON double-covered
|
|
923
|
+
# ground this date pair actually shares.
|
|
853
924
|
azimuth_time_interval_s = ref_geom.azimuth_time_interval_s
|
|
854
925
|
|
|
855
926
|
ref_row_off, _ = (
|
|
@@ -881,6 +952,7 @@ class InterferogramGenerator:
|
|
|
881
952
|
esd_shift_s, per_overlap = estimate_esd_shift_per_burst_overlap(
|
|
882
953
|
ref_complex, sec_complex, ref_burst_info, azimuth_time_interval_s,
|
|
883
954
|
row_offset=int(ref_row_off),
|
|
955
|
+
sec_swath_timing=sec_burst_info, # NEW
|
|
884
956
|
)
|
|
885
957
|
if esd_shift_s is not None:
|
|
886
958
|
esd_shift_px = esd_shift_s / azimuth_time_interval_s
|
|
@@ -1278,14 +1350,35 @@ class InterferogramGenerator:
|
|
|
1278
1350
|
ref_center_time_fe = ref_geom_fe.azimuth_time(ref_geom_fe.n_lines / 2)
|
|
1279
1351
|
sec_center_time_fe = sec_geom_fe.azimuth_time(sec_geom_fe.n_lines / 2)
|
|
1280
1352
|
|
|
1281
|
-
with rasterio.open(str(reference)) as _src:
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1353
|
+
# with rasterio.open(str(reference)) as _src:
|
|
1354
|
+
# b = _src.bounds
|
|
1355
|
+
# margin_lon = (b.right - b.left) * 0.2
|
|
1356
|
+
# margin_lat = (b.top - b.bottom) * 0.2
|
|
1357
|
+
# sample_bounds_fe = (
|
|
1358
|
+
# b.left - margin_lon, b.bottom - margin_lat,
|
|
1359
|
+
# b.right + margin_lon, b.top + margin_lat,
|
|
1360
|
+
# )
|
|
1361
|
+
|
|
1362
|
+
# FIX: Use the profile we already read (which now has the GCP-fitted
|
|
1363
|
+
# transform) instead of reopening the file. Reopening a GCP-only
|
|
1364
|
+
# file yields pixel-coordinate bounds, which breaks sampling.
|
|
1365
|
+
transform_fe = profile.get("transform")
|
|
1366
|
+
if transform_fe is not None:
|
|
1367
|
+
from rasterio.transform import array_bounds
|
|
1368
|
+
# array_bounds returns (left, bottom, right, top)
|
|
1369
|
+
left, bottom, right, top = array_bounds(
|
|
1370
|
+
profile.get("height", ref_complex_native.shape[0]),
|
|
1371
|
+
profile.get("width", ref_complex_native.shape[1]),
|
|
1372
|
+
transform_fe
|
|
1373
|
+
)
|
|
1374
|
+
margin_lon = (right - left) * 0.2
|
|
1375
|
+
margin_lat = (top - bottom) * 0.2
|
|
1285
1376
|
sample_bounds_fe = (
|
|
1286
|
-
|
|
1287
|
-
|
|
1377
|
+
left - margin_lon, bottom - margin_lat,
|
|
1378
|
+
right + margin_lon, top + margin_lat,
|
|
1288
1379
|
)
|
|
1380
|
+
else:
|
|
1381
|
+
sample_bounds_fe = None
|
|
1289
1382
|
|
|
1290
1383
|
wavelength_m = 0.05546576 # real Sentinel-1 C-band wavelength
|
|
1291
1384
|
flat_earth_phase = compute_flat_earth_phase(
|
|
@@ -1408,8 +1501,25 @@ class InterferogramGenerator:
|
|
|
1408
1501
|
|
|
1409
1502
|
with rasterio.open(path) as src:
|
|
1410
1503
|
profile = src.profile.copy()
|
|
1504
|
+
|
|
1505
|
+
# FIX: Full sub-swath files are GCP-georeferenced and lack an affine
|
|
1506
|
+
# transform. Fit an approximate one from the GCPs so downstream
|
|
1507
|
+
# steps (flat-earth, topo phase, AOI crop) have a valid transform.
|
|
1508
|
+
if profile.get("transform") is None or getattr(profile.get("transform"), "is_identity", False):
|
|
1509
|
+
gcps, gcp_crs = src.gcps
|
|
1510
|
+
if gcps and len(gcps) >= 4:
|
|
1511
|
+
from rasterio.transform import from_gcps
|
|
1512
|
+
profile["transform"] = from_gcps(gcps)
|
|
1513
|
+
if gcp_crs:
|
|
1514
|
+
profile["crs"] = gcp_crs
|
|
1515
|
+
logger.info(
|
|
1516
|
+
"Fitted approximate affine transform from %d GCPs for %s",
|
|
1517
|
+
len(gcps), path.name
|
|
1518
|
+
)
|
|
1519
|
+
|
|
1411
1520
|
dtype = src.dtypes[0]
|
|
1412
1521
|
if "complex" in dtype:
|
|
1522
|
+
|
|
1413
1523
|
data = src.read(1)
|
|
1414
1524
|
elif src.count >= 2:
|
|
1415
1525
|
real = src.read(1).astype(np.float32)
|
|
@@ -1486,11 +1596,26 @@ class InterferogramGenerator:
|
|
|
1486
1596
|
# full Sentinel-1 swath and completely misses the small clipped
|
|
1487
1597
|
# AOI where the DEM actually exists.
|
|
1488
1598
|
# ═══════════════════════════════════════════════════════════
|
|
1599
|
+
# sample_bounds = None
|
|
1600
|
+
# if reference_extracted_path and Path(reference_extracted_path).exists():
|
|
1601
|
+
# with rasterio.open(reference_extracted_path) as src:
|
|
1602
|
+
# b = src.bounds
|
|
1603
|
+
# sample_bounds = (b.left, b.bottom, b.right, b.top)
|
|
1604
|
+
|
|
1489
1605
|
sample_bounds = None
|
|
1490
1606
|
if reference_extracted_path and Path(reference_extracted_path).exists():
|
|
1491
1607
|
with rasterio.open(reference_extracted_path) as src:
|
|
1492
|
-
|
|
1493
|
-
|
|
1608
|
+
# FIX: Handle GCP-only files (full sub-swath). src.bounds
|
|
1609
|
+
# returns pixel coordinates for GCP-only files. Fit an
|
|
1610
|
+
# approximate transform from GCPs to get real lon/lat bounds.
|
|
1611
|
+
if src.transform.is_identity and src.gcps[0]:
|
|
1612
|
+
from rasterio.transform import from_gcps, array_bounds
|
|
1613
|
+
approx_transform = from_gcps(src.gcps[0])
|
|
1614
|
+
left, bottom, right, top = array_bounds(src.height, src.width, approx_transform)
|
|
1615
|
+
sample_bounds = (left, bottom, right, top)
|
|
1616
|
+
else:
|
|
1617
|
+
b = src.bounds
|
|
1618
|
+
sample_bounds = (b.left, b.bottom, b.right, b.top)
|
|
1494
1619
|
|
|
1495
1620
|
grid_rows, grid_cols, off_rows, off_cols = compute_offset_field_from_dem(
|
|
1496
1621
|
ref_geom, ref_orbit, sec_geom, sec_orbit, dem,
|
pygeofetch/meta.yaml
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
pygeofetch/__init__.py,sha256=
|
|
2
|
-
pygeofetch/meta.yaml,sha256=
|
|
1
|
+
pygeofetch/__init__.py,sha256=g7_nMbdOgkKQ2ey8F9Vne6cto0205qDjI-sfcj-JULs,1990
|
|
2
|
+
pygeofetch/meta.yaml,sha256=k9ZW4b5eJItOWjelJsCPU0XUp1-2KgXXiklYjo4vxSI,1280
|
|
3
3
|
pygeofetch/cli/__init__.py,sha256=QZMIIHEgmjGFieucqq9fZ5uephxP9-jdu1s8Q4qqw8o,30
|
|
4
4
|
pygeofetch/cli/auth_commands.py,sha256=YqbrN_BYcAYCaUIYLpSJ_VIw35tKFUiD9Kc55SWbDWg,7742
|
|
5
5
|
pygeofetch/cli/config_commands.py,sha256=1Pps0HLCLPODMlM6SFV8ru3IKCDpexSTz8uKIXvIIF8,3918
|
|
6
6
|
pygeofetch/cli/download_commands.py,sha256=reQ9vmA50mj1zqechjLHAOe_cjBfa1miqfWa8UiIlxU,11741
|
|
7
7
|
pygeofetch/cli/index_commands.py,sha256=IoLGrsSU-lTcBitFFsg8EtucqSKZtNL-0ODj_I8Twew,10020
|
|
8
|
-
pygeofetch/cli/main.py,sha256=
|
|
8
|
+
pygeofetch/cli/main.py,sha256=6p8mrv2zmIF0NFV04v1gf4Gp0VU48xHmRC4XlXi1pW8,30335
|
|
9
9
|
pygeofetch/cli/pipeline_process_commands.py,sha256=OvuouT35zZlrC-7hWAJRfaY_aUNk4ScK0Fia9WVB_Gw,6904
|
|
10
10
|
pygeofetch/cli/postprocess_commands.py,sha256=kof_f-kjjvYl5eqPqrcNHSOUM_qVkGdSh6-N4vcx2y4,5825
|
|
11
11
|
pygeofetch/cli/preprocess_commands.py,sha256=cFp1VR03Q1pmCwiBWYmS5lXF3NOnzVAq3XgrZh3rBdU,8696
|
|
@@ -28,12 +28,12 @@ pygeofetch/insar/annotation.py,sha256=kg7bjR9jJ1U_2cztbOBWNMDQA2KqY_LvfaJTI5nl35
|
|
|
28
28
|
pygeofetch/insar/atmosphere.py,sha256=GO4qAKF4Rfrj-AfVQ_VprblyO9-7cGKOGPltOnSxusk,32475
|
|
29
29
|
pygeofetch/insar/coregister.py,sha256=tyQ5V6WH0mOcDJt3B30ScpbwdXC484hABCD7zPN5RB4,57355
|
|
30
30
|
pygeofetch/insar/deburst.py,sha256=yiFHc-Ht_avzNrU-qjFk0RkdKfvvvovr-VCoIKGO6IY,5611
|
|
31
|
-
pygeofetch/insar/esd.py,sha256=
|
|
31
|
+
pygeofetch/insar/esd.py,sha256=r4sHDnbFocKqD3gamzaNRodLK2WtmgxOmQ62utVn6Oc,21488
|
|
32
32
|
pygeofetch/insar/extraction.py,sha256=4d7c_vSUpyzEiCP2qk41S9tX1UAN78BOhyIc4RCm74g,40610
|
|
33
33
|
pygeofetch/insar/flatearth.py,sha256=N2S-ZI7yi-BiC3d6jv1w8lLHd47lAYDt-aTdLng5E-c,13141
|
|
34
34
|
pygeofetch/insar/geolocation.py,sha256=-vwTYFhvutKjI4c_f-3BGkfV3XnnDUh5r7KwQbLlErU,27465
|
|
35
35
|
pygeofetch/insar/gpu.py,sha256=E4Dx9-78s2GyKl2_dJrc6lXIHiy67tlqHlpkYue_418,3933
|
|
36
|
-
pygeofetch/insar/interferogram.py,sha256=
|
|
36
|
+
pygeofetch/insar/interferogram.py,sha256=tdX4lYilv-tAY5anr1aAeVO_7Zc-BEOCgGmOydtcRng,94264
|
|
37
37
|
pygeofetch/insar/ionosphere.py,sha256=vG60pyUtxyoJqLCMb_KjFEJvkDE1Bl8fqTzTTbrTIX8,30101
|
|
38
38
|
pygeofetch/insar/splitspectrum_diagnostic.py,sha256=_IlfBs__BW7Yz2RwdmWxfrOspe67jHMgJPyNyzK_rYE,7705
|
|
39
39
|
pygeofetch/insar/stack_selection.py,sha256=ebFwO063cCT9kn3zLPlkYu6fpnfx-SRC90Ch18E7qJk,13157
|
|
@@ -104,9 +104,9 @@ pygeofetch/utils/validators.py,sha256=f-k3mGFP4byjimyDymhPEdsz5I16jZacx1HSnwrARj
|
|
|
104
104
|
pygeofetch/viz/__init__.py,sha256=xf2C9IL4n3bv3oxk8RYkogq7nkuNyt18V-urAoXWRQY,449
|
|
105
105
|
pygeofetch/viz/map.py,sha256=vYO99NvTumCAVXVtXcEzMx_cpmH-FvSVNWl8X0PNSgE,15494
|
|
106
106
|
pygeofetch/viz/plot.py,sha256=I16rdOWb80gcjYDsDvTMzuyek7QHgUNOQxY2RejBAPs,53675
|
|
107
|
-
pygeofetch-2.0.
|
|
108
|
-
pygeofetch-2.0.
|
|
109
|
-
pygeofetch-2.0.
|
|
110
|
-
pygeofetch-2.0.
|
|
111
|
-
pygeofetch-2.0.
|
|
112
|
-
pygeofetch-2.0.
|
|
107
|
+
pygeofetch-2.0.3.dist-info/licenses/LICENSE,sha256=zavMyhODD6NvZTmmKFEtATrngwY4CU_NEtY7lhjxGnI,1075
|
|
108
|
+
pygeofetch-2.0.3.dist-info/METADATA,sha256=ogUCAYOSzmGFw-n2_2siFpF3CNsKg1uB5mhjFfXv0pQ,43254
|
|
109
|
+
pygeofetch-2.0.3.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
110
|
+
pygeofetch-2.0.3.dist-info/entry_points.txt,sha256=dHiUXLF5xNJ8vgtXZoUQNleEMQUeDPZiKQOE1tW8SDY,55
|
|
111
|
+
pygeofetch-2.0.3.dist-info/top_level.txt,sha256=rOmnZargIfNk99fwVOqt6OJyJvpSTBBAYO5HU37yhHk,11
|
|
112
|
+
pygeofetch-2.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|