xradio 0.0.41__py3-none-any.whl → 0.0.43__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 (62) hide show
  1. xradio/_utils/coord_math.py +100 -0
  2. xradio/_utils/list_and_array.py +49 -4
  3. xradio/_utils/schema.py +36 -16
  4. xradio/image/_util/_casacore/xds_from_casacore.py +5 -5
  5. xradio/image/_util/_casacore/xds_to_casacore.py +12 -11
  6. xradio/image/_util/_fits/xds_from_fits.py +18 -17
  7. xradio/image/_util/_zarr/zarr_low_level.py +29 -12
  8. xradio/image/_util/common.py +1 -1
  9. xradio/image/_util/image_factory.py +1 -1
  10. xradio/{correlated_data → measurement_set}/__init__.py +7 -4
  11. xradio/measurement_set/_utils/__init__.py +5 -0
  12. xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/_tables/load_main_table.py +1 -1
  13. xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/_tables/read.py +1 -1
  14. xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/conversion.py +115 -37
  15. xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/create_antenna_xds.py +62 -37
  16. xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/create_field_and_source_xds.py +117 -25
  17. xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/msv4_sub_xdss.py +47 -13
  18. xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/partition_queries.py +4 -0
  19. xradio/{correlated_data → measurement_set}/_utils/_utils/xds_helper.py +1 -1
  20. xradio/{correlated_data/_utils/ms.py → measurement_set/_utils/msv2.py} +4 -4
  21. xradio/{correlated_data → measurement_set}/convert_msv2_to_processing_set.py +7 -2
  22. xradio/{correlated_data → measurement_set}/load_processing_set.py +5 -5
  23. xradio/measurement_set/measurement_set_xds.py +110 -0
  24. xradio/{correlated_data → measurement_set}/open_processing_set.py +9 -16
  25. xradio/measurement_set/processing_set.py +777 -0
  26. xradio/{correlated_data → measurement_set}/schema.py +1110 -586
  27. xradio/schema/check.py +42 -22
  28. xradio/schema/dataclass.py +56 -6
  29. xradio/sphinx/__init__.py +12 -0
  30. xradio/sphinx/schema_table.py +351 -0
  31. {xradio-0.0.41.dist-info → xradio-0.0.43.dist-info}/METADATA +9 -6
  32. xradio-0.0.43.dist-info/RECORD +76 -0
  33. {xradio-0.0.41.dist-info → xradio-0.0.43.dist-info}/WHEEL +1 -1
  34. xradio/_utils/common.py +0 -101
  35. xradio/correlated_data/_utils/__init__.py +0 -5
  36. xradio/correlated_data/correlated_xds.py +0 -13
  37. xradio/correlated_data/processing_set.py +0 -301
  38. xradio/correlated_data/test__processing_set.py +0 -74
  39. xradio-0.0.41.dist-info/RECORD +0 -75
  40. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/_tables/load.py +0 -0
  41. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/_tables/read_main_table.py +0 -0
  42. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/_tables/read_subtables.py +0 -0
  43. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/_tables/table_query.py +0 -0
  44. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/_tables/write.py +0 -0
  45. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/_tables/write_exp_api.py +0 -0
  46. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/chunks.py +0 -0
  47. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/descr.py +0 -0
  48. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/msv2_msv3.py +0 -0
  49. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/msv2_to_msv4_meta.py +0 -0
  50. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/msv4_info_dicts.py +0 -0
  51. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/optimised_functions.py +0 -0
  52. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/partitions.py +0 -0
  53. /xradio/{correlated_data/_utils/_ms → measurement_set/_utils/_msv2}/subtables.py +0 -0
  54. /xradio/{correlated_data → measurement_set}/_utils/_utils/cds.py +0 -0
  55. /xradio/{correlated_data → measurement_set}/_utils/_utils/partition_attrs.py +0 -0
  56. /xradio/{correlated_data → measurement_set}/_utils/_utils/stokes_types.py +0 -0
  57. /xradio/{correlated_data → measurement_set}/_utils/_zarr/encoding.py +0 -0
  58. /xradio/{correlated_data → measurement_set}/_utils/_zarr/read.py +0 -0
  59. /xradio/{correlated_data → measurement_set}/_utils/_zarr/write.py +0 -0
  60. /xradio/{correlated_data → measurement_set}/_utils/zarr.py +0 -0
  61. {xradio-0.0.41.dist-info → xradio-0.0.43.dist-info}/LICENSE.txt +0 -0
  62. {xradio-0.0.41.dist-info → xradio-0.0.43.dist-info}/top_level.txt +0 -0
@@ -11,18 +11,18 @@ import xarray as xr
11
11
 
12
12
  import toolviper.utils.logger as logger
13
13
  from casacore import tables
14
- from xradio.correlated_data._utils._ms.msv4_sub_xdss import (
14
+ from xradio.measurement_set._utils._msv2.msv4_sub_xdss import (
15
15
  create_pointing_xds,
16
16
  create_system_calibration_xds,
17
17
  create_weather_xds,
18
18
  )
19
19
  from .msv4_info_dicts import create_info_dicts
20
- from xradio.correlated_data._utils._ms.create_antenna_xds import (
20
+ from xradio.measurement_set._utils._msv2.create_antenna_xds import (
21
21
  create_antenna_xds,
22
22
  create_gain_curve_xds,
23
23
  create_phase_calibration_xds,
24
24
  )
25
- from xradio.correlated_data._utils._ms.create_field_and_source_xds import (
25
+ from xradio.measurement_set._utils._msv2.create_field_and_source_xds import (
26
26
  create_field_and_source_xds,
27
27
  )
28
28
  from xradio._utils.schema import column_description_casacore_to_msv4_measure
@@ -422,9 +422,9 @@ def create_coordinates(
422
422
  "time": utime,
423
423
  "baseline_antenna1_id": ("baseline_id", baseline_ant1_id),
424
424
  "baseline_antenna2_id": ("baseline_id", baseline_ant2_id),
425
- "uvw_label": ["u", "v", "w"],
426
425
  "baseline_id": np.arange(len(baseline_ant1_id)),
427
426
  "scan_number": ("time", scan_id),
427
+ "uvw_label": ["u", "v", "w"],
428
428
  }
429
429
 
430
430
  ddi_xds = load_generic_table(in_file, "DATA_DESCRIPTION").sel(row=ddi)
@@ -482,7 +482,6 @@ def create_coordinates(
482
482
  }
483
483
  xds.frequency.attrs["spectral_window_id"] = spectral_window_id
484
484
 
485
- # xds.frequency.attrs["effective_channel_width"] = "EFFECTIVE_CHANNEL_WIDTH"
486
485
  # Add if doppler table is present
487
486
  # xds.frequency.attrs["doppler_velocity"] =
488
487
  # xds.frequency.attrs["doppler_type"] =
@@ -605,8 +604,8 @@ def create_data_variables(
605
604
  logger.debug(
606
605
  "Time to read column " + str(col) + " : " + str(time.time() - start)
607
606
  )
608
- except Exception as e:
609
- logger.debug("Could not load column", col)
607
+ except Exception as exc:
608
+ logger.debug(f"Could not load column {col}, exception: {exc}")
610
609
 
611
610
  if ("WEIGHT_SPECTRUM" == col) and (
612
611
  "WEIGHT" in col_names
@@ -624,13 +623,27 @@ def create_data_variables(
624
623
 
625
624
 
626
625
  def add_missing_data_var_attrs(xds):
627
- """Adds in attributes expected metadata that cannot be found
628
- in the input MSv2. For now specifically for missing
629
- single-dish/SPECTRUM metadata"""
626
+ """
627
+ Adds in the xds attributes expected metadata that cannot be found in the input MSv2.
628
+ For now:
629
+ - missing single-dish/SPECTRUM metadata
630
+ - missing interferometry/VISIBILITY_MODEL metadata
631
+ """
630
632
  data_var_names = ["SPECTRUM", "SPECTRUM_CORRECTED"]
631
633
  for var_name in data_var_names:
632
634
  if var_name in xds.data_vars:
633
- xds.data_vars[var_name].attrs["units"] = ["Jy"]
635
+ xds.data_vars[var_name].attrs["units"] = [""]
636
+
637
+ vis_var_names = ["VISIBILITY_MODEL"]
638
+ for var_name in vis_var_names:
639
+ if var_name in xds.data_vars and "units" not in xds.data_vars[var_name].attrs:
640
+ # Assume MODEL uses the same units
641
+ if "VISIBILITY" in xds.data_vars:
642
+ xds.data_vars[var_name].attrs["units"] = xds.data_vars[
643
+ "VISIBILITY"
644
+ ].attrs["units"]
645
+ else:
646
+ xds.data_vars[var_name].attrs["units"] = [""]
634
647
 
635
648
  return xds
636
649
 
@@ -674,6 +687,7 @@ def create_taql_query(partition_info):
674
687
  "FIELD_ID",
675
688
  "SCAN_NUMBER",
676
689
  "STATE_ID",
690
+ "ANTENNA1",
677
691
  ]
678
692
 
679
693
  taql_where = "WHERE "
@@ -683,11 +697,38 @@ def create_taql_query(partition_info):
683
697
  taql_where
684
698
  + f"({col_name} IN [{','.join(map(str, partition_info[col_name]))}]) AND"
685
699
  )
700
+ if col_name == "ANTENNA1":
701
+ taql_where = (
702
+ taql_where
703
+ + f"(ANTENNA2 IN [{','.join(map(str, partition_info[col_name]))}]) AND"
704
+ )
686
705
  taql_where = taql_where[:-3]
687
706
 
688
707
  return taql_where
689
708
 
690
709
 
710
+ def fix_uvw_frame(
711
+ xds: xr.Dataset, field_and_source_xds: xr.Dataset, is_single_dish: bool
712
+ ) -> xr.Dataset:
713
+ """
714
+ Fix UVW frame
715
+
716
+ From CASA fixvis docs: clean and the im tool ignore the reference frame claimed by the UVW column (it is often
717
+ mislabelled as ITRF when it is really FK5 (J2000)) and instead assume the (u, v, w)s are in the same frame as the phase
718
+ tracking center. calcuvw does not yet force the UVW column and field centers to use the same reference frame!
719
+ Blank = use the phase tracking frame of vis.
720
+ """
721
+ if xds.UVW.attrs["frame"] == "ITRF":
722
+ if is_single_dish:
723
+ center_var = "FIELD_REFERENCE_CENTER"
724
+ else:
725
+ center_var = "FIELD_PHASE_CENTER"
726
+
727
+ xds.UVW.attrs["frame"] = field_and_source_xds[center_var].attrs["frame"]
728
+
729
+ return xds
730
+
731
+
691
732
  def convert_and_write_partition(
692
733
  in_file: str,
693
734
  out_file: str,
@@ -798,9 +839,9 @@ def convert_and_write_partition(
798
839
  start = time.time()
799
840
  xds = xr.Dataset(
800
841
  attrs={
801
- "creation_date": datetime.datetime.now().isoformat(),
842
+ "creation_date": datetime.datetime.utcnow().isoformat(),
802
843
  "xradio_version": importlib.metadata.version("xradio"),
803
- "schema_version": "4.0.-9999",
844
+ "schema_version": "4.0.-9994",
804
845
  "type": "visibility",
805
846
  }
806
847
  )
@@ -847,7 +888,6 @@ def convert_and_write_partition(
847
888
 
848
889
  # Add data_groups
849
890
  xds, is_single_dish = add_data_groups(xds)
850
-
851
891
  xds = add_missing_data_var_attrs(xds)
852
892
 
853
893
  if (
@@ -895,8 +935,8 @@ def convert_and_write_partition(
895
935
  antenna_id,
896
936
  feed_id,
897
937
  telescope_name,
938
+ xds.polarization,
898
939
  )
899
-
900
940
  logger.debug("Time antenna xds " + str(time.time() - start))
901
941
 
902
942
  start = time.time()
@@ -916,7 +956,10 @@ def convert_and_write_partition(
916
956
  logger.debug("Time phase_calibration xds " + str(time.time() - start))
917
957
 
918
958
  # Change antenna_ids to antenna_names
919
- xds = antenna_ids_to_names(xds, ant_xds, is_single_dish)
959
+ with_antenna_partitioning = "ANTENNA1" in partition_info
960
+ xds = antenna_ids_to_names(
961
+ xds, ant_xds, is_single_dish, with_antenna_partitioning
962
+ )
920
963
  # but before, keep the name-id arrays, we need them for the pointing and weather xds
921
964
  ant_xds_name_ids = ant_xds["antenna_name"].set_xindex("antenna_id")
922
965
  ant_xds_station_name_ids = ant_xds["station"].set_xindex("antenna_id")
@@ -943,6 +986,7 @@ def convert_and_write_partition(
943
986
  logger.debug("Time weather " + str(time.time() - start))
944
987
 
945
988
  # Create pointing_xds
989
+ pointing_xds = xr.Dataset()
946
990
  if with_pointing:
947
991
  start = time.time()
948
992
  if pointing_interpolate:
@@ -1003,16 +1047,7 @@ def convert_and_write_partition(
1003
1047
  )
1004
1048
  logger.debug("Time field_and_source_xds " + str(time.time() - start))
1005
1049
 
1006
- # Fix UVW frame
1007
- # From CASA fixvis docs: clean and the im tool ignore the reference frame claimed by the UVW column (it is often mislabelled as ITRF when it is really FK5 (J2000)) and instead assume the (u, v, w)s are in the same frame as the phase tracking center. calcuvw does not yet force the UVW column and field centers to use the same reference frame! Blank = use the phase tracking frame of vis.
1008
- if is_single_dish:
1009
- xds.UVW.attrs["frame"] = field_and_source_xds[
1010
- "FIELD_REFERENCE_CENTER"
1011
- ].attrs["frame"]
1012
- else:
1013
- xds.UVW.attrs["frame"] = field_and_source_xds[
1014
- "FIELD_PHASE_CENTER"
1015
- ].attrs["frame"]
1050
+ xds = fix_uvw_frame(xds, field_and_source_xds, is_single_dish)
1016
1051
 
1017
1052
  partition_info_misc_fields = {
1018
1053
  "scan_id": scan_id,
@@ -1040,41 +1075,53 @@ def convert_and_write_partition(
1040
1075
  else:
1041
1076
  mode = "w-"
1042
1077
 
1078
+ if is_single_dish:
1079
+ xds.attrs["type"] = "spectrum"
1080
+ xds = xds.drop_vars(["UVW"])
1081
+ del xds["uvw_label"]
1082
+ else:
1083
+ if any("WVR" in s for s in intents):
1084
+ xds.attrs["type"] = "wvr"
1085
+ else:
1086
+ xds.attrs["type"] = "visibility"
1087
+
1043
1088
  start = time.time()
1044
1089
  if storage_backend == "zarr":
1045
- xds.to_zarr(store=os.path.join(file_name, "MAIN"), mode=mode)
1046
- ant_xds.to_zarr(store=os.path.join(file_name, "ANTENNA"), mode=mode)
1090
+ xds.to_zarr(store=os.path.join(file_name, "correlated_xds"), mode=mode)
1091
+ ant_xds.to_zarr(store=os.path.join(file_name, "antenna_xds"), mode=mode)
1047
1092
  for group_name in xds.attrs["data_groups"]:
1048
1093
  field_and_source_xds.to_zarr(
1049
1094
  store=os.path.join(
1050
- file_name, f"FIELD_AND_SOURCE_{group_name.upper()}"
1095
+ file_name, f"field_and_source_xds_{group_name}"
1051
1096
  ),
1052
1097
  mode=mode,
1053
1098
  )
1054
1099
 
1055
- if with_pointing and len(pointing_xds.data_vars) > 1:
1100
+ if with_pointing and len(pointing_xds.data_vars) > 0:
1056
1101
  pointing_xds.to_zarr(
1057
- store=os.path.join(file_name, "POINTING"), mode=mode
1102
+ store=os.path.join(file_name, "pointing_xds"), mode=mode
1058
1103
  )
1059
1104
 
1060
1105
  if system_calibration_xds:
1061
1106
  system_calibration_xds.to_zarr(
1062
- store=os.path.join(file_name, "SYSCAL"), mode=mode
1107
+ store=os.path.join(file_name, "system_calibration_xds"),
1108
+ mode=mode,
1063
1109
  )
1064
1110
 
1065
1111
  if gain_curve_xds:
1066
1112
  gain_curve_xds.to_zarr(
1067
- store=os.path.join(file_name, "GAIN_CURVE"), mode=mode
1113
+ store=os.path.join(file_name, "gain_curve_xds"), mode=mode
1068
1114
  )
1069
1115
 
1070
1116
  if phase_calibration_xds:
1071
1117
  phase_calibration_xds.to_zarr(
1072
- store=os.path.join(file_name, "PHASE_CAL"), mode=mode
1118
+ store=os.path.join(file_name, "phase_calibration_xds"),
1119
+ mode=mode,
1073
1120
  )
1074
1121
 
1075
1122
  if weather_xds:
1076
1123
  weather_xds.to_zarr(
1077
- store=os.path.join(file_name, "WEATHER"), mode=mode
1124
+ store=os.path.join(file_name, "weather_xds"), mode=mode
1078
1125
  )
1079
1126
 
1080
1127
  elif storage_backend == "netcdf":
@@ -1086,8 +1133,31 @@ def convert_and_write_partition(
1086
1133
 
1087
1134
 
1088
1135
  def antenna_ids_to_names(
1089
- xds: xr.Dataset, ant_xds: xr.Dataset, is_single_dish: bool
1136
+ xds: xr.Dataset,
1137
+ ant_xds: xr.Dataset,
1138
+ is_single_dish: bool,
1139
+ with_antenna_partitioning,
1090
1140
  ) -> xr.Dataset:
1141
+ """
1142
+ Turns the antenna_ids that we get from MSv2 into MSv4 antenna_name
1143
+
1144
+ Parameters
1145
+ ----------
1146
+ xds: xr.Dataset
1147
+ A main xds (MSv4)
1148
+ ant_xds: xr.Dataset
1149
+ The antenna_xds for this MSv4
1150
+ is_single_dish: bool
1151
+ Whether a single-dish ("spectrum" data) dataset
1152
+ with_antenna_partitioning: bool
1153
+ Whether the MSv4 partitions include the antenna axis => only
1154
+ one antenna (and implicitly one 'baseline' - auto-correlation)
1155
+
1156
+ Returns
1157
+ ----------
1158
+ xr.Dataset
1159
+ The main xds with antenna_id replaced with antenna_name
1160
+ """
1091
1161
  ant_xds = ant_xds.set_xindex(
1092
1162
  "antenna_id"
1093
1163
  ) # Allows for non-dimension coordinate selection.
@@ -1106,7 +1176,15 @@ def antenna_ids_to_names(
1106
1176
  }
1107
1177
  )
1108
1178
  else:
1109
- xds["baseline_id"] = ant_xds["antenna_name"].sel(antenna_id=xds["baseline_id"])
1179
+ if not with_antenna_partitioning:
1180
+ # baseline_antenna1_id will be removed soon below, but it is useful here to know the actual antenna_ids,
1181
+ # as opposed to the baseline_ids which can mismatch when data is missing for some antennas
1182
+ xds["baseline_id"] = ant_xds["antenna_name"].sel(
1183
+ antenna_id=xds["baseline_antenna1_id"]
1184
+ )
1185
+ else:
1186
+ xds["baseline_id"] = ant_xds["antenna_name"]
1187
+
1110
1188
  unwanted_coords_from_ant_xds = [
1111
1189
  "antenna_id",
1112
1190
  "antenna_name",
@@ -6,8 +6,8 @@ import numpy as np
6
6
  import xarray as xr
7
7
  import os
8
8
 
9
- from xradio.correlated_data._utils._ms.subtables import subt_rename_ids
10
- from xradio.correlated_data._utils._ms._tables.read import (
9
+ from xradio.measurement_set._utils._msv2.subtables import subt_rename_ids
10
+ from xradio.measurement_set._utils._msv2._tables.read import (
11
11
  load_generic_table,
12
12
  convert_casacore_time,
13
13
  convert_casacore_time_to_mjd,
@@ -15,7 +15,7 @@ from xradio.correlated_data._utils._ms._tables.read import (
15
15
  table_exists,
16
16
  )
17
17
  from xradio._utils.schema import convert_generic_xds_to_xradio_schema
18
- from xradio.correlated_data._utils._ms.msv4_sub_xdss import interpolate_to_time
18
+ from xradio.measurement_set._utils._msv2.msv4_sub_xdss import interpolate_to_time
19
19
 
20
20
  from xradio._utils.list_and_array import (
21
21
  check_if_consistent,
@@ -31,6 +31,7 @@ def create_antenna_xds(
31
31
  antenna_id: list,
32
32
  feed_id: list,
33
33
  telescope_name: str,
34
+ partition_polarization: xr.DataArray,
34
35
  ) -> xr.Dataset:
35
36
  """
36
37
  Create an Xarray Dataset containing antenna information.
@@ -47,6 +48,8 @@ def create_antenna_xds(
47
48
  List of feed IDs.
48
49
  telescope_name : str
49
50
  Name of the telescope.
51
+ partition_polarization: xr.DataArray
52
+ Polarization labels of this partition, needed if that info is not present in FEED
50
53
 
51
54
  Returns
52
55
  ----------
@@ -59,6 +62,18 @@ def create_antenna_xds(
59
62
  ant_xds = extract_feed_info(
60
63
  ant_xds, in_file, antenna_id, feed_id, spectral_window_id
61
64
  )
65
+ # Needed for special SPWs such as ALMA WVR or CHANNEL_AVERAGE data (have no feed info)
66
+ if "polarization_type" not in ant_xds:
67
+ pols_chars = list(partition_polarization.values[0])
68
+ pols_labels = [f"pol_{idx}" for idx in np.arange(0, len(pols_chars))]
69
+ ant_xds = ant_xds.assign_coords(receptor_label=pols_labels)
70
+ pol_type_values = [pols_chars] * len(ant_xds.antenna_name)
71
+ ant_xds = ant_xds.assign_coords(
72
+ polarization_type=(
73
+ ["antenna_name", "receptor_label"],
74
+ pol_type_values,
75
+ )
76
+ )
62
77
 
63
78
  ant_xds.attrs["overall_telescope_name"] = telescope_name
64
79
  return ant_xds
@@ -87,7 +102,6 @@ def extract_antenna_info(
87
102
  """
88
103
  to_new_data_variables = {
89
104
  "POSITION": ["ANTENNA_POSITION", ["antenna_name", "cartesian_pos_label"]],
90
- "OFFSET": ["ANTENNA_FEED_OFFSET", ["antenna_name", "cartesian_pos_label"]],
91
105
  "DISH_DIAMETER": ["ANTENNA_DISH_DIAMETER", ["antenna_name"]],
92
106
  }
93
107
 
@@ -95,7 +109,7 @@ def extract_antenna_info(
95
109
  "NAME": ["antenna_name", ["antenna_name"]],
96
110
  "STATION": ["station", ["antenna_name"]],
97
111
  "MOUNT": ["mount", ["antenna_name"]],
98
- "PHASED_ARRAY_ID": ["phased_array_id", ["antenna_name"]],
112
+ # "PHASED_ARRAY_ID": ["phased_array_id", ["antenna_name"]],
99
113
  "antenna_id": ["antenna_id", ["antenna_name"]],
100
114
  }
101
115
 
@@ -124,9 +138,8 @@ def extract_antenna_info(
124
138
 
125
139
  ant_xds["ANTENNA_DISH_DIAMETER"].attrs.update({"units": ["m"], "type": "quantity"})
126
140
 
127
- ant_xds["ANTENNA_FEED_OFFSET"].attrs["type"] = "earth_location_offset"
128
- ant_xds["ANTENNA_FEED_OFFSET"].attrs["coordinate_system"] = "geocentric"
129
141
  ant_xds["ANTENNA_POSITION"].attrs["coordinate_system"] = "geocentric"
142
+ ant_xds["ANTENNA_POSITION"].attrs["origin_object_name"] = "earth"
130
143
 
131
144
  if telescope_name in ["ALMA", "VLA", "NOEMA", "EVLA"]:
132
145
  # antenna_name = ant_xds["antenna_name"].values + "_" + ant_xds["station"].values
@@ -202,12 +215,17 @@ def extract_feed_info(
202
215
  taql_where=f" where (ANTENNA_ID IN [{','.join(map(str, ant_xds.antenna_id.values))}]) AND (FEED_ID IN [{','.join(map(str, feed_id))}])",
203
216
  ) # Some Lofar and MeerKAT data have the spw column set to -1 so we can't use '(SPECTRAL_WINDOW_ID = {spectral_window_id})'
204
217
 
218
+ if not generic_feed_xds:
219
+ # Some MSv2 have a FEED table that does not cover all antenna_id (and feed_id)
220
+ return ant_xds
221
+
205
222
  feed_spw = np.unique(generic_feed_xds.SPECTRAL_WINDOW_ID)
206
223
  if len(feed_spw) == 1 and feed_spw[0] == -1:
207
224
  generic_feed_xds = generic_feed_xds.isel(SPECTRAL_WINDOW_ID=0, drop=True)
208
225
  else:
209
226
  if spectral_window_id not in feed_spw:
210
- return ant_xds # For some spw the feed table is empty (this is the case with ALMA spw WVR#NOMINAL).
227
+ # For some spw the feed table is empty (this is the case with ALMA spw WVR#NOMINAL).
228
+ return ant_xds
211
229
  else:
212
230
  generic_feed_xds = generic_feed_xds.sel(
213
231
  SPECTRAL_WINDOW_ID=spectral_window_id, drop=True
@@ -228,14 +246,14 @@ def extract_feed_info(
228
246
  ), "The number of receptors must be constant in feed table."
229
247
 
230
248
  to_new_data_variables = {
231
- "BEAM_OFFSET": [
232
- "BEAM_OFFSET",
233
- ["antenna_name", "receptor_label", "sky_dir_label"],
249
+ "RECEPTOR_ANGLE": [
250
+ "ANTENNA_RECEPTOR_ANGLE",
251
+ ["antenna_name", "receptor_label"],
234
252
  ],
235
- "RECEPTOR_ANGLE": ["RECEPTOR_ANGLE", ["antenna_name", "receptor_label"]],
236
- # "pol_response": ["POLARIZATION_RESPONSE", ["antenna_name", "receptor_label", "receptor_name_"]] #repeated dim creates problems.
237
- "FOCUS_LENGTH": ["FOCUS_LENGTH", ["antenna_name"]], # optional
238
- # "position": ["ANTENNA_FEED_OFFSET",["antenna_name", "cartesian_pos_label"]] #Will be added to the existing position in ant_xds
253
+ "FOCUS_LENGTH": [
254
+ "ANTENNA_FOCUS_LENGTH",
255
+ ["antenna_name"],
256
+ ], # optional
239
257
  }
240
258
 
241
259
  to_new_coords = {
@@ -249,29 +267,31 @@ def extract_feed_info(
249
267
  to_new_coords=to_new_coords,
250
268
  )
251
269
 
252
- # print('ant_xds["ANTENNA_FEED_OFFSET"]',ant_xds["ANTENNA_FEED_OFFSET"].data)
253
- # print('generic_feed_xds["POSITION"].data',generic_feed_xds["POSITION"].data)
254
- feed_offset_attrs = ant_xds["ANTENNA_FEED_OFFSET"].attrs
255
- ant_xds["ANTENNA_FEED_OFFSET"] = (
256
- ant_xds["ANTENNA_FEED_OFFSET"] + generic_feed_xds["POSITION"].data
257
- )
258
- # recover attrs after arithmetic operation
259
- ant_xds["ANTENNA_FEED_OFFSET"].attrs.update(feed_offset_attrs)
260
-
261
- coords = {}
262
270
  # coords["receptor_label"] = "pol_" + np.arange(ant_xds.sizes["receptor_label"]).astype(str) #Works on laptop but fails in github test runner.
263
- coords["receptor_label"] = np.array(
264
- list(
265
- map(
266
- lambda x, y: x + "_" + y,
267
- ["pol"] * ant_xds.sizes["receptor_label"],
268
- np.arange(ant_xds.sizes["receptor_label"]).astype(str),
269
- )
271
+ coords = {
272
+ "receptor_label": np.array(
273
+ list(
274
+ map(
275
+ lambda x, y: x + "_" + y,
276
+ ["pol"] * ant_xds.sizes["receptor_label"],
277
+ np.arange(ant_xds.sizes["receptor_label"]).astype(str),
278
+ )
279
+ ),
280
+ dtype=str,
270
281
  )
271
- )
282
+ }
272
283
 
273
- coords["sky_dir_label"] = ["ra", "dec"]
274
284
  ant_xds = ant_xds.assign_coords(coords)
285
+
286
+ # Correct to expected types. Some ALMA-SD (at least) leave receptor_label, polarization_type columns
287
+ # in the MS empty, causing a type mismatch
288
+ if (
289
+ "polarization_type" in ant_xds.coords
290
+ and ant_xds.coords["polarization_type"].dtype != str
291
+ ):
292
+ ant_xds.coords["polarization_type"] = ant_xds.coords[
293
+ "polarization_type"
294
+ ].astype(str)
275
295
  return ant_xds
276
296
 
277
297
 
@@ -370,6 +390,11 @@ def create_gain_curve_xds(
370
390
  }
371
391
  )
372
392
 
393
+ # correct expected types (for example "GAIN_CURVE" can be float32)
394
+ for data_var in gain_curve_xds:
395
+ if gain_curve_xds.data_vars[data_var].dtype != np.float64:
396
+ gain_curve_xds[data_var] = gain_curve_xds[data_var].astype(np.float64)
397
+
373
398
  return gain_curve_xds
374
399
 
375
400
 
@@ -445,7 +470,7 @@ def create_phase_calibration_xds(
445
470
  "TIME": ["time_phase_cal", ["time_phase_cal"]],
446
471
  }
447
472
 
448
- phase_cal_xds = xr.Dataset(attrs={"type": "phase_cal"})
473
+ phase_cal_xds = xr.Dataset(attrs={"type": "phase_calibration"})
449
474
  phase_cal_xds = convert_generic_xds_to_xradio_schema(
450
475
  generic_phase_cal_xds, phase_cal_xds, to_new_data_variables, to_new_coords
451
476
  )
@@ -494,8 +519,8 @@ def create_phase_calibration_xds(
494
519
  time_coord_attrs = {
495
520
  "type": "time",
496
521
  "units": ["s"],
497
- "scale": "UTC",
498
- "format": "UNIX",
522
+ "scale": "utc",
523
+ "format": "unix",
499
524
  }
500
525
 
501
526
  # If we interpolate rename the time_phase_cal axis to time.