sarkit-convert 0.1.0__py3-none-any.whl → 0.2.0__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.
@@ -8,6 +8,7 @@ Convert a complex image(s) from the Sentinel SAFE to SICD(s).
8
8
  """
9
9
 
10
10
  import argparse
11
+ import datetime
11
12
  import pathlib
12
13
 
13
14
  import dateutil.parser
@@ -138,6 +139,20 @@ def _collect_base_info(root_node):
138
139
  # TOPSAR - closest SICD analog is Dynamic Stripmap
139
140
  base_info["mode_type"] = "DYNAMIC STRIPMAP"
140
141
 
142
+ relative_orbit_num = int(
143
+ root_node.findtext(".//{*}relativeOrbitNumber[@type='start']")
144
+ )
145
+ base_info["parameters"] = {
146
+ "RELATIVE_ORBIT_NUM": relative_orbit_num,
147
+ }
148
+
149
+ base_info["start_time_anx"] = float(
150
+ root_node.findtext(".//{*}acquisitionPeriod//{*}startTimeANX")
151
+ )
152
+ base_info["acq_start_str"] = root_node.findtext(
153
+ ".//{*}acquisitionPeriod//{*}startTime"
154
+ )
155
+
141
156
  # Image Creation
142
157
  processing = root_node.find(
143
158
  "./metadataSection/metadataObject[@ID='processing']/metadataWrap/xmlData/{*}processing",
@@ -148,9 +163,6 @@ def _collect_base_info(root_node):
148
163
  f"{software.attrib['name']} {software.attrib['version']}"
149
164
  )
150
165
  base_info["creation_date_time"] = dateutil.parser.parse(processing.attrib["stop"])
151
- base_info["creation_site"] = (
152
- f"{facility.attrib['name']}, {facility.attrib['site']}, {facility.attrib['country']}"
153
- )
154
166
 
155
167
  # Radar Collection
156
168
  polarizations = root_node.findall(
@@ -164,7 +176,7 @@ def _collect_base_info(root_node):
164
176
  return base_info
165
177
 
166
178
 
167
- def _collect_swath_info(product_root_node):
179
+ def _collect_swath_info(product_root_node, base_info):
168
180
  swath_info = dict()
169
181
  burst_list = product_root_node.findall("./swathTiming/burstList/burst")
170
182
 
@@ -173,12 +185,25 @@ def _collect_swath_info(product_root_node):
173
185
  swath_info["mode_id"] = product_root_node.find("./adsHeader/mode").text
174
186
  t_slice = _get_slice(product_root_node)
175
187
  swath = _get_swath(product_root_node)
188
+
189
+ mission_data_take_id = product_root_node.find("./adsHeader/missionDataTakeId").text
176
190
  swath_info["parameters"] = {
177
191
  "SLICE": t_slice,
178
192
  "SWATH": swath,
179
193
  "ORBIT_SOURCE": "SLC_INTERNAL",
194
+ "MISSION_DATA_TAKE_ID": mission_data_take_id,
180
195
  }
181
196
 
197
+ slice_list = product_root_node.findall(
198
+ "./imageAnnotation/imageInformation/sliceList/slice"
199
+ )
200
+ swath_info["sensing_start"] = dateutil.parser.parse(
201
+ slice_list[int(t_slice) - 1].find("./sensingStartTime").text
202
+ )
203
+ swath_info["sensing_stop"] = dateutil.parser.parse(
204
+ slice_list[int(t_slice) - 1].find("./sensingStopTime").text
205
+ )
206
+
182
207
  # Radar Collection
183
208
  center_frequency = float(
184
209
  product_root_node.find(
@@ -226,8 +251,6 @@ def _collect_swath_info(product_root_node):
226
251
  "./generalAnnotation/downlinkInformationList/downlinkInformation/prf"
227
252
  ).text
228
253
  )
229
- swath_info["t_start"] = 0
230
- swath_info["ipp_start"] = 0
231
254
  swath_info["ipp_poly"] = (0, swath_info["prf"])
232
255
 
233
256
  # Image Formation
@@ -235,7 +258,6 @@ def _collect_swath_info(product_root_node):
235
258
  f"{swath_info['tx_polarization']}:{swath_info['rcv_polarization']}"
236
259
  )
237
260
  swath_info["image_form_algo"] = "RMA"
238
- swath_info["t_start_proc"] = 0
239
261
  swath_info["tx_freq_proc"] = swath_info["tx_freq"]
240
262
  swath_info["image_beam_comp"] = "SV"
241
263
  swath_info["az_autofocus"] = "NO"
@@ -379,7 +401,7 @@ def _collect_swath_info(product_root_node):
379
401
  return swath_info
380
402
 
381
403
 
382
- def _collect_burst_info(product_root_node, swath_info):
404
+ def _collect_burst_info(product_root_node, base_info, swath_info):
383
405
  burst_list = product_root_node.findall("./swathTiming/burstList/burst")
384
406
  # parse the geolocation information - for SCP calculation
385
407
  geo_grid_point_list = product_root_node.findall(
@@ -549,7 +571,7 @@ def _collect_burst_info(product_root_node, swath_info):
549
571
  )
550
572
 
551
573
  # common use for the fitting efforts
552
- poly_order = 2
574
+ poly_order = 4
553
575
  grid_samples = poly_order + 4
554
576
  num_rows = swath_info["num_rows"]
555
577
  num_cols = swath_info["num_cols"]
@@ -584,12 +606,13 @@ def _collect_burst_info(product_root_node, swath_info):
584
606
  demod_phase = eta_arg * f_eta_c[:, np.newaxis]
585
607
  total_phase = deramp_phase + demod_phase
586
608
 
587
- phase = utils.polyfit2d(
609
+ phase = utils.polyfit2d_tol(
588
610
  coords_rg_2d.flatten(),
589
611
  coords_az_2d.flatten(),
590
612
  total_phase.flatten(),
591
613
  poly_order,
592
- poly_order,
614
+ poly_order + 1,
615
+ 1e-2,
593
616
  )
594
617
 
595
618
  # DeltaKCOAPoly is derivative of phase in azimuth/Col direction
@@ -608,12 +631,13 @@ def _collect_burst_info(product_root_node, swath_info):
608
631
  )
609
632
  time_coa_sampled = time_ca_sampled + dop_centroid_sampled / doppler_rate_sampled
610
633
 
611
- burst_info["time_coa_poly_coefs"] = utils.polyfit2d(
634
+ burst_info["time_coa_poly_coefs"] = utils.polyfit2d_tol(
612
635
  coords_rg_2d.flatten(),
613
636
  coords_az_2d.flatten(),
614
637
  time_coa_sampled.flatten(),
615
638
  poly_order,
616
639
  poly_order,
640
+ 1e-3,
617
641
  )
618
642
 
619
643
  full_img_verticies = np.array(
@@ -694,6 +718,28 @@ def _collect_burst_info(product_root_node, swath_info):
694
718
 
695
719
  return row_uvect_ecf, np.cross(uspz, row_uvect_ecf)
696
720
 
721
+ def _compute_burst_id(base_info, swath_info, burst_info):
722
+ """Add `burst_id` to each burst info in `swaths_info` based on DI-MPC-IPFDPM, Issue 2.5, Clause 9.25"""
723
+ t_orb = 12 * 24 * 3600 / 175
724
+ t_pre = {"IW": 2.299849, "EW": 2.299970}[swath_info["mode_id"]]
725
+ t_beam = {"IW": 2.758273, "EW": 3.038376}[swath_info["mode_id"]]
726
+
727
+ acq_start_utc = datetime.datetime.fromisoformat(
728
+ base_info["acq_start_str"]
729
+ ).replace(tzinfo=datetime.timezone.utc)
730
+ t_anx = acq_start_utc - datetime.timedelta(
731
+ milliseconds=base_info["start_time_anx"]
732
+ )
733
+
734
+ scp_tcoa = burst_info["time_coa_poly_coefs"][0, 0]
735
+ t_b = (
736
+ burst_info["collect_start"] + datetime.timedelta(seconds=scp_tcoa)
737
+ ).replace(tzinfo=datetime.timezone.utc)
738
+ delta_t_b = (t_b - t_anx).total_seconds() + (
739
+ base_info["parameters"]["RELATIVE_ORBIT_NUM"] - 1
740
+ ) * t_orb
741
+ return int(1 + (delta_t_b - t_pre) // t_beam)
742
+
697
743
  def _finalize_stripmap():
698
744
  burst_info = dict()
699
745
 
@@ -711,38 +757,34 @@ def _collect_burst_info(product_root_node, swath_info):
711
757
  (num_rows - 1, 0),
712
758
  ]
713
759
 
714
- start = dateutil.parser.parse(
715
- product_root_node.find(
716
- "./generalAnnotation/downlinkInformationList/downlinkInformation/firstLineSensingTime"
717
- ).text
718
- )
719
- stop = dateutil.parser.parse(
720
- product_root_node.find(
721
- "./generalAnnotation/downlinkInformationList/downlinkInformation/lastLineSensingTime"
722
- ).text
723
- )
724
- slice = int(_get_slice(product_root_node))
760
+ t_slice = int(_get_slice(product_root_node))
725
761
  swath = _get_swath(product_root_node)
726
762
  burst_info["core_name"] = (
727
- f"{start.strftime('%d%b%YT%H%M%S').upper()}_{product_root_node.find('./adsHeader/missionId').text}{product_root_node.find('./adsHeader/missionDataTakeId').text}_{slice:02d}_{swath}_01"
763
+ f"{swath_info['sensing_start'].strftime('%d%b%YT%H%M%S').upper()}_{product_root_node.find('./adsHeader/missionId').text}{product_root_node.find('./adsHeader/missionDataTakeId').text}_{t_slice:02d}_{swath}_01"
728
764
  )
729
765
 
730
- burst_info["parameters"] = {"BURST": f"{1:d}"}
766
+ burst_info["parameters"] = {"BURST": f"{1:02d}"}
767
+
731
768
  prf = float(
732
769
  product_root_node.find(
733
770
  "./generalAnnotation/downlinkInformationList/downlinkInformation/prf"
734
771
  ).text
735
772
  )
736
- duration = (stop - start).total_seconds()
773
+ duration = (
774
+ swath_info["sensing_stop"] - swath_info["sensing_start"]
775
+ ).total_seconds()
737
776
 
738
- burst_info["collect_start"] = start
777
+ burst_info["collect_start"] = swath_info["sensing_start"]
739
778
  burst_info["collect_duration"] = duration
779
+ burst_info["ipp_set_tstart"] = 0
740
780
  burst_info["ipp_set_tend"] = duration
781
+ burst_info["ipp_set_ippstart"] = 0
741
782
  burst_info["ipp_set_ippend"] = round(duration * prf) - 1
783
+ burst_info["tstart_proc"] = 0
742
784
  burst_info["tend_proc"] = duration
743
785
 
744
786
  burst_info["arp_poly_coefs"] = _compute_arp_poly_coefs(
745
- product_root_node, start
787
+ product_root_node, swath_info["sensing_start"]
746
788
  ).T
747
789
 
748
790
  azimuth_time_first_line = dateutil.parser.parse(
@@ -750,9 +792,14 @@ def _collect_burst_info(product_root_node, swath_info):
750
792
  "./imageAnnotation/imageInformation/productFirstLineUtcTime"
751
793
  ).text
752
794
  )
753
- first_line_relative_start = (azimuth_time_first_line - start).total_seconds()
795
+ first_line_relative_start = (
796
+ azimuth_time_first_line - swath_info["sensing_start"]
797
+ ).total_seconds()
754
798
  _, _ = _calc_rma_and_grid_info(
755
- swath_info, burst_info, first_line_relative_start, start
799
+ swath_info,
800
+ burst_info,
801
+ first_line_relative_start,
802
+ swath_info["sensing_start"],
756
803
  )
757
804
  burst_info["scp_ecf"], burst_info["scp_llh"] = _update_geo_data_info(
758
805
  swath_info, burst_info
@@ -767,6 +814,7 @@ def _collect_burst_info(product_root_node, swath_info):
767
814
  burst_info_list = []
768
815
 
769
816
  scps = _get_scps(swath_info, len(burst_list))
817
+ t_slice = int(_get_slice(product_root_node))
770
818
  for j, burst in enumerate(burst_list):
771
819
  # set preliminary geodata (required for projection)
772
820
  burst_info = dict()
@@ -791,42 +839,53 @@ def _collect_burst_info(product_root_node, swath_info):
791
839
  (last_row, first_col),
792
840
  ]
793
841
 
794
- # This is the first and last zero doppler times of the columns in the burst.
795
- # Not really CollectStart and CollectDuration in SICD (first last pulse time)
796
- start = dateutil.parser.parse(burst.find("./azimuthTime").text)
797
- t_slice = int(_get_slice(product_root_node))
798
842
  swath = _get_swath(product_root_node)
843
+ # Use burst index for burst_id to maintain SARPy compatibility
799
844
  burst_info["core_name"] = (
800
- f"{start.strftime('%d%b%YT%H%M%S').upper()}_{product_root_node.find('./adsHeader/missionId').text}{product_root_node.find('./adsHeader/missionDataTakeId').text}_{t_slice:02d}_{swath}_{j + 1:02d}"
845
+ f"{swath_info['sensing_start'].strftime('%d%b%YT%H%M%S').upper()}_{product_root_node.find('./adsHeader/missionId').text}{product_root_node.find('./adsHeader/missionDataTakeId').text}_{t_slice:02d}_{swath}_{j + 1:02d}"
846
+ )
847
+ arp_poly_coefs = _compute_arp_poly_coefs(
848
+ product_root_node, swath_info["sensing_start"]
801
849
  )
802
-
803
- burst_info["parameters"] = {"BURST": f"{j + 1:d}"}
804
- arp_poly_coefs = _compute_arp_poly_coefs(product_root_node, start)
805
850
  burst_info["arp_poly_coefs"] = arp_poly_coefs.T
806
- early, late = _calc_rma_and_grid_info(swath_info, burst_info, 0, start)
807
- new_start = start + np.timedelta64(np.int64(early * 1e6), "us")
808
- duration = late - early
851
+
852
+ az_time = dateutil.parser.parse(burst.find("./azimuthTime").text)
853
+ first_line_relative_start = (
854
+ az_time - swath_info["sensing_start"]
855
+ ).total_seconds()
856
+ early, late = _calc_rma_and_grid_info(
857
+ swath_info,
858
+ burst_info,
859
+ first_line_relative_start,
860
+ swath_info["sensing_start"],
861
+ )
809
862
  prf = float(
810
863
  product_root_node.find(
811
864
  "./generalAnnotation/downlinkInformationList/downlinkInformation/prf"
812
865
  ).text
813
866
  )
814
- burst_info["collect_start"] = new_start
815
- burst_info["collect_duration"] = duration
816
- burst_info["ipp_set_tend"] = duration
817
- burst_info["ipp_set_ippend"] = round(duration * prf) - 1
818
- burst_info["tend_proc"] = duration
819
-
820
- # adjust time offset
821
- burst_info["time_coa_poly_coefs"][0, 0] -= early
822
- burst_info["time_ca_poly_coefs"][0] -= early
823
-
824
- arp_poly_coefs = np.array(
825
- [
826
- _shift(arp_poly_coefs[i], t_0=-early)
827
- for i in range(len(arp_poly_coefs))
828
- ]
829
- )
867
+ burst_info["collect_start"] = swath_info["sensing_start"]
868
+ burst_info["collect_duration"] = (
869
+ swath_info["sensing_stop"] - swath_info["sensing_start"]
870
+ ).total_seconds()
871
+ burst_info["ipp_set_tstart"] = early
872
+ burst_info["ipp_set_tend"] = late
873
+ burst_info["ipp_set_ippstart"] = round(early * prf)
874
+ burst_info["ipp_set_ippend"] = round(late * prf) - 1
875
+ burst_info["tstart_proc"] = early
876
+ burst_info["tend_proc"] = late
877
+
878
+ if burst.find("./burstId") is not None:
879
+ burst_id = burst.findtext("./burstId")
880
+ else:
881
+ burst_id = _compute_burst_id(base_info, swath_info, burst_info)
882
+
883
+ # Keep BURST as the index to maintain SARPy compatibility
884
+ burst_info["parameters"] = {
885
+ "BURST": f"{j + 1:02d}",
886
+ "BURST_ID": burst_id,
887
+ }
888
+
830
889
  burst_info["arp_poly_coefs"] = arp_poly_coefs.T
831
890
 
832
891
  burst_info["scp_ecf"], burst_info["scp_llh"] = _update_geo_data_info(
@@ -884,14 +943,15 @@ def _calc_radiometric_info(cal_file_name, swath_info, burst_info_list):
884
943
  gamma = 1.0 / (gamma * gamma)
885
944
 
886
945
  for idx, burst_info in enumerate(burst_info_list):
887
- valid_lines = (line >= idx * lines_per_burst) & (
888
- line < (idx + 1) * lines_per_burst
889
- )
946
+ burst_first_line = idx * lines_per_burst
947
+ burst_last_line = burst_first_line + lines_per_burst
948
+ valid_lines = (line >= burst_first_line) & (line < burst_last_line)
890
949
  valid_count = np.sum(valid_lines)
891
950
  if valid_count == 0:
892
951
  # this burst contained no useful calibration data
893
952
  return
894
953
 
954
+ max_poly_order = 4
895
955
  first_row = swath_info["first_row"]
896
956
  first_col = swath_info["first_col"]
897
957
  scp_row = swath_info["scp_pixel"][0]
@@ -899,32 +959,45 @@ def _calc_radiometric_info(cal_file_name, swath_info, burst_info_list):
899
959
  row_ss = swath_info["row_ss"]
900
960
  col_ss = swath_info["col_ss"]
901
961
  coords_rg = (pixel[valid_lines] + first_row - scp_row) * row_ss
902
- coords_az = (line[valid_lines] + first_col - scp_col) * col_ss
962
+ coords_az = (
963
+ line[valid_lines] + first_col - scp_col - burst_first_line
964
+ ) * col_ss
903
965
  # NB: coords_rg = (valid_count, M) and coords_az = (valid_count, )
904
966
  coords_az = np.repeat(coords_az, pixel.shape[1])
905
967
  if valid_count > 1:
906
968
  coords_az = coords_az.reshape((valid_count, -1))
969
+ max_poly_order_rg = min(max_poly_order, len(coords_rg) - 1)
970
+ max_poly_order_az = min(max_poly_order, len(coords_az) - 1)
907
971
 
908
- burst_info["radiometric"]["sigma_zero_poly_coefs"] = utils.polyfit2d(
972
+ sigma_vals = sigma[valid_lines, :].flatten()
973
+ sigma_tol = np.maximum(np.std(sigma_vals) * 1e-1, np.mean(sigma_vals) * 1e-2)
974
+ burst_info["radiometric"]["sigma_zero_poly_coefs"] = utils.polyfit2d_tol(
909
975
  coords_rg.flatten(),
910
976
  coords_az.flatten(),
911
- sigma[valid_lines, :].flatten(),
912
- 2,
913
- 2,
977
+ sigma_vals,
978
+ max_poly_order_rg,
979
+ max_poly_order_az,
980
+ sigma_tol,
914
981
  )
915
- burst_info["radiometric"]["beta_zero_poly_coefs"] = utils.polyfit2d(
982
+ beta_vals = beta[valid_lines, :].flatten()
983
+ beta_tol = np.maximum(np.std(beta_vals) * 1e-1, np.mean(beta_vals) * 1e-2)
984
+ burst_info["radiometric"]["beta_zero_poly_coefs"] = utils.polyfit2d_tol(
916
985
  coords_rg.flatten(),
917
986
  coords_az.flatten(),
918
- beta[valid_lines, :].flatten(),
919
- 2,
920
- 2,
987
+ beta_vals,
988
+ max_poly_order_rg,
989
+ max_poly_order_az,
990
+ beta_tol,
921
991
  )
922
- burst_info["radiometric"]["gamma_zero_poly_coefs"] = utils.polyfit2d(
992
+ gamma_vals = gamma[valid_lines, :].flatten()
993
+ gamma_tol = np.maximum(np.std(gamma_vals) * 1e-1, np.mean(gamma_vals) * 1e-2)
994
+ burst_info["radiometric"]["gamma_zero_poly_coefs"] = utils.polyfit2d_tol(
923
995
  coords_rg.flatten(),
924
996
  coords_az.flatten(),
925
- gamma[valid_lines, :].flatten(),
926
- 2,
927
- 2,
997
+ gamma_vals,
998
+ max_poly_order_rg,
999
+ max_poly_order_az,
1000
+ gamma_tol,
928
1001
  )
929
1002
 
930
1003
  range_weight_f = azimuth_weight_f = 1.0
@@ -1017,7 +1090,7 @@ def _calc_noise_level_info(noise_file_name, swath_info, burst_info_list):
1017
1090
  else:
1018
1091
  azimuth_line, azimuth_noise = None, None
1019
1092
 
1020
- rg_poly_order = min(5, range_pixel[0].size - 1)
1093
+ rg_poly_order = min(4, range_pixel[0].size - 1)
1021
1094
  first_row = swath_info["first_row"]
1022
1095
  first_col = swath_info["first_col"]
1023
1096
  scp_row = swath_info["scp_pixel"][0]
@@ -1033,12 +1106,13 @@ def _calc_noise_level_info(noise_file_name, swath_info, burst_info_list):
1033
1106
 
1034
1107
  coords_az_2d, coords_rg_2d = np.meshgrid(coords_az, coords_rg)
1035
1108
 
1036
- noise_poly = utils.polyfit2d(
1109
+ noise_poly = utils.polyfit2d_tol(
1037
1110
  coords_rg_2d.flatten(),
1038
1111
  coords_az_2d.flatten(),
1039
1112
  np.array(range_noise).flatten(),
1040
1113
  rg_poly_order,
1041
1114
  az_poly_order,
1115
+ 1e-2,
1042
1116
  )
1043
1117
  else:
1044
1118
  # TOPSAR has single LUT per burst
@@ -1083,9 +1157,8 @@ def _calc_noise_level_info(noise_file_name, swath_info, burst_info_list):
1083
1157
 
1084
1158
 
1085
1159
  def _complete_filename(swath_info, burst_info, filename_template):
1086
- core_name = burst_info["core_name"]
1087
- burst = core_name[-2:]
1088
1160
  swath = swath_info["parameters"]["SWATH"]
1161
+ burst = burst_info["parameters"]["BURST"]
1089
1162
  polarization = swath_info["tx_rcv_polarization_proc"].replace(":", "")
1090
1163
  formatted_name = filename_template.name.format(
1091
1164
  swath=swath, burst=burst, pol=polarization
@@ -1098,8 +1171,11 @@ def _complete_filename(swath_info, burst_info, filename_template):
1098
1171
  def _create_sicd_xml(base_info, swath_info, burst_info, classification):
1099
1172
  em = lxml.builder.ElementMaker(namespace=NSMAP["sicd"], nsmap={None: NSMAP["sicd"]})
1100
1173
 
1174
+ sicd_xml_obj = em.SICD()
1175
+ sicd_ew = sksicd.ElementWrapper(sicd_xml_obj)
1176
+
1101
1177
  # Collection Info
1102
- collection_info_node = em.CollectionInfo(
1178
+ sicd_ew["CollectionInfo"] = em.CollectionInfo(
1103
1179
  em.CollectorName(swath_info["collector_name"]),
1104
1180
  em.CoreName(burst_info["core_name"]),
1105
1181
  em.CollectType(base_info["collect_type"]),
@@ -1114,18 +1190,33 @@ def _create_sicd_xml(base_info, swath_info, burst_info, classification):
1114
1190
  em.Parameter(
1115
1191
  {"name": "ORBIT_SOURCE"}, swath_info["parameters"]["ORBIT_SOURCE"]
1116
1192
  ),
1193
+ *(
1194
+ [
1195
+ em.Parameter(
1196
+ {"name": "BURST_ID"}, str(burst_info["parameters"]["BURST_ID"])
1197
+ )
1198
+ ]
1199
+ if burst_info["parameters"].get("BURST_ID") is not None
1200
+ else []
1201
+ ),
1202
+ em.Parameter(
1203
+ {"name": "MISSION_DATA_TAKE_ID"},
1204
+ swath_info["parameters"]["MISSION_DATA_TAKE_ID"],
1205
+ ),
1206
+ em.Parameter(
1207
+ {"name": "RELATIVE_ORBIT_NUM"},
1208
+ str(base_info["parameters"]["RELATIVE_ORBIT_NUM"]),
1209
+ ),
1117
1210
  )
1118
1211
 
1119
1212
  # Image Creation
1120
- image_creation_node = em.ImageCreation(
1213
+ sicd_ew["ImageCreation"] = em.ImageCreation(
1121
1214
  em.Application(base_info["creation_application"]),
1122
1215
  em.DateTime(base_info["creation_date_time"].strftime("%Y-%m-%dT%H:%M:%SZ")),
1123
- em.Site(base_info["creation_site"]),
1124
- em.Profile(f"sarkit-convert {__version__}"),
1125
1216
  )
1126
1217
 
1127
1218
  # Image Data
1128
- image_data_node = em.ImageData(
1219
+ sicd_ew["ImageData"] = em.ImageData(
1129
1220
  em.PixelType(swath_info["pixel_type"]),
1130
1221
  em.NumRows(str(swath_info["num_rows"])),
1131
1222
  em.NumCols(str(swath_info["num_cols"])),
@@ -1171,7 +1262,7 @@ def _create_sicd_xml(base_info, swath_info, burst_info, classification):
1171
1262
  return [em.Lat(str(arr[0])), em.Lon(str(arr[1])), em.HAE(str(arr[2]))]
1172
1263
 
1173
1264
  # Geo Data
1174
- geo_data_node = em.GeoData(
1265
+ sicd_ew["GeoData"] = em.GeoData(
1175
1266
  em.EarthModel("WGS_84"),
1176
1267
  em.SCP(
1177
1268
  em.ECF(*_make_xyz(burst_info["scp_ecf"])),
@@ -1182,7 +1273,7 @@ def _create_sicd_xml(base_info, swath_info, burst_info, classification):
1182
1273
  )
1183
1274
 
1184
1275
  # Grid
1185
- grid_node = em.Grid(
1276
+ sicd_ew["Grid"] = em.Grid(
1186
1277
  em.ImagePlane(swath_info["image_plane"]),
1187
1278
  em.Type(swath_info["grid_type"]),
1188
1279
  em.TimeCOAPoly(),
@@ -1239,50 +1330,35 @@ def _create_sicd_xml(base_info, swath_info, burst_info, classification):
1239
1330
  ),
1240
1331
  ),
1241
1332
  )
1242
- sksicd.Poly2dType().set_elem(
1243
- grid_node.find("./{*}TimeCOAPoly"), burst_info["time_coa_poly_coefs"]
1244
- )
1245
- sksicd.Poly2dType().set_elem(
1246
- grid_node.find("./{*}Row/{*}DeltaKCOAPoly"), swath_info["row_deltak_coa_poly"]
1247
- )
1248
- sksicd.Poly2dType().set_elem(
1249
- grid_node.find("./{*}Col/{*}DeltaKCOAPoly"), burst_info["col_deltak_coa_poly"]
1250
- )
1251
- wgtfunc = em.WgtFunct()
1252
- sksicd.TRANSCODERS["Grid/Row/WgtFunct"].set_elem(wgtfunc, swath_info["row_wgts"])
1253
- grid_node.find("./{*}Row").append(wgtfunc)
1254
- wgtfunc = em.WgtFunct()
1255
- sksicd.TRANSCODERS["Grid/Col/WgtFunct"].set_elem(wgtfunc, swath_info["col_wgts"])
1256
- grid_node.find("./{*}Col").append(wgtfunc)
1333
+ sicd_ew["Grid"]["TimeCOAPoly"] = burst_info["time_coa_poly_coefs"]
1334
+ sicd_ew["Grid"]["Row"]["DeltaKCOAPoly"] = swath_info["row_deltak_coa_poly"]
1335
+ sicd_ew["Grid"]["Col"]["DeltaKCOAPoly"] = burst_info["col_deltak_coa_poly"]
1336
+ sicd_ew["Grid"]["Row"]["WgtFunct"] = swath_info["row_wgts"]
1337
+ sicd_ew["Grid"]["Col"]["WgtFunct"] = swath_info["col_wgts"]
1257
1338
 
1258
1339
  # Timeline
1259
- timeline_node = em.Timeline(
1340
+ sicd_ew["Timeline"] = em.Timeline(
1260
1341
  em.CollectStart(burst_info["collect_start"].strftime("%Y-%m-%dT%H:%M:%S.%fZ")),
1261
1342
  em.CollectDuration(str(burst_info["collect_duration"])),
1262
1343
  em.IPP(
1263
1344
  {"size": "1"},
1264
1345
  em.Set(
1265
1346
  {"index": "1"},
1266
- em.TStart(str(swath_info["t_start"])),
1347
+ em.TStart(str(burst_info["ipp_set_tstart"])),
1267
1348
  em.TEnd(str(burst_info["ipp_set_tend"])),
1268
- em.IPPStart(str(swath_info["ipp_start"])),
1349
+ em.IPPStart(str(burst_info["ipp_set_ippstart"])),
1269
1350
  em.IPPEnd(str(burst_info["ipp_set_ippend"])),
1270
1351
  em.IPPPoly(),
1271
1352
  ),
1272
1353
  ),
1273
1354
  )
1274
- sksicd.PolyType().set_elem(
1275
- timeline_node.find("./{*}IPP/{*}Set/{*}IPPPoly"), swath_info["ipp_poly"]
1276
- )
1355
+ sicd_ew["Timeline"]["IPP"]["Set"][0]["IPPPoly"] = swath_info["ipp_poly"]
1277
1356
 
1278
1357
  # Position
1279
- position_node = em.Position(em.ARPPoly())
1280
- sksicd.XyzPolyType().set_elem(
1281
- position_node.find("./{*}ARPPoly"), burst_info["arp_poly_coefs"]
1282
- )
1358
+ sicd_ew["Position"]["ARPPoly"] = burst_info["arp_poly_coefs"]
1283
1359
 
1284
1360
  # Radar Collection
1285
- radar_collection_node = em.RadarCollection(
1361
+ sicd_ew["RadarCollection"] = em.RadarCollection(
1286
1362
  em.TxFrequency(
1287
1363
  em.Min(str(swath_info["tx_freq"][0])),
1288
1364
  em.Max(str(swath_info["tx_freq"][1])),
@@ -1321,14 +1397,19 @@ def _create_sicd_xml(base_info, swath_info, burst_info, classification):
1321
1397
  chan_indices = str(i)
1322
1398
 
1323
1399
  # Image Formation
1324
- image_formation_node = em.ImageFormation(
1400
+ now = (
1401
+ datetime.datetime.now(datetime.timezone.utc)
1402
+ .isoformat(timespec="microseconds")
1403
+ .replace("+00:00", "Z")
1404
+ )
1405
+ sicd_ew["ImageFormation"] = em.ImageFormation(
1325
1406
  em.RcvChanProc(
1326
1407
  em.NumChanProc("1"),
1327
1408
  em.PRFScaleFactor("1"),
1328
1409
  em.ChanIndex(chan_indices),
1329
1410
  ),
1330
1411
  em.TxRcvPolarizationProc(swath_info["tx_rcv_polarization_proc"]),
1331
- em.TStartProc(str(swath_info["t_start_proc"])),
1412
+ em.TStartProc(str(burst_info["tstart_proc"])),
1332
1413
  em.TEndProc(str(burst_info["tend_proc"])),
1333
1414
  em.TxFrequencyProc(
1334
1415
  em.MinProc(str(swath_info["tx_freq_proc"][0])),
@@ -1339,10 +1420,14 @@ def _create_sicd_xml(base_info, swath_info, burst_info, classification):
1339
1420
  em.ImageBeamComp(swath_info["image_beam_comp"]),
1340
1421
  em.AzAutofocus(swath_info["az_autofocus"]),
1341
1422
  em.RgAutofocus(swath_info["rg_autofocus"]),
1423
+ em.Processing(
1424
+ em.Type(f"sarkit-convert {__version__} @ {now}"),
1425
+ em.Applied("true"),
1426
+ ),
1342
1427
  )
1343
1428
 
1344
1429
  # RMA
1345
- rma_node = em.RMA(
1430
+ sicd_ew["RMA"] = em.RMA(
1346
1431
  em.RMAlgoType(swath_info["rm_algo_type"]),
1347
1432
  em.ImageType(swath_info["image_type"]),
1348
1433
  em.INCA(
@@ -1354,65 +1439,33 @@ def _create_sicd_xml(base_info, swath_info, burst_info, classification):
1354
1439
  em.DopCentroidCOA(swath_info["dop_centroid_coa"]),
1355
1440
  ),
1356
1441
  )
1357
- sksicd.PolyType().set_elem(
1358
- rma_node.find("./{*}INCA/{*}TimeCAPoly"), burst_info["time_ca_poly_coefs"]
1359
- )
1360
- sksicd.Poly2dType().set_elem(
1361
- rma_node.find("./{*}INCA/{*}DRateSFPoly"), burst_info["drsf_poly_coefs"]
1362
- )
1363
- sksicd.Poly2dType().set_elem(
1364
- rma_node.find("./{*}INCA/{*}DopCentroidPoly"),
1365
- burst_info["doppler_centroid_poly_coefs"],
1366
- )
1442
+ sicd_ew["RMA"]["INCA"]["TimeCAPoly"] = burst_info["time_ca_poly_coefs"]
1443
+ sicd_ew["RMA"]["INCA"]["DRateSFPoly"] = burst_info["drsf_poly_coefs"]
1444
+ sicd_ew["RMA"]["INCA"]["DopCentroidPoly"] = burst_info[
1445
+ "doppler_centroid_poly_coefs"
1446
+ ]
1367
1447
 
1368
- sicd_xml_obj = em.SICD(
1369
- collection_info_node,
1370
- image_creation_node,
1371
- image_data_node,
1372
- geo_data_node,
1373
- grid_node,
1374
- timeline_node,
1375
- position_node,
1376
- radar_collection_node,
1377
- image_formation_node,
1378
- rma_node,
1379
- )
1380
- breakpoint()
1381
1448
  # Add Radiometric after Sentinel baseline processing calibration update on 25 Nov 2015.
1382
1449
  if "radiometric" in burst_info:
1383
1450
  # Radiometric
1384
- radiometric_node = em.Radiometric(
1385
- em.NoiseLevel(
1386
- em.NoiseLevelType(burst_info["radiometric"]["noise_level_type"]),
1387
- em.NoisePoly(),
1388
- ),
1389
- em.RCSSFPoly(),
1390
- em.SigmaZeroSFPoly(),
1391
- em.BetaZeroSFPoly(),
1392
- em.GammaZeroSFPoly(),
1393
- )
1394
- sksicd.Poly2dType().set_elem(
1395
- radiometric_node.find("./{*}NoiseLevel/{*}NoisePoly"),
1396
- burst_info["radiometric"]["noise_poly_coefs"],
1397
- )
1398
- sksicd.Poly2dType().set_elem(
1399
- radiometric_node.find("./{*}RCSSFPoly"),
1400
- burst_info["radiometric"]["rcs_sf_poly_coefs"],
1401
- )
1402
- sksicd.Poly2dType().set_elem(
1403
- radiometric_node.find("./{*}SigmaZeroSFPoly"),
1404
- burst_info["radiometric"]["sigma_zero_poly_coefs"],
1405
- )
1406
- sksicd.Poly2dType().set_elem(
1407
- radiometric_node.find("./{*}BetaZeroSFPoly"),
1408
- burst_info["radiometric"]["beta_zero_poly_coefs"],
1409
- )
1410
- sksicd.Poly2dType().set_elem(
1411
- radiometric_node.find("./{*}GammaZeroSFPoly"),
1412
- burst_info["radiometric"]["gamma_zero_poly_coefs"],
1413
- )
1414
-
1415
- sicd_xml_obj.find("{*}RMA").addprevious(radiometric_node)
1451
+ sicd_ew["Radiometric"]["NoiseLevel"]["NoiseLevelType"] = burst_info[
1452
+ "radiometric"
1453
+ ]["noise_level_type"]
1454
+ sicd_ew["Radiometric"]["NoiseLevel"]["NoisePoly"] = burst_info["radiometric"][
1455
+ "noise_poly_coefs"
1456
+ ]
1457
+ sicd_ew["Radiometric"]["RCSSFPoly"] = burst_info["radiometric"][
1458
+ "rcs_sf_poly_coefs"
1459
+ ]
1460
+ sicd_ew["Radiometric"]["SigmaZeroSFPoly"] = burst_info["radiometric"][
1461
+ "sigma_zero_poly_coefs"
1462
+ ]
1463
+ sicd_ew["Radiometric"]["BetaZeroSFPoly"] = burst_info["radiometric"][
1464
+ "beta_zero_poly_coefs"
1465
+ ]
1466
+ sicd_ew["Radiometric"]["GammaZeroSFPoly"] = burst_info["radiometric"][
1467
+ "gamma_zero_poly_coefs"
1468
+ ]
1416
1469
 
1417
1470
  return sicd_xml_obj
1418
1471
 
@@ -1481,12 +1534,6 @@ def main(args=None):
1481
1534
  type=pathlib.Path,
1482
1535
  help="path of the output SICD file. The strings '{swath}', '{burst}', '{pol}' will be replaced as appropriate for multiple images",
1483
1536
  )
1484
- parser.add_argument(
1485
- "--ostaid",
1486
- type=str,
1487
- help="content of the originating station ID (OSTAID) field of the NITF header",
1488
- default="Unknown",
1489
- )
1490
1537
  config = parser.parse_args(args)
1491
1538
 
1492
1539
  manifest_filename = config.safe_product_folder / "manifest.safe"
@@ -1498,9 +1545,8 @@ def main(args=None):
1498
1545
  used_filenames = set()
1499
1546
  for entry in files:
1500
1547
  product_root_node = et.parse(entry["product"]).getroot()
1501
- swath_info = _collect_swath_info(product_root_node)
1502
- burst_info_list = _collect_burst_info(product_root_node, swath_info)
1503
- breakpoint()
1548
+ swath_info = _collect_swath_info(product_root_node, base_info)
1549
+ burst_info_list = _collect_burst_info(product_root_node, base_info, swath_info)
1504
1550
  if base_info["creation_date_time"].date() >= np.datetime64("2015-11-25"):
1505
1551
  [burst_info.update({"radiometric": {}}) for burst_info in burst_info_list]
1506
1552
  _calc_radiometric_info(entry["calibration"], swath_info, burst_info_list)
@@ -1544,7 +1590,7 @@ def main(args=None):
1544
1590
  metadata = sksicd.NitfMetadata(
1545
1591
  xmltree=sicd.getroottree(),
1546
1592
  file_header_part={
1547
- "ostaid": config.ostaid,
1593
+ "ostaid": "ESA",
1548
1594
  "ftitle": xml_helper.load("{*}CollectionInfo/{*}CoreName"),
1549
1595
  "security": {
1550
1596
  "clas": config.classification[0].upper(),