sarkit-convert 0.1.0__tar.gz → 0.2.0__tar.gz

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 (45) hide show
  1. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/PKG-INFO +8 -18
  2. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/pyproject.toml +20 -21
  3. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/sarkit_convert/_utils.py +32 -0
  4. sarkit_convert-0.2.0/sarkit_convert/_version.py +1 -0
  5. sarkit_convert-0.1.0/sarkit_convert/csk.py → sarkit_convert-0.2.0/sarkit_convert/cosmo.py +216 -59
  6. sarkit_convert-0.2.0/sarkit_convert/create_arp_poly.py +166 -0
  7. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/sarkit_convert/iceye.py +25 -14
  8. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/sarkit_convert/sentinel.py +224 -178
  9. sarkit_convert-0.2.0/sarkit_convert/sidd_metadata.py +201 -0
  10. sarkit_convert-0.1.0/sarkit_convert/tsx.py → sarkit_convert-0.2.0/sarkit_convert/terrasar.py +165 -62
  11. sarkit_convert-0.2.0/tests/core/__init__.py +0 -0
  12. sarkit_convert-0.2.0/tests/core/data/README.md +3 -0
  13. sarkit_convert-0.2.0/tests/core/data/gdal_results.json +8140 -0
  14. sarkit_convert-0.2.0/tests/core/data/generate_gdal_results.py +64 -0
  15. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/LICENSE.txt +1 -0
  16. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/S1A_IW_RAW__0SDV_20250602T135218_20250602T135250_059468_0761DC_3FC1_Channel_IW2_151224.sicd.xml +1081 -0
  17. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/S1A_IW_RAW__0SDV_20250619T232444_20250619T232516_059722_076A87_E109_Channel_IW1_320291.sicd.xml +1174 -0
  18. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/S1B_IW_RAW__0SDV_20210709T233156_20210709T233229_027724_034EFB_2B2D_Channel_IW3_101200.sicd.xml +1235 -0
  19. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/S1B_IW_RAW__0SDV_20210711T135126_20210711T135158_027747_034FBD_708E_Channel_IW2_151224.sicd.xml +1150 -0
  20. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/S1C_IW_RAW__0SDV_20250527T014921_20250527T014954_002510_0053AA_6A5F_Channel_IW2_135523.sicd.xml +1081 -0
  21. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/S1C_IW_RAW__0SDV_20250527T135101_20250527T135133_002517_0053E1_EDEB_Channel_IW2_151224.sicd.xml +1082 -0
  22. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/S1C_IW_RAW__0SDV_20250608T014922_20250608T014954_002685_0058B2_FC36_Channel_IW2_135523.sicd.xml +1081 -0
  23. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/S1C_IW_RAW__0SDV_20250608T135101_20250608T135134_002692_0058EA_1F12_Channel_IW2_151224.sicd.xml +1082 -0
  24. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/S1C_IW_RAW__0SDV_20250615T052352_20250615T052425_002789_005BB9_CA6D_Channel_IW2_359460.sicd.xml +1082 -0
  25. sarkit_convert-0.2.0/tests/core/data/sentinel_sicd_xml/S1C_IW_RAW__0SDV_20250618T233138_20250618T233210_002844_005D47_D58C_Channel_IW3_101200.sicd.xml +1191 -0
  26. sarkit_convert-0.2.0/tests/core/test_create_arp.py +127 -0
  27. sarkit_convert-0.2.0/tests/core/test_dependencies.py +19 -0
  28. sarkit_convert-0.2.0/tests/core/test_sidd_metadata.py +45 -0
  29. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/tests/core/test_utils.py +13 -0
  30. sarkit_convert-0.2.0/tests/cosmo/__init__.py +0 -0
  31. sarkit_convert-0.1.0/tests/cosmo/test_csk.py → sarkit_convert-0.2.0/tests/cosmo/test_cosmo.py +1 -1
  32. sarkit_convert-0.2.0/tests/cosmo/test_dependencies.py +18 -0
  33. sarkit_convert-0.2.0/tests/iceye/__init__.py +0 -0
  34. sarkit_convert-0.2.0/tests/iceye/test_dependencies.py +18 -0
  35. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/tests/iceye/test_iceye.py +3 -1
  36. sarkit_convert-0.2.0/tests/sentinel/__init__.py +0 -0
  37. sarkit_convert-0.2.0/tests/sentinel/test_dependencies.py +18 -0
  38. sarkit_convert-0.2.0/tests/terrasar/__init__.py +0 -0
  39. sarkit_convert-0.2.0/tests/terrasar/test_dependencies.py +18 -0
  40. sarkit_convert-0.1.0/tests/tsx/test_tsx.py → sarkit_convert-0.2.0/tests/terrasar/test_terrasar.py +4 -4
  41. sarkit_convert-0.1.0/sarkit_convert/_version.py +0 -1
  42. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/LICENSE +0 -0
  43. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/README.md +0 -0
  44. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/sarkit_convert/__init__.py +0 -0
  45. {sarkit_convert-0.1.0 → sarkit_convert-0.2.0}/tests/sentinel/test_sentinel.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sarkit-convert
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Python library for converting SAR data to standard formats.
5
5
  Author-Email: Valkyrie Systems Corporation <info@govsco.com>
6
6
  License: MIT
@@ -16,37 +16,27 @@ Classifier: Programming Language :: Python :: 3.13
16
16
  Requires-Python: >=3.11
17
17
  Requires-Dist: lxml>=5.1.0
18
18
  Requires-Dist: numpy>=1.26.3
19
- Requires-Dist: sarkit[verification]>=0.6.0
19
+ Requires-Dist: pyproj>=3.7.2
20
+ Requires-Dist: sarkit>=1.3.0
21
+ Requires-Dist: scipy>=1.15.1
20
22
  Provides-Extra: iceye
21
23
  Requires-Dist: h5py>=3.12.1; extra == "iceye"
22
24
  Requires-Dist: python-dateutil>=2.9.0; extra == "iceye"
23
25
  Provides-Extra: cosmo
26
+ Requires-Dist: astropy>=6.0.0; extra == "cosmo"
24
27
  Requires-Dist: h5py>=3.12.1; extra == "cosmo"
25
28
  Requires-Dist: python-dateutil>=2.9.0; extra == "cosmo"
26
- Requires-Dist: scipy>=1.15.1; extra == "cosmo"
27
29
  Requires-Dist: shapely>=2.0.2; extra == "cosmo"
28
- Provides-Extra: tsx
29
- Requires-Dist: lxml>=5.1.0; extra == "tsx"
30
- Requires-Dist: python-dateutil>=2.9.0; extra == "tsx"
31
- Requires-Dist: sarkit[verification]>=0.5.0; extra == "tsx"
32
- Requires-Dist: scipy>=1.15.1; extra == "tsx"
30
+ Provides-Extra: terrasar
31
+ Requires-Dist: python-dateutil>=2.9.0; extra == "terrasar"
33
32
  Provides-Extra: sentinel
34
33
  Requires-Dist: python-dateutil>=2.9.0; extra == "sentinel"
35
- Requires-Dist: scipy>=1.15.1; extra == "sentinel"
36
34
  Requires-Dist: tifffile>=2025.5.10; extra == "sentinel"
37
35
  Provides-Extra: all
38
36
  Requires-Dist: sarkit-convert[iceye]; extra == "all"
39
37
  Requires-Dist: sarkit-convert[cosmo]; extra == "all"
40
- Requires-Dist: sarkit-convert[tsx]; extra == "all"
38
+ Requires-Dist: sarkit-convert[terrasar]; extra == "all"
41
39
  Requires-Dist: sarkit-convert[sentinel]; extra == "all"
42
- Provides-Extra: dev-lint
43
- Requires-Dist: ruff>=0.3.0; extra == "dev-lint"
44
- Requires-Dist: mypy>=1.8.0; extra == "dev-lint"
45
- Requires-Dist: types-python-dateutil>=2.9.0; extra == "dev-lint"
46
- Provides-Extra: dev-test
47
- Requires-Dist: pytest>=7.4.4; extra == "dev-test"
48
- Provides-Extra: dev
49
- Requires-Dist: sarkit-convert[dev-lint,dev-test]; extra == "dev"
50
40
  Description-Content-Type: text/markdown
51
41
 
52
42
  <div align="center">
@@ -21,9 +21,11 @@ dynamic = []
21
21
  dependencies = [
22
22
  "lxml>=5.1.0",
23
23
  "numpy>=1.26.3",
24
- "sarkit[verification]>=0.6.0",
24
+ "pyproj>=3.7.2",
25
+ "sarkit>=1.3.0",
26
+ "scipy>=1.15.1",
25
27
  ]
26
- version = "0.1.0"
28
+ version = "0.2.0"
27
29
 
28
30
  [project.license]
29
31
  text = "MIT"
@@ -34,43 +36,28 @@ iceye = [
34
36
  "python-dateutil>=2.9.0",
35
37
  ]
36
38
  cosmo = [
39
+ "astropy>=6.0.0",
37
40
  "h5py>=3.12.1",
38
41
  "python-dateutil>=2.9.0",
39
- "scipy>=1.15.1",
40
42
  "shapely>=2.0.2",
41
43
  ]
42
- tsx = [
43
- "lxml>=5.1.0",
44
+ terrasar = [
44
45
  "python-dateutil>=2.9.0",
45
- "sarkit[verification]>=0.5.0",
46
- "scipy>=1.15.1",
47
46
  ]
48
47
  sentinel = [
49
48
  "python-dateutil>=2.9.0",
50
- "scipy>=1.15.1",
51
49
  "tifffile>=2025.5.10",
52
50
  ]
53
51
  all = [
54
52
  "sarkit-convert[iceye]",
55
53
  "sarkit-convert[cosmo]",
56
- "sarkit-convert[tsx]",
54
+ "sarkit-convert[terrasar]",
57
55
  "sarkit-convert[sentinel]",
58
56
  ]
59
- dev-lint = [
60
- "ruff>=0.3.0",
61
- "mypy>=1.8.0",
62
- "types-python-dateutil>=2.9.0",
63
- ]
64
- dev-test = [
65
- "pytest>=7.4.4",
66
- ]
67
- dev = [
68
- "sarkit-convert[dev-test,dev-lint]",
69
- ]
70
57
 
71
58
  [dependency-groups]
72
59
  test = [
73
- "nox>=2024.3.2",
60
+ "nox>=2025.2.9",
74
61
  ]
75
62
  doc = [
76
63
  "sphinx>=7.2.6",
@@ -78,6 +65,16 @@ doc = [
78
65
  "sphinx-rtd-theme>=2.0.0",
79
66
  "sphinxcontrib-autoprogram>=0.1.9",
80
67
  ]
68
+ dev-lint = [
69
+ "ruff>=0.3.0",
70
+ "mypy>=1.8.0",
71
+ "types-python-dateutil>=2.9.0",
72
+ ]
73
+ dev-test = [
74
+ "pytest>=7.4.4",
75
+ "smart-open[http]>=7.5",
76
+ "tifffile>=2025.5.10",
77
+ ]
81
78
 
82
79
  [build-system]
83
80
  requires = [
@@ -109,8 +106,10 @@ preview = true
109
106
 
110
107
  [[tool.mypy.overrides]]
111
108
  module = [
109
+ "astropy.*",
112
110
  "h5py.*",
113
111
  "lxml.*",
112
+ "pyproj.*",
114
113
  "scipy.*",
115
114
  "shapely.*",
116
115
  ]
@@ -124,6 +124,38 @@ def polyfit2d_tol(x, y, z, max_order_x, max_order_y, tol, strict_tol=False):
124
124
  return best[0]
125
125
 
126
126
 
127
+ def polyshift(poly, new_origin):
128
+ """Returns new polynomial with shifted origin
129
+
130
+ Args
131
+ ----
132
+ poly: array-like
133
+ 1d polynomial coefficients, with constant term first
134
+ new_origin: float
135
+ location in `poly`'s domain to place new polynomial's origin
136
+
137
+ Returns
138
+ -------
139
+ new_poly
140
+ polynomial of same order as `poly` for which new_poly(0) == poly(new_origin)
141
+ """
142
+
143
+ working_coeffs = np.array(list(reversed(poly)))
144
+ output_coeffs = []
145
+
146
+ for _ in np.arange(len(poly)):
147
+ quot = np.zeros(shape=working_coeffs.shape)
148
+ rem = 0.0
149
+ for ndx, val in enumerate(working_coeffs):
150
+ carry = rem * new_origin
151
+ rem = val + carry
152
+ quot[ndx] = rem
153
+ output_coeffs.append(rem)
154
+ working_coeffs = quot[:-1]
155
+
156
+ return np.array(output_coeffs)
157
+
158
+
127
159
  def broadening_from_amp(amp_vals, threshold_db=None):
128
160
  """Compute the broadening factor from amplitudes
129
161
 
@@ -0,0 +1 @@
1
+ __version__ = '0.2.0'
@@ -17,8 +17,12 @@ metadata that would predict the complex data characteristics
17
17
 
18
18
  import argparse
19
19
  import contextlib
20
+ import datetime
20
21
  import pathlib
21
22
 
23
+ import astropy.coordinates as apcoord
24
+ import astropy.units as apu
25
+ import astropy.utils
22
26
  import dateutil.parser
23
27
  import h5py
24
28
  import lxml.builder
@@ -30,9 +34,13 @@ import sarkit.verification
30
34
  import sarkit.wgs84
31
35
  import scipy.constants
32
36
  import scipy.optimize
37
+ import scipy.spatial.transform
33
38
 
39
+ from sarkit_convert import __version__
34
40
  from sarkit_convert import _utils as utils
35
41
 
42
+ astropy.utils.iers.conf.autodownload = False
43
+
36
44
  NSMAP = {
37
45
  "sicd": "urn:SICD:1.4.0",
38
46
  }
@@ -130,7 +138,6 @@ def hdf5_to_sicd(
130
138
  h5_filename,
131
139
  sicd_filename,
132
140
  classification,
133
- ostaid,
134
141
  img_str,
135
142
  chan_index,
136
143
  tx_polarizations,
@@ -141,8 +148,10 @@ def hdf5_to_sicd(
141
148
  mission_id = h5_attrs["Mission ID"]
142
149
  if mission_id == "CSG":
143
150
  dataset_str = "IMG"
151
+ burst_str = "B0001"
144
152
  else:
145
153
  dataset_str = "SBI"
154
+ burst_str = "B001"
146
155
  sample_data_h5_path = f"{img_str}/{dataset_str}"
147
156
  sample_data_shape = h5file[sample_data_h5_path].shape
148
157
  sample_data_dtype = h5file[sample_data_h5_path].dtype
@@ -171,7 +180,6 @@ def hdf5_to_sicd(
171
180
 
172
181
  # Creation Info
173
182
  creation_time = dateutil.parser.parse(h5_attrs["Product Generation UTC"])
174
- creation_site = h5_attrs["Processing Centre"]
175
183
  l0_ver = h5_attrs.get("L0 Software Version", "NONE")
176
184
  l1_ver = h5_attrs.get("L1A Software Version", "NONE")
177
185
  creation_application = f"L0: {l0_ver}, L1: {l1_ver}"
@@ -311,31 +319,34 @@ def hdf5_to_sicd(
311
319
  range_rate_per_hz = -scipy.constants.speed_of_light / (2 * center_frequency)
312
320
  range_rate = doppler_centroid * range_rate_per_hz
313
321
  range_rate_rate = doppler_rate * range_rate_per_hz
314
- doppler_centroid_poly = utils.polyfit2d(
322
+ doppler_centroid_poly = utils.polyfit2d_tol(
315
323
  grid_coords[..., 0].flatten(),
316
324
  grid_coords[..., 1].flatten(),
317
325
  doppler_centroid.flatten(),
318
326
  4,
319
327
  4,
328
+ 1e-2,
320
329
  )
321
- doppler_rate_poly = utils.polyfit2d(
330
+ doppler_rate_poly = utils.polyfit2d_tol(
322
331
  grid_coords[..., 0].flatten(),
323
332
  grid_coords[..., 1].flatten(),
324
333
  doppler_rate.flatten(),
325
334
  4,
326
335
  4,
336
+ 1e-3,
327
337
  )
328
338
  time_ca_samps = time_coords[..., 1] - start_minus_ref
329
339
  time_ca_poly = npp.polyfit(
330
340
  grid_coords[..., 1].flatten(), time_ca_samps.flatten(), 1
331
341
  )
332
342
  time_coa_samps = time_ca_samps + range_rate / range_rate_rate
333
- time_coa_poly = utils.polyfit2d(
343
+ time_coa_poly = utils.polyfit2d_tol(
334
344
  grid_coords[..., 0].flatten(),
335
345
  grid_coords[..., 1].flatten(),
336
346
  time_coa_samps.flatten(),
337
347
  4,
338
348
  4,
349
+ 1e-3,
339
350
  )
340
351
 
341
352
  range_ca = time_coords[..., 0] * scipy.constants.speed_of_light / 2
@@ -344,12 +355,13 @@ def hdf5_to_sicd(
344
355
  axis=0,
345
356
  )
346
357
  drsf = range_rate_rate * range_ca / speed_ca**2
347
- drsf_poly = utils.polyfit2d(
358
+ drsf_poly = utils.polyfit2d_tol(
348
359
  grid_coords[..., 0].flatten(),
349
360
  grid_coords[..., 1].flatten(),
350
361
  drsf.flatten(),
351
362
  4,
352
363
  4,
364
+ 1e-6,
353
365
  )
354
366
 
355
367
  llh_ddm = h5_attrs["Scene Centre Geodetic Coordinates"]
@@ -398,23 +410,176 @@ def hdf5_to_sicd(
398
410
  uspz = spz / npl.norm(spz)
399
411
  u_col = np.cross(uspz, u_row)
400
412
 
413
+ # Antenna
414
+ attitude_quaternion = np.roll(h5_attrs["Attitude Quaternions"], -1, axis=1)
415
+ attitude_times = h5_attrs["Attitude Times"]
416
+ attitude_utcs = [
417
+ ref_time + datetime.timedelta(seconds=attitude_time)
418
+ for attitude_time in attitude_times
419
+ ]
420
+
421
+ inertial_position = h5_attrs["Inertial Satellite Position"]
422
+ inertial_velocity = h5_attrs["Inertial Satellite Velocity"]
423
+ inertial_acceleration = h5_attrs["Inertial Satellite Acceleration"]
424
+ eci_apc_poly = utils.fit_state_vectors(
425
+ (0, (collection_stop_time - collection_start_time).total_seconds()),
426
+ h5_attrs["State Vectors Times"]
427
+ - (collection_start_time - ref_time).total_seconds(),
428
+ inertial_position,
429
+ inertial_velocity,
430
+ inertial_acceleration,
431
+ order=5,
432
+ )
433
+
434
+ def get_nadir_plane_at_time(time):
435
+ obstime = collection_start_time + datetime.timedelta(seconds=time)
436
+ eci_pos = npp.polyval(time, eci_apc_poly)
437
+ eci_vel = npp.polyval(time, npp.polyder(eci_apc_poly))
438
+
439
+ # Derived from https://adsabs.harvard.edu/full/2006ESASP.606E..35C, Section 3
440
+ z_sc = -eci_pos / np.linalg.norm(eci_pos)
441
+ y_sc_dir = np.cross(z_sc, eci_vel)
442
+ y_sc = y_sc_dir / np.linalg.norm(y_sc_dir)
443
+ x_sc = np.cross(y_sc, z_sc)
444
+ eci_2_ecf = np.array(
445
+ [
446
+ apcoord.GCRS(
447
+ apcoord.CartesianRepresentation(1, 0, 0, unit=apu.m),
448
+ obstime=obstime,
449
+ )
450
+ .transform_to(apcoord.ITRS(obstime=obstime))
451
+ .data.xyz.value,
452
+ apcoord.GCRS(
453
+ apcoord.CartesianRepresentation(0, 1, 0, unit=apu.m),
454
+ obstime=obstime,
455
+ )
456
+ .transform_to(apcoord.ITRS(obstime=obstime))
457
+ .data.xyz.value,
458
+ apcoord.GCRS(
459
+ apcoord.CartesianRepresentation(0, 0, 1, unit=apu.m),
460
+ obstime=obstime,
461
+ )
462
+ .transform_to(apcoord.ITRS(obstime=obstime))
463
+ .data.xyz.value,
464
+ ]
465
+ )
466
+ x_sc = x_sc @ eci_2_ecf
467
+ y_sc = y_sc @ eci_2_ecf
468
+ z_sc = z_sc @ eci_2_ecf
469
+ return np.array([x_sc, y_sc, z_sc])
470
+
471
+ rel_att_times = np.array(
472
+ [(att_utc - collection_start_time).total_seconds() for att_utc in attitude_utcs]
473
+ )
474
+ good_indices = np.where(
475
+ np.logical_and(
476
+ np.less(-60, rel_att_times),
477
+ np.less(rel_att_times, 60 + collection_duration),
478
+ )
479
+ )
480
+ good_times = rel_att_times[good_indices]
481
+ good_att_quat = attitude_quaternion[good_indices]
482
+ nadir_planes = [get_nadir_plane_at_time(time) for time in good_times]
483
+ body_frame = np.array(
484
+ [
485
+ scipy.spatial.transform.Rotation.from_quat(att_quat)
486
+ .inv()
487
+ .apply(nadir_plane.T)
488
+ .T
489
+ for att_quat, nadir_plane in zip(good_att_quat, nadir_planes)
490
+ ]
491
+ )
492
+
493
+ ux_rot = body_frame[:, 0, :]
494
+ uy_rot = body_frame[:, 1, :]
495
+
496
+ ant_x_dir_poly = utils.fit_state_vectors(
497
+ (0, (collection_stop_time - collection_start_time).total_seconds()),
498
+ good_times,
499
+ ux_rot,
500
+ None,
501
+ None,
502
+ order=4,
503
+ )
504
+ ant_y_dir_poly = utils.fit_state_vectors(
505
+ (0, (collection_stop_time - collection_start_time).total_seconds()),
506
+ good_times,
507
+ uy_rot,
508
+ None,
509
+ None,
510
+ order=4,
511
+ )
512
+
513
+ freq_zero = h5_attrs["Radar Frequency"]
514
+ antenna_beam_elevation = h5_attrs[img_str]["Antenna Beam Elevation"]
515
+ fit_order = 4
516
+
517
+ def fit_steering(code_change_lines, dcs):
518
+ if len(code_change_lines) > 1:
519
+ times = code_change_lines / prf
520
+ return npp.polyfit(times, dcs, fit_order)
521
+ else:
522
+ return np.array(dcs).reshape((1,))
523
+
524
+ if radar_mode_type != "STRIPMAP":
525
+ azimuth_ramp_code_change_lines = h5_attrs[img_str][burst_str][
526
+ "Azimuth Ramp Code Change Lines"
527
+ ]
528
+ azimuth_steering = h5_attrs[img_str][burst_str]["Azimuth Steering"]
529
+ elevation_ramp_code_change_lines = h5_attrs[img_str][burst_str][
530
+ "Elevation Ramp Code Change Lines"
531
+ ]
532
+ elevation_steering = h5_attrs[img_str][burst_str]["Elevation Steering"]
533
+ eb_dcx = np.sin(np.deg2rad(azimuth_steering))
534
+ eb_dcx_poly = fit_steering(azimuth_ramp_code_change_lines, eb_dcx)
535
+ eb_dcy = -np.sin(np.deg2rad(antenna_beam_elevation + elevation_steering))
536
+ eb_dcy_poly = fit_steering(elevation_ramp_code_change_lines, eb_dcy)
537
+ else:
538
+ eb_dcx_poly = [0.0]
539
+ eb_dcy_poly = [-np.sin(np.deg2rad(antenna_beam_elevation))]
540
+
541
+ antenna_az_gains = h5_attrs[img_str]["Azimuth Antenna Pattern Gains"]
542
+ antenna_az_origin = h5_attrs[img_str]["Azimuth Antenna Pattern Origin"]
543
+ antenna_az_spacing = h5_attrs[img_str]["Azimuth Antenna Pattern Resolution"]
544
+
545
+ antenna_rg_gains = h5_attrs[img_str]["Range Antenna Pattern Gains"]
546
+ antenna_rg_origin = h5_attrs[img_str]["Range Antenna Pattern Origin"]
547
+ antenna_rg_spacing = h5_attrs[img_str]["Range Antenna Pattern Resolution"]
548
+
549
+ def fit_gains(origin, spacing, gains):
550
+ fit_limit = -9
551
+ array_mask = gains > fit_limit
552
+ dcs = np.sin(np.deg2rad(origin + spacing * np.arange(len(gains))))
553
+ return npp.polyfit(dcs[array_mask], gains[array_mask], fit_order)
554
+
555
+ antenna_array_gain = np.zeros((fit_order + 1, fit_order + 1), dtype=float)
556
+ antenna_array_gain[0, :] = fit_gains(
557
+ antenna_rg_origin, antenna_rg_spacing, antenna_rg_gains
558
+ )
559
+ antenna_array_gain[:, 0] = fit_gains(
560
+ antenna_az_origin, antenna_az_spacing, antenna_az_gains
561
+ )
562
+ antenna_array_gain[0, 0] = 0.0
563
+
401
564
  # Build XML
402
565
  sicd = lxml.builder.ElementMaker(
403
566
  namespace=NSMAP["sicd"], nsmap={None: NSMAP["sicd"]}
404
567
  )
405
- collection_info = sicd.CollectionInfo(
568
+ sicd_xml_obj = sicd.SICD()
569
+ sicd_ew = sksicd.ElementWrapper(sicd_xml_obj)
570
+
571
+ sicd_ew["CollectionInfo"] = sicd.CollectionInfo(
406
572
  sicd.CollectorName(collector_name),
407
573
  sicd.CoreName(core_name),
408
574
  sicd.CollectType("MONOSTATIC"),
409
575
  sicd.RadarMode(sicd.ModeType(radar_mode_type), sicd.ModeID(radar_mode_id)),
410
576
  sicd.Classification(classification),
411
577
  )
412
- image_creation = sicd.ImageCreation(
578
+ sicd_ew["ImageCreation"] = sicd.ImageCreation(
413
579
  sicd.Application(creation_application),
414
580
  sicd.DateTime(creation_time.isoformat() + "Z"),
415
- sicd.Site(creation_site),
416
581
  )
417
- image_data = sicd.ImageData(
582
+ sicd_ew["ImageData"] = sicd.ImageData(
418
583
  sicd.PixelType(pixel_type),
419
584
  sicd.NumRows(str(num_rows)),
420
585
  sicd.NumCols(str(num_cols)),
@@ -434,7 +599,7 @@ def hdf5_to_sicd(
434
599
  return [sicd.Lat(str(arr[0])), sicd.Lon(str(arr[1]))]
435
600
 
436
601
  # Placeholder locations
437
- geo_data = sicd.GeoData(
602
+ sicd_ew["GeoData"] = sicd.GeoData(
438
603
  sicd.EarthModel("WGS_84"),
439
604
  sicd.SCP(sicd.ECF(*make_xyz(scp_ecf)), sicd.LLH(*make_llh(scp_llh))),
440
605
  sicd.ImageCorners(
@@ -468,7 +633,7 @@ def hdf5_to_sicd(
468
633
  col_window_name = h5_attrs["Azimuth Focusing Weighting Function"]
469
634
  col_window_coeff = h5_attrs["Azimuth Focusing Weighting Coefficient"]
470
635
 
471
- grid = sicd.Grid(
636
+ sicd_ew["Grid"] = sicd.Grid(
472
637
  sicd.ImagePlane("SLANT"),
473
638
  sicd.Type("RGZERO"),
474
639
  sicd.TimeCOAPoly(),
@@ -503,33 +668,27 @@ def hdf5_to_sicd(
503
668
  ),
504
669
  ),
505
670
  )
506
- sksicd.Poly2dType().set_elem(grid.find("./{*}TimeCOAPoly"), time_coa_poly)
507
- sksicd.Poly2dType().set_elem(grid.find("./{*}Row/{*}DeltaKCOAPoly"), [[0]])
508
- sksicd.Poly2dType().set_elem(
509
- grid.find("./{*}Col/{*}DeltaKCOAPoly"), col_deltakcoa_poly
510
- )
671
+ sicd_ew["Grid"]["TimeCOAPoly"] = time_coa_poly
672
+ sicd_ew["Grid"]["Row"]["DeltaKCOAPoly"] = [[0]]
673
+ sicd_ew["Grid"]["Col"]["DeltaKCOAPoly"] = col_deltakcoa_poly
511
674
  rcs_row_sf = None
512
675
  rcs_col_sf = None
513
676
  if row_window_name == "HAMMING":
514
677
  wgts = scipy.signal.windows.general_hamming(512, row_window_coeff, sym=True)
515
- wgtfunc = sicd.WgtFunct()
516
- sksicd.TRANSCODERS["Grid/Row/WgtFunct"].set_elem(wgtfunc, wgts)
517
- grid.find("./{*}Row").append(wgtfunc)
678
+ sicd_ew["Grid"]["Row"]["WgtFunct"] = wgts
518
679
  row_broadening_factor = utils.broadening_from_amp(wgts)
519
680
  row_wid = row_broadening_factor / row_bw
520
- sksicd.DblType().set_elem(grid.find("./{*}Row/{*}ImpRespWid"), row_wid)
681
+ sicd_ew["Grid"]["Row"]["ImpRespWid"] = row_wid
521
682
  rcs_row_sf = 1 + np.var(wgts) / np.mean(wgts) ** 2
522
683
  if col_window_name == "HAMMING":
523
684
  wgts = scipy.signal.windows.general_hamming(512, col_window_coeff, sym=True)
524
- wgtfunc = sicd.WgtFunct()
525
- sksicd.TRANSCODERS["Grid/Col/WgtFunct"].set_elem(wgtfunc, wgts)
526
- grid.find("./{*}Col").append(wgtfunc)
685
+ sicd_ew["Grid"]["Col"]["WgtFunct"] = wgts
527
686
  col_broadening_factor = utils.broadening_from_amp(wgts)
528
687
  col_wid = col_broadening_factor / col_bw
529
- sksicd.DblType().set_elem(grid.find("./{*}Col/{*}ImpRespWid"), col_wid)
688
+ sicd_ew["Grid"]["Col"]["ImpRespWid"] = col_wid
530
689
  rcs_col_sf = 1 + np.var(wgts) / np.mean(wgts) ** 2
531
690
 
532
- timeline = sicd.Timeline(
691
+ sicd_ew["Timeline"] = sicd.Timeline(
533
692
  sicd.CollectStart(collection_start_time.isoformat() + "Z"),
534
693
  sicd.CollectDuration(str(collection_duration)),
535
694
  sicd.IPP(
@@ -544,10 +703,9 @@ def hdf5_to_sicd(
544
703
  ),
545
704
  ),
546
705
  )
547
- sksicd.PolyType().set_elem(timeline.find("./{*}IPP/{*}Set/{*}IPPPoly"), [0, prf])
706
+ sicd_ew["Timeline"]["IPP"]["Set"][0]["IPPPoly"] = [0, prf]
548
707
 
549
- position = sicd.Position(sicd.ARPPoly())
550
- sksicd.XyzPolyType().set_elem(position.find("./{*}ARPPoly"), apc_poly)
708
+ sicd_ew["Position"]["ARPPoly"] = apc_poly
551
709
 
552
710
  rcv_channels = sicd.RcvChannels(
553
711
  {"size": str(len(tx_rcv_pols))},
@@ -559,7 +717,7 @@ def hdf5_to_sicd(
559
717
  )
560
718
  )
561
719
 
562
- radar_collection = sicd.RadarCollection(
720
+ sicd_ew["RadarCollection"] = sicd.RadarCollection(
563
721
  sicd.TxFrequency(sicd.Min(str(tx_freq_min)), sicd.Max(str(tx_freq_max))),
564
722
  sicd.Waveform(
565
723
  {"size": "1"},
@@ -577,7 +735,7 @@ def hdf5_to_sicd(
577
735
  rcv_channels,
578
736
  )
579
737
  if len(tx_polarizations) > 1:
580
- radar_collection.find("./{*}TxPolarization").text = "SEQUENCE"
738
+ sicd_ew["RadarCollection"]["TxPolarization"] = "SEQUENCE"
581
739
  tx_sequence = sicd.TxSequence({"size": str(len(tx_polarizations))})
582
740
  for ndx, tx_pol in enumerate(tx_polarizations):
583
741
  tx_sequence.append(
@@ -585,7 +743,12 @@ def hdf5_to_sicd(
585
743
  )
586
744
  rcv_channels.addprevious(tx_sequence)
587
745
 
588
- image_formation = sicd.ImageFormation(
746
+ now = (
747
+ datetime.datetime.now(datetime.timezone.utc)
748
+ .isoformat(timespec="microseconds")
749
+ .replace("+00:00", "Z")
750
+ )
751
+ sicd_ew["ImageFormation"] = sicd.ImageFormation(
589
752
  sicd.RcvChanProc(sicd.NumChanProc("1"), sicd.ChanIndex(str(chan_index))),
590
753
  sicd.TxRcvPolarizationProc(tx_rcv_polarization),
591
754
  sicd.TStartProc(str(0)),
@@ -598,9 +761,23 @@ def hdf5_to_sicd(
598
761
  sicd.ImageBeamComp("SV"),
599
762
  sicd.AzAutofocus("NO"),
600
763
  sicd.RgAutofocus("NO"),
764
+ sicd.Processing(
765
+ sicd.Type(f"sarkit-convert {__version__} @ {now}"),
766
+ sicd.Applied("true"),
767
+ ),
768
+ )
769
+
770
+ sicd_ew["Antenna"]["TwoWay"]["XAxisPoly"] = ant_x_dir_poly
771
+ sicd_ew["Antenna"]["TwoWay"]["YAxisPoly"] = ant_y_dir_poly
772
+ sicd_ew["Antenna"]["TwoWay"]["FreqZero"] = freq_zero
773
+ sicd_ew["Antenna"]["TwoWay"]["EB"]["DCXPoly"] = eb_dcx_poly
774
+ sicd_ew["Antenna"]["TwoWay"]["EB"]["DCYPoly"] = eb_dcy_poly
775
+ sicd_ew["Antenna"]["TwoWay"]["Array"]["GainPoly"] = antenna_array_gain
776
+ sicd_ew["Antenna"]["TwoWay"]["Array"]["PhasePoly"] = np.zeros(
777
+ dtype=float, shape=(1, 1)
601
778
  )
602
779
 
603
- rma = sicd.RMA(
780
+ sicd_ew["RMA"] = sicd.RMA(
604
781
  sicd.RMAlgoType("OMEGA_K"),
605
782
  sicd.ImageType("INCA"),
606
783
  sicd.INCA(
@@ -611,25 +788,11 @@ def hdf5_to_sicd(
611
788
  sicd.DopCentroidPoly(),
612
789
  ),
613
790
  )
614
- sksicd.PolyType().set_elem(rma.find("./{*}INCA/{*}TimeCAPoly"), time_ca_poly)
615
- sksicd.Poly2dType().set_elem(rma.find("./{*}INCA/{*}DRateSFPoly"), drsf_poly)
616
- sksicd.Poly2dType().set_elem(
617
- rma.find("./{*}INCA/{*}DopCentroidPoly"), doppler_centroid_poly
618
- )
619
- sicd_xml_obj = sicd.SICD(
620
- collection_info,
621
- image_creation,
622
- image_data,
623
- geo_data,
624
- grid,
625
- timeline,
626
- position,
627
- radar_collection,
628
- image_formation,
629
- rma,
630
- )
791
+ sicd_ew["RMA"]["INCA"]["TimeCAPoly"] = time_ca_poly
792
+ sicd_ew["RMA"]["INCA"]["DRateSFPoly"] = drsf_poly
793
+ sicd_ew["RMA"]["INCA"]["DopCentroidPoly"] = doppler_centroid_poly
631
794
 
632
- image_formation.addnext(sksicd.compute_scp_coa(sicd_xml_obj.getroottree()))
795
+ sicd_ew["SCPCOA"] = sksicd.compute_scp_coa(sicd_xml_obj.getroottree())
633
796
 
634
797
  # Add Radiometric
635
798
  if mission_id == "CSK":
@@ -668,7 +831,7 @@ def hdf5_to_sicd(
668
831
  sksicd.Poly2dType().set_elem(
669
832
  radiometric.find("./{*}RCSSFPoly"), rcssf_poly
670
833
  )
671
- sicd_xml_obj.find("./{*}RMA").addprevious(radiometric)
834
+ sicd_xml_obj.find("./{*}Antenna").addprevious(radiometric)
672
835
 
673
836
  # Add Geodata Corners
674
837
  sicd_xmltree = sicd_xml_obj.getroottree()
@@ -708,7 +871,7 @@ def hdf5_to_sicd(
708
871
  metadata = sksicd.NitfMetadata(
709
872
  xmltree=sicd_xmltree,
710
873
  file_header_part={
711
- "ostaid": ostaid,
874
+ "ostaid": h5_attrs["Processing Centre"],
712
875
  "ftitle": core_name,
713
876
  "security": {
714
877
  "clas": classification[0].upper(),
@@ -756,11 +919,6 @@ def main(args=None):
756
919
  type=pathlib.Path,
757
920
  help='path of the output SICD file. The string "{pol}" will be replaced with polarization for multiple images',
758
921
  )
759
- parser.add_argument(
760
- "--ostaid",
761
- help="content of the originating station ID (OSTAID) field of the NITF header",
762
- default="Unknown",
763
- )
764
922
  config = parser.parse_args(args)
765
923
 
766
924
  tx_polarizations = []
@@ -810,7 +968,6 @@ def main(args=None):
810
968
  h5_filename=config.input_h5_file,
811
969
  sicd_filename=img_info["filename"],
812
970
  classification=config.classification,
813
- ostaid=config.ostaid,
814
971
  img_str=img_str,
815
972
  chan_index=img_info["chan_index"],
816
973
  tx_polarizations=tx_polarizations,