sarpyx 0.1.5__py3-none-any.whl → 0.1.6__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.
Files changed (48) hide show
  1. docs/examples/advanced/batch_processing.py +1 -1
  2. docs/examples/advanced/custom_processing_chains.py +1 -1
  3. docs/examples/advanced/performance_optimization.py +1 -1
  4. docs/examples/basic/snap_integration.py +1 -1
  5. docs/examples/intermediate/quality_assessment.py +1 -1
  6. outputs/baseline/20260205-234828/__init__.py +33 -0
  7. outputs/baseline/20260205-234828/main.py +493 -0
  8. outputs/final/20260205-234851/__init__.py +33 -0
  9. outputs/final/20260205-234851/main.py +493 -0
  10. sarpyx/__init__.py +2 -2
  11. sarpyx/algorithms/__init__.py +2 -2
  12. sarpyx/cli/__init__.py +1 -1
  13. sarpyx/cli/focus.py +3 -5
  14. sarpyx/cli/main.py +106 -7
  15. sarpyx/cli/shipdet.py +1 -1
  16. sarpyx/cli/worldsar.py +549 -0
  17. sarpyx/processor/__init__.py +1 -1
  18. sarpyx/processor/core/decode.py +43 -8
  19. sarpyx/processor/core/focus.py +104 -57
  20. sarpyx/science/__init__.py +1 -1
  21. sarpyx/sla/__init__.py +8 -0
  22. sarpyx/sla/metrics.py +101 -0
  23. sarpyx/{snap → snapflow}/__init__.py +1 -1
  24. sarpyx/snapflow/engine.py +6165 -0
  25. sarpyx/{snap → snapflow}/op.py +0 -1
  26. sarpyx/utils/__init__.py +1 -1
  27. sarpyx/utils/geos.py +652 -0
  28. sarpyx/utils/grid.py +285 -0
  29. sarpyx/utils/io.py +77 -9
  30. sarpyx/utils/meta.py +55 -0
  31. sarpyx/utils/nisar_utils.py +652 -0
  32. sarpyx/utils/rfigen.py +108 -0
  33. sarpyx/utils/wkt_utils.py +109 -0
  34. sarpyx/utils/zarr_utils.py +55 -37
  35. {sarpyx-0.1.5.dist-info → sarpyx-0.1.6.dist-info}/METADATA +9 -5
  36. {sarpyx-0.1.5.dist-info → sarpyx-0.1.6.dist-info}/RECORD +41 -32
  37. {sarpyx-0.1.5.dist-info → sarpyx-0.1.6.dist-info}/WHEEL +1 -1
  38. sarpyx-0.1.6.dist-info/licenses/LICENSE +201 -0
  39. sarpyx-0.1.6.dist-info/top_level.txt +4 -0
  40. tests/test_zarr_compat.py +35 -0
  41. sarpyx/processor/core/decode_v0.py +0 -0
  42. sarpyx/processor/core/decode_v1.py +0 -849
  43. sarpyx/processor/core/focus_old.py +0 -1550
  44. sarpyx/processor/core/focus_v1.py +0 -1566
  45. sarpyx/processor/core/focus_v2.py +0 -1625
  46. sarpyx/snap/engine.py +0 -633
  47. sarpyx-0.1.5.dist-info/top_level.txt +0 -2
  48. {sarpyx-0.1.5.dist-info → sarpyx-0.1.6.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,493 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SARPyX Command Line Interface.
4
+
5
+ Main entry point for all sarpyx CLI tools.
6
+ """
7
+
8
+ import argparse
9
+ import sys
10
+ from typing import List
11
+
12
+
13
+ def create_main_parser() -> argparse.ArgumentParser:
14
+ """
15
+ Create the main argument parser with subcommands.
16
+
17
+ Returns:
18
+ Configured ArgumentParser instance.
19
+ """
20
+ parser = argparse.ArgumentParser(
21
+ prog='sarpyx',
22
+ description='SARPyX: A comprehensive toolkit for SAR data processing',
23
+ formatter_class=argparse.RawDescriptionHelpFormatter,
24
+ epilog="""
25
+ Available commands:
26
+ decode Decode Sentinel-1 Level-0 products to zarr format
27
+ focus Focus SAR data using Range-Doppler Algorithm
28
+ shipdet Ship detection using SNAP GPT engine
29
+ unzip Extract SAR data from zip archives
30
+ upload Upload data to Hugging Face Hub
31
+ worldsar Process SAR products with SNAP GPT pipelines and tiling
32
+
33
+ Examples:
34
+ sarpyx decode --input /path/to/file.dat --output /path/to/output
35
+ sarpyx focus --input /path/to/data.zarr --output /path/to/output
36
+ sarpyx shipdet --product-path /path/to/S1A_*.SAFE --outdir /path/to/output
37
+ sarpyx unzip --input /path/to/file.zip --output /path/to/output
38
+ sarpyx upload --folder /path/to/folder --repo username/dataset-name
39
+ sarpyx worldsar --input /path/to/product --output /path/to/output \\
40
+ --cuts-outdir /path/to/tiles --product-wkt "POLYGON ((...))" \\
41
+ --prod-mode S1TOPS
42
+
43
+ For command-specific help:
44
+ sarpyx <command> --help
45
+ """
46
+ )
47
+
48
+ parser.add_argument(
49
+ '--version',
50
+ action='version',
51
+ version='sarpyx-cli 0.1.6'
52
+ )
53
+
54
+ # Create subparsers for different commands
55
+ subparsers = parser.add_subparsers(
56
+ dest='command',
57
+ help='Available commands',
58
+ metavar='<command>'
59
+ )
60
+
61
+ # Decode subcommand
62
+ decode_parser = subparsers.add_parser(
63
+ 'decode',
64
+ help='Decode Sentinel-1 Level-0 products to zarr format',
65
+ description='Decode S1 L0 products (.dat files or .SAFE folders) to zarr format'
66
+ )
67
+ _add_decode_arguments(decode_parser)
68
+
69
+ # Focus subcommand
70
+ focus_parser = subparsers.add_parser(
71
+ 'focus',
72
+ help='Focus SAR data using Range-Doppler Algorithm',
73
+ description='Focus SAR data from zarr files using CoarseRDA processor'
74
+ )
75
+ _add_focus_arguments(focus_parser)
76
+
77
+ # Ship detection subcommand
78
+ shipdet_parser = subparsers.add_parser(
79
+ 'shipdet',
80
+ help='Ship detection using SNAP GPT engine',
81
+ description='Detect ships in SAR data using adaptive thresholding and object discrimination'
82
+ )
83
+ _add_shipdet_arguments(shipdet_parser)
84
+
85
+ # Unzip subcommand
86
+ unzip_parser = subparsers.add_parser(
87
+ 'unzip',
88
+ help='Extract SAR data from zip archives',
89
+ description='Extract zip files containing SAR data'
90
+ )
91
+ _add_unzip_arguments(unzip_parser)
92
+
93
+ # Upload subcommand
94
+ upload_parser = subparsers.add_parser(
95
+ 'upload',
96
+ help='Upload data to Hugging Face Hub',
97
+ description='Upload processed SAR data to Hugging Face Hub'
98
+ )
99
+ _add_upload_arguments(upload_parser)
100
+
101
+ # WorldSAR subcommand
102
+ worldsar_parser = subparsers.add_parser(
103
+ 'worldsar',
104
+ help='Process SAR products with SNAP GPT pipelines and tiling',
105
+ description='Process SAR products from multiple missions and generate tiles'
106
+ )
107
+ _add_worldsar_arguments(worldsar_parser)
108
+
109
+ return parser
110
+
111
+
112
+ def _add_decode_arguments(parser: argparse.ArgumentParser) -> None:
113
+ """
114
+ Add arguments for the decode subcommand.
115
+
116
+ Args:
117
+ parser: The subparser for decode command.
118
+ """
119
+ parser.add_argument(
120
+ '--input',
121
+ type=str,
122
+ required=True,
123
+ help='Path to .dat file or .SAFE folder'
124
+ )
125
+ parser.add_argument(
126
+ '--output',
127
+ type=str,
128
+ default='./decoded_data',
129
+ help='Output directory for decoded files (default: ./decoded_data)'
130
+ )
131
+ parser.add_argument(
132
+ '--verbose',
133
+ '-v',
134
+ action='store_true',
135
+ help='Enable verbose logging'
136
+ )
137
+ parser.add_argument(
138
+ '--debug',
139
+ action='store_true',
140
+ help='Enable debug logging'
141
+ )
142
+
143
+
144
+ def _add_focus_arguments(parser: argparse.ArgumentParser) -> None:
145
+ """
146
+ Add arguments for the focus subcommand.
147
+
148
+ Args:
149
+ parser: The subparser for focus command.
150
+ """
151
+ parser.add_argument(
152
+ '--input',
153
+ type=str,
154
+ required=True,
155
+ help='Input zarr file path'
156
+ )
157
+ parser.add_argument(
158
+ '--output',
159
+ type=str,
160
+ default='./focused_data',
161
+ help='Output directory (default: ./focused_data)'
162
+ )
163
+ parser.add_argument(
164
+ '--slice-height',
165
+ type=int,
166
+ default=15000,
167
+ help='Slice height for processing (default: 15000)'
168
+ )
169
+ parser.add_argument(
170
+ '--verbose',
171
+ '-v',
172
+ action='store_true',
173
+ help='Enable verbose output'
174
+ )
175
+ parser.add_argument(
176
+ '--keep-tmp',
177
+ action='store_true',
178
+ help='Keep temporary files after processing'
179
+ )
180
+
181
+
182
+ def _add_shipdet_arguments(parser: argparse.ArgumentParser) -> None:
183
+ """
184
+ Add arguments for the shipdet subcommand.
185
+
186
+ Args:
187
+ parser: The subparser for shipdet command.
188
+ """
189
+ # Required arguments
190
+ parser.add_argument(
191
+ '--product-path',
192
+ type=str,
193
+ required=True,
194
+ help='Path to the SAR product (.SAFE directory or zip file)'
195
+ )
196
+ parser.add_argument(
197
+ '--outdir',
198
+ type=str,
199
+ required=True,
200
+ help='Output directory for processed data'
201
+ )
202
+
203
+ # Optional processing arguments
204
+ parser.add_argument(
205
+ '--format',
206
+ type=str,
207
+ default='BEAM-DIMAP',
208
+ choices=['BEAM-DIMAP', 'GeoTIFF', 'NetCDF4-CF', 'ENVI', 'HDF5'],
209
+ help='Output format (default: BEAM-DIMAP)'
210
+ )
211
+ parser.add_argument(
212
+ '--gpt-path',
213
+ type=str,
214
+ default=None,
215
+ help='Path to GPT executable (default: None - use system PATH)'
216
+ )
217
+
218
+ # Calibration arguments
219
+ parser.add_argument(
220
+ '--output-complex',
221
+ action='store_true',
222
+ help='Output complex values for calibration (default: False)'
223
+ )
224
+ parser.add_argument(
225
+ '--polarizations',
226
+ type=str,
227
+ nargs='+',
228
+ default=['VV'],
229
+ choices=['VV', 'VH', 'HH', 'HV'],
230
+ help='Polarizations to process (default: VV)'
231
+ )
232
+
233
+ # Adaptive thresholding arguments
234
+ parser.add_argument(
235
+ '--pfa',
236
+ type=float,
237
+ default=6.5,
238
+ help='Probability of false alarm for adaptive thresholding (default: 6.5)'
239
+ )
240
+ parser.add_argument(
241
+ '--background-window-m',
242
+ type=float,
243
+ default=800.0,
244
+ help='Background window size in meters (default: 800.0)'
245
+ )
246
+ parser.add_argument(
247
+ '--guard-window-m',
248
+ type=float,
249
+ default=500.0,
250
+ help='Guard window size in meters (default: 500.0)'
251
+ )
252
+ parser.add_argument(
253
+ '--target-window-m',
254
+ type=float,
255
+ default=50.0,
256
+ help='Target window size in meters (default: 50.0)'
257
+ )
258
+
259
+ # Object discrimination arguments
260
+ parser.add_argument(
261
+ '--min-target-m',
262
+ type=float,
263
+ default=50.0,
264
+ help='Minimum target size in meters (default: 50.0)'
265
+ )
266
+ parser.add_argument(
267
+ '--max-target-m',
268
+ type=float,
269
+ default=600.0,
270
+ help='Maximum target size in meters (default: 600.0)'
271
+ )
272
+
273
+ # Processing options
274
+ parser.add_argument(
275
+ '--skip-calibration',
276
+ action='store_true',
277
+ help='Skip calibration step (default: False)'
278
+ )
279
+ parser.add_argument(
280
+ '--skip-discrimination',
281
+ action='store_true',
282
+ help='Skip object discrimination step (default: False)'
283
+ )
284
+ parser.add_argument(
285
+ '--verbose',
286
+ '-v',
287
+ action='store_true',
288
+ help='Enable verbose output (default: False)'
289
+ )
290
+
291
+
292
+ def _add_unzip_arguments(parser: argparse.ArgumentParser) -> None:
293
+ """
294
+ Add arguments for the unzip subcommand.
295
+
296
+ Args:
297
+ parser: The subparser for unzip command.
298
+ """
299
+ parser.add_argument(
300
+ '--input',
301
+ type=str,
302
+ required=True,
303
+ help='Path to zip file or directory containing zip files'
304
+ )
305
+ parser.add_argument(
306
+ '--output',
307
+ type=str,
308
+ default='./extracted_data',
309
+ help='Output directory for extracted files (default: ./extracted_data)'
310
+ )
311
+ parser.add_argument(
312
+ '--recursive',
313
+ '-r',
314
+ action='store_true',
315
+ help='Recursively search for zip files in subdirectories'
316
+ )
317
+ parser.add_argument(
318
+ '--verbose',
319
+ '-v',
320
+ action='store_true',
321
+ help='Enable verbose logging'
322
+ )
323
+
324
+
325
+ def _add_upload_arguments(parser: argparse.ArgumentParser) -> None:
326
+ """
327
+ Add arguments for the upload subcommand.
328
+
329
+ Args:
330
+ parser: The subparser for upload command.
331
+ """
332
+ parser.add_argument(
333
+ '--folder',
334
+ type=str,
335
+ required=True,
336
+ help='Path to the folder to upload'
337
+ )
338
+ parser.add_argument(
339
+ '--repo',
340
+ type=str,
341
+ required=True,
342
+ help='Repository ID in format username/repo-name'
343
+ )
344
+ parser.add_argument(
345
+ '--repo-type',
346
+ type=str,
347
+ default='dataset',
348
+ choices=['dataset', 'model', 'space'],
349
+ help='Type of repository (default: dataset)'
350
+ )
351
+ parser.add_argument(
352
+ '--verbose',
353
+ '-v',
354
+ action='store_true',
355
+ help='Enable verbose logging'
356
+ )
357
+
358
+
359
+ def _add_worldsar_arguments(parser: argparse.ArgumentParser) -> None:
360
+ """
361
+ Add arguments for the worldsar subcommand.
362
+
363
+ Args:
364
+ parser: The subparser for worldsar command.
365
+ """
366
+ parser.add_argument(
367
+ '--input',
368
+ '-i',
369
+ dest='product_path',
370
+ type=str,
371
+ required=True,
372
+ help='Path to the input SAR product.'
373
+ )
374
+ parser.add_argument(
375
+ '--output',
376
+ '-o',
377
+ dest='output_dir',
378
+ type=str,
379
+ required=True,
380
+ help='Directory to save the processed output.'
381
+ )
382
+ parser.add_argument(
383
+ '--cuts-outdir',
384
+ '--cuts_outdir',
385
+ dest='cuts_outdir',
386
+ type=str,
387
+ required=True,
388
+ help='Where to store the tiles after extraction.'
389
+ )
390
+ parser.add_argument(
391
+ '--product-wkt',
392
+ '--product_wkt',
393
+ dest='product_wkt',
394
+ type=str,
395
+ required=True,
396
+ help='WKT string defining the product region of interest.'
397
+ )
398
+ parser.add_argument(
399
+ '--prod-mode',
400
+ '--prod_mode',
401
+ dest='prod_mode',
402
+ type=str,
403
+ required=True,
404
+ help='Product mode: ["S1TOPS", "S1STRIP", "BM", "NISAR", "TSX", "CSG", "ICE"].'
405
+ )
406
+ parser.add_argument(
407
+ '--gpt-path',
408
+ dest='gpt_path',
409
+ type=str,
410
+ default=None,
411
+ help='Override GPT executable path (default: gpt_path env var).'
412
+ )
413
+ parser.add_argument(
414
+ '--grid-path',
415
+ dest='grid_path',
416
+ type=str,
417
+ default=None,
418
+ help='Override grid GeoJSON path (default: grid_path env var).'
419
+ )
420
+ parser.add_argument(
421
+ '--db-dir',
422
+ dest='db_dir',
423
+ type=str,
424
+ default=None,
425
+ help='Override database output directory (default: db_dir env var).'
426
+ )
427
+ parser.add_argument(
428
+ '--gpt-memory',
429
+ dest='gpt_memory',
430
+ type=str,
431
+ default=None,
432
+ help='Override GPT Java heap (e.g., 24G).'
433
+ )
434
+ parser.add_argument(
435
+ '--gpt-parallelism',
436
+ dest='gpt_parallelism',
437
+ type=int,
438
+ default=None,
439
+ help='Override GPT parallelism (number of tiles).'
440
+ )
441
+
442
+
443
+ def main() -> None:
444
+ """
445
+ Main entry point for sarpyx CLI.
446
+ """
447
+ parser = create_main_parser()
448
+ args = parser.parse_args()
449
+
450
+ # Check if a command was provided
451
+ if args.command is None:
452
+ parser.print_help()
453
+ sys.exit(1)
454
+
455
+ # Route to appropriate command handler
456
+ original_argv = sys.argv.copy()
457
+
458
+ try:
459
+ if args.command == 'decode':
460
+ from .decode import main as decode_main
461
+ sys.argv = ['sarpyx-decode'] + [arg for arg in original_argv[2:]]
462
+ decode_main()
463
+ elif args.command == 'focus':
464
+ from .focus import main as focus_main
465
+ sys.argv = ['sarpyx-focus'] + [arg for arg in original_argv[2:]]
466
+ focus_main()
467
+ elif args.command == 'shipdet':
468
+ from .shipdet import main as shipdet_main
469
+ sys.argv = ['sarpyx-shipdet'] + [arg for arg in original_argv[2:]]
470
+ shipdet_main()
471
+ elif args.command == 'unzip':
472
+ from .unzip import main as unzip_main
473
+ sys.argv = ['sarpyx-unzip'] + [arg for arg in original_argv[2:]]
474
+ unzip_main()
475
+ elif args.command == 'upload':
476
+ from .upload import main as upload_main
477
+ sys.argv = ['sarpyx-upload'] + [arg for arg in original_argv[2:]]
478
+ upload_main()
479
+ elif args.command == 'worldsar':
480
+ from .worldsar import main as worldsar_main
481
+ sys.argv = ['sarpyx-worldsar'] + [arg for arg in original_argv[2:]]
482
+ worldsar_main()
483
+ else:
484
+ print(f'Unknown command: {args.command}', file=sys.stderr)
485
+ sys.exit(1)
486
+ except SystemExit as e:
487
+ sys.exit(e.code)
488
+ finally:
489
+ sys.argv = original_argv
490
+
491
+
492
+ if __name__ == '__main__':
493
+ main()
sarpyx/__init__.py CHANGED
@@ -5,7 +5,7 @@ data processing, including sub-look analysis, visualization, and integration
5
5
  with processing frameworks.
6
6
  """
7
7
 
8
- __version__ = '0.1.0'
8
+ __version__ = '0.1.6'
9
9
  __author__ = 'ESA Phi-Lab'
10
10
 
11
11
  __all__ = [
@@ -33,4 +33,4 @@ def __getattr__(name):
33
33
  _module_cache[name] = module
34
34
  return module
35
35
 
36
- raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
36
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
@@ -4,7 +4,7 @@ This module contains various algorithms for Synthetic Aperture Radar (SAR)
4
4
  data processing and analysis.
5
5
  """
6
6
 
7
- __version__ = '0.1.0'
7
+ __version__ = '0.1.6'
8
8
  __author__ = 'SAR Processing Team'
9
9
 
10
10
  # Import main algorithm modules here when they are created
@@ -15,4 +15,4 @@ __author__ = 'SAR Processing Team'
15
15
 
16
16
  __all__ = [
17
17
  # Add algorithm function/class names here as they are implemented
18
- ]
18
+ ]
sarpyx/cli/__init__.py CHANGED
@@ -15,4 +15,4 @@ __all__ = [
15
15
  'utils',
16
16
  ]
17
17
 
18
- __version__ = '0.1.0'
18
+ __version__ = '0.1.6'
sarpyx/cli/focus.py CHANGED
@@ -186,7 +186,7 @@ def process_sar_slice(
186
186
 
187
187
  # Focus slice data
188
188
  result = focalize_slice(raw_data=raw_data, verbose=verbose)
189
- logger.info(f'✅ Slice focused successfully.')
189
+ logger.info('✅ Slice focused successfully.')
190
190
 
191
191
  # Drop overlapping data
192
192
  result['raw'] = result['raw'][drop_start:-drop_end] if drop_end > 0 else result['raw'][drop_start:]
@@ -198,10 +198,8 @@ def process_sar_slice(
198
198
  if hasattr(result['metadata'], 'iloc'):
199
199
  if drop_end > 0:
200
200
  result['metadata'] = result['metadata'].iloc[drop_start:-drop_end]
201
- result['ephemeris'] = result['ephemeris'].iloc[drop_start:-drop_end]
202
201
  else:
203
202
  result['metadata'] = result['metadata'].iloc[drop_start:]
204
- result['ephemeris'] = result['ephemeris'].iloc[drop_start:]
205
203
 
206
204
  logger.info(f'📉 Dropped overlapping data: start={drop_start}, end={drop_end}')
207
205
  logger.info(f'📊 Focused data shape: {result["raw"].shape}')
@@ -217,7 +215,7 @@ def process_sar_slice(
217
215
  zarr_path = tmp_dir.parent / f'{filename}.zarr'
218
216
  logger.info(f'💾 Saving entire product to: {zarr_path}')
219
217
  dask_slice_saver(result, zarr_path, chunks='auto', clevel=5)
220
- logger.info(f'📂 Product saved successfully.')
218
+ logger.info('📂 Product saved successfully.')
221
219
 
222
220
  # Clean up memory
223
221
  del raw_data, result
@@ -258,7 +256,7 @@ def main() -> None:
258
256
  # Extract product name
259
257
  product_name = input_path.stem
260
258
 
261
- logger.info(f'🚀 Starting SAR data focusing...')
259
+ logger.info('🚀 Starting SAR data focusing...')
262
260
  logger.info(f'📁 Input file: {input_path}')
263
261
  logger.info(f'📁 Output directory: {output_dir}')
264
262
  logger.info(f'📁 Temporary directory: {tmp_dir}')