xradio 0.0.48__py3-none-any.whl → 0.0.50__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 (32) hide show
  1. xradio/__init__.py +1 -0
  2. xradio/_utils/dict_helpers.py +69 -2
  3. xradio/image/_util/__init__.py +0 -3
  4. xradio/image/_util/_casacore/common.py +0 -13
  5. xradio/image/_util/_casacore/xds_from_casacore.py +102 -97
  6. xradio/image/_util/_casacore/xds_to_casacore.py +36 -24
  7. xradio/image/_util/_fits/xds_from_fits.py +81 -36
  8. xradio/image/_util/_zarr/zarr_low_level.py +3 -3
  9. xradio/image/_util/casacore.py +7 -5
  10. xradio/image/_util/common.py +13 -26
  11. xradio/image/_util/image_factory.py +143 -191
  12. xradio/image/image.py +10 -59
  13. xradio/measurement_set/__init__.py +11 -6
  14. xradio/measurement_set/_utils/_msv2/_tables/read.py +187 -46
  15. xradio/measurement_set/_utils/_msv2/_tables/table_query.py +22 -0
  16. xradio/measurement_set/_utils/_msv2/conversion.py +352 -318
  17. xradio/measurement_set/_utils/_msv2/msv4_info_dicts.py +20 -17
  18. xradio/measurement_set/convert_msv2_to_processing_set.py +46 -6
  19. xradio/measurement_set/load_processing_set.py +100 -53
  20. xradio/measurement_set/measurement_set_xdt.py +319 -0
  21. xradio/measurement_set/open_processing_set.py +122 -86
  22. xradio/measurement_set/processing_set_xdt.py +1552 -0
  23. xradio/measurement_set/schema.py +201 -94
  24. xradio/schema/bases.py +5 -1
  25. xradio/schema/check.py +97 -5
  26. {xradio-0.0.48.dist-info → xradio-0.0.50.dist-info}/METADATA +5 -4
  27. {xradio-0.0.48.dist-info → xradio-0.0.50.dist-info}/RECORD +30 -30
  28. {xradio-0.0.48.dist-info → xradio-0.0.50.dist-info}/WHEEL +1 -1
  29. xradio/measurement_set/measurement_set_xds.py +0 -117
  30. xradio/measurement_set/processing_set.py +0 -803
  31. {xradio-0.0.48.dist-info → xradio-0.0.50.dist-info/licenses}/LICENSE.txt +0 -0
  32. {xradio-0.0.48.dist-info → xradio-0.0.50.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,8 @@ from xradio.schema.bases import (
9
9
  from xradio.schema.typing import Attr, Coord, Coordof, Data, Dataof, Name
10
10
  import numpy
11
11
 
12
+ MSV4_SCHEMA_VERSION = "4.0.-9988"
13
+
12
14
  # Dimensions
13
15
  Time = Literal["time"]
14
16
  """ Observation time dimension """
@@ -670,7 +672,7 @@ class DopplerArray:
670
672
  data: Data[ZD, numpy.float64]
671
673
 
672
674
  type: Attr[Doppler] = "doppler"
673
- """ Coordinate type. Should be ``"spectral_coord"``. """
675
+ """ Coordinate type. Should be ``"doppler"``. """
674
676
 
675
677
  units: Attr[UnitsOfDopplerShift] = ("m/s",)
676
678
  """ Units to associate with axis, [ratio]/[m/s]"""
@@ -788,11 +790,18 @@ class FlagArray:
788
790
  flagged bad if the ``FLAG`` array element is ``True``.
789
791
  """
790
792
 
793
+ # data: Data[
794
+ # Union[
795
+ # tuple[Time, BaselineId, Frequency, Polarization],
796
+ # tuple[Time, BaselineId, Frequency],
797
+ # tuple[Time, BaselineId],
798
+ # tuple[Time, AntennaName, Frequency, Polarization], # SD
799
+ # ],
800
+ # bool,
801
+ # ]
791
802
  data: Data[
792
803
  Union[
793
804
  tuple[Time, BaselineId, Frequency, Polarization],
794
- tuple[Time, BaselineId, Frequency],
795
- tuple[Time, BaselineId],
796
805
  tuple[Time, AntennaName, Frequency, Polarization], # SD
797
806
  ],
798
807
  bool,
@@ -820,12 +829,20 @@ class WeightArray:
820
829
  data: Data[
821
830
  Union[
822
831
  tuple[Time, BaselineId, Frequency, Polarization],
823
- tuple[Time, BaselineId, Frequency],
824
- tuple[Time, BaselineId],
825
832
  tuple[Time, AntennaName, Frequency, Polarization], # SD
826
833
  ],
827
834
  Union[numpy.float16, numpy.float32, numpy.float64],
828
835
  ]
836
+
837
+ # data: Data[
838
+ # Union[
839
+ # tuple[Time, BaselineId, Frequency, Polarization],
840
+ # tuple[Time, BaselineId, Frequency],
841
+ # tuple[Time, BaselineId],
842
+ # tuple[Time, AntennaName, Frequency, Polarization], # SD
843
+ # ],
844
+ # Union[numpy.float16, numpy.float32, numpy.float64],
845
+ # ]
829
846
  """Visibility weights"""
830
847
  time: Coordof[TimeCoordArray]
831
848
  baseline_id: Optional[Coordof[BaselineArray]] # Only IF
@@ -928,19 +945,79 @@ class TimeSamplingArray:
928
945
  units: Attr[UnitsSeconds] = ("s",)
929
946
 
930
947
 
948
+ # @xarray_dataarray_schema
949
+ # class FreqSamplingArray:
950
+ # """
951
+ # Model of frequency related data variables of the main dataset, such as EFFECTIV_CHANNEL_WIDTH and FREQUENCY_CENTROID.
952
+ # """
953
+
954
+ # data: Data[
955
+ # Union[
956
+ # tuple[Time, BaselineId, Frequency, Polarization],
957
+ # tuple[Time, BaselineId, Frequency],
958
+ # tuple[Time, Frequency],
959
+ # tuple[Frequency],
960
+ # ],
961
+ # float,
962
+ # ]
963
+ # """
964
+ # Data about frequency sampling, such as centroid or integration
965
+ # time. Concrete function depends on concrete data array within
966
+ # :py:class:`VisibilityXds` or :py:class:`SpectrumXds`.
967
+ # """
968
+ # frequency: Coordof[FrequencyArray]
969
+ # time: Optional[Coordof[TimeCoordArray]] = None
970
+ # baseline_id: Optional[Coordof[BaselineArray]] = None
971
+ # polarization: Optional[Coordof[PolarizationArray]] = None
972
+ # long_name: Optional[Attr[str]] = "Frequency sampling data"
973
+ # units: Attr[UnitsHertz] = ("Hz",)
974
+ # observer: Attr[AllowedSpectralCoordFrames] = "icrs"
975
+ # """
976
+ # Astropy velocity reference frames (see :external:ref:`astropy-spectralcoord`).
977
+ # Note that Astropy does not use the name
978
+ # 'topo' (telescope centric) velocity frame, rather it assumes if no velocity
979
+ # frame is given that this is the default.
980
+ # """
981
+
982
+
983
+ @xarray_dataarray_schema
984
+ class FrequencyCentroidArray:
985
+ """
986
+ Model of frequency related data variables of the main dataset, such as FREQUENCY_CENTROID.
987
+ """
988
+
989
+ data: Data[
990
+ tuple[Frequency],
991
+ float,
992
+ ]
993
+ """
994
+ Data about frequency sampling, such as centroid or integration
995
+ time. Concrete function depends on concrete data array within
996
+ :py:class:`VisibilityXds` or :py:class:`SpectrumXds`.
997
+ """
998
+ frequency: Coordof[FrequencyArray]
999
+ long_name: Optional[Attr[str]] = "Frequency sampling data"
1000
+ units: Attr[UnitsHertz] = ("Hz",)
1001
+ observer: Attr[AllowedSpectralCoordFrames] = "icrs"
1002
+ """
1003
+ Astropy velocity reference frames (see :external:ref:`astropy-spectralcoord`).
1004
+ Note that Astropy does not use the name
1005
+ 'topo' (telescope centric) velocity frame, rather it assumes if no velocity
1006
+ frame is given that this is the default.
1007
+ """
1008
+
1009
+
931
1010
  @xarray_dataarray_schema
932
- class FreqSamplingArray:
1011
+ class EffectiveChannelWidthArray:
933
1012
  """
934
- Model of frequency related data variables of the main dataset, such as EFFECTIV_CHANNEL_WIDTH and FREQUENCY_CENTROID.
1013
+ Model of frequency related data variables of the main dataset, such as EFFECTIV_CHANNEL_WIDTH.
935
1014
  """
936
1015
 
937
1016
  data: Data[
938
1017
  Union[
939
1018
  tuple[Time, BaselineId, Frequency, Polarization],
940
- tuple[Time, BaselineId, Frequency],
941
- tuple[Time, Frequency],
942
- tuple[Frequency],
943
- ],
1019
+ tuple[Time, AntennaName, Frequency, Polarization],
1020
+ ], # SD
944
1021
  float,
945
1022
  ]
946
1023
  """
@@ -963,7 +1040,7 @@ class FreqSamplingArray:
963
1040
  """
964
1041
 
965
1042
 
966
- # Define FieldAndSourceXds and FieldSourceEphemerisXds already here, as they are needed in the
1043
+ # Define FieldSourceXds and FieldSourceEphemerisXds already here, as they are needed in the
967
1044
  # definition of VisibilityArray
968
1045
  @xarray_dataset_schema
969
1046
  class FieldSourceXds:
@@ -1222,9 +1299,6 @@ class SpectrumArray:
1222
1299
  frequency: Coordof[FrequencyArray]
1223
1300
  polarization: Coordof[PolarizationArray]
1224
1301
 
1225
- field_and_source_xds: Attr[Union[FieldSourceXds, FieldSourceEphemerisXds]]
1226
- """ Field and source information. Also alows for variant where ephemeris information is included. """
1227
-
1228
1302
  long_name: Optional[Attr[str]] = "Spectrum values"
1229
1303
  """ Long-form name to use for axis. Should be ``"Spectrum values"``"""
1230
1304
  units: Attr[list[str]] = ("Jy",)
@@ -1244,9 +1318,6 @@ class VisibilityArray:
1244
1318
  polarization: Coordof[PolarizationArray]
1245
1319
  frequency: Coordof[FrequencyArray]
1246
1320
 
1247
- field_and_source_xds: Attr[Union[FieldSourceXds, FieldSourceEphemerisXds]]
1248
- """ Field and source information. Also alows for variant where ephemeris information is included. """
1249
-
1250
1321
  long_name: Optional[Attr[str]] = "Visibility values"
1251
1322
  """ Long-form name to use for axis. Should be ``"Visibility values"``"""
1252
1323
  units: Attr[list[str]] = ("Jy",)
@@ -1257,32 +1328,32 @@ class VisibilityArray:
1257
1328
  # Info dicts
1258
1329
 
1259
1330
 
1260
- @dict_schema
1261
- class PartitionInfoDict:
1262
- # spectral_window_id: missing / remove for good?
1263
- spectral_window_name: str
1264
- """ Spectral window Name """
1265
- # field_id: missing / probably remove for good?
1266
- field_name: list[str]
1267
- """ List of all field names """
1268
- polarization_setup: list[str]
1269
- """ List of polrization bases. """
1270
- scan_name: list[str]
1271
- """ List of scan names. """
1272
- source_name: list[str]
1273
- """ List of source names. """
1274
- # source_id: mising / remove for good?
1275
- intents: list[str]
1276
- """ An intent string identifies one intention of the scan, such as to calibrate or observe a
1277
- target. See :ref:`scan intents` for possible values. When converting from MSv2, the list of
1278
- intents is derived from the OBS_MODE column of MSv2 state table (every comma separated value
1279
- is taken as an intent). """
1280
- taql: Optional[str]
1281
- """ The taql query used if converted from MSv2. """
1282
- line_name: list[str]
1283
- """ Spectral line names """
1284
- antenna_name: Optional[str]
1285
- """ Name of antenna when partitioning also by antenna (single-dish). """
1331
+ # @dict_schema
1332
+ # class PartitionInfoDict:
1333
+ # # spectral_window_id: missing / remove for good?
1334
+ # spectral_window_name: str
1335
+ # """ Spectral window Name """
1336
+ # # field_id: missing / probably remove for good?
1337
+ # field_name: list[str]
1338
+ # """ List of all field names """
1339
+ # polarization_setup: list[str]
1340
+ # """ List of polrization bases. """
1341
+ # scan_name: list[str]
1342
+ # """ List of scan names. """
1343
+ # source_name: list[str]
1344
+ # """ List of source names. """
1345
+ # # source_id: mising / remove for good?
1346
+ # intents: list[str]
1347
+ # """ An intent string identifies one intention of the scan, such as to calibrate or observe a
1348
+ # target. See :ref:`scan intents` for possible values. When converting from MSv2, the list of
1349
+ # intents is derived from the OBS_MODE column of MSv2 state table (every comma separated value
1350
+ # is taken as an intent). """
1351
+ # taql: Optional[str]
1352
+ # """ The taql query used if converted from MSv2. """
1353
+ # line_name: list[str]
1354
+ # """ Spectral line names """
1355
+ # antenna_name: Optional[str]
1356
+ # """ Name of antenna when partitioning also by antenna (single-dish). """
1286
1357
 
1287
1358
 
1288
1359
  @dict_schema
@@ -1310,6 +1381,11 @@ class ObservationInfoDict:
1310
1381
  """ASDM: A reference to the Entity which contains the observing script."""
1311
1382
  observing_log: Optional[str]
1312
1383
  """ASDM: Logs of the observation during this execu- tion block."""
1384
+ intents: list[str]
1385
+ """ An intent string identifies one intention of the scan, such as to calibrate or observe a
1386
+ target. See :ref:`scan intents` for possible values. When converting from MSv2, the list of
1387
+ intents is derived from the OBS_MODE column of MSv2 state table (every comma separated value
1388
+ is taken as an intent). """
1313
1389
 
1314
1390
 
1315
1391
  @dict_schema
@@ -1322,6 +1398,41 @@ class ProcessorInfoDict:
1322
1398
  """Processor sub-type, e.g. ”GBT” or ”JIVE”."""
1323
1399
 
1324
1400
 
1401
+ @dict_schema
1402
+ class DataGroupDict:
1403
+ """Defines a group of correlated data + flag + weight + uvw variables."""
1404
+
1405
+ correlated_data: str
1406
+ """ Name of the correlated data variable, for example 'VISIBILITY' or 'VISIBILITY_MODEL'. """
1407
+ flag: str
1408
+ """ Name of the flag variable, for example 'FLAG'. """
1409
+ weight: str
1410
+ """ Name of the weight variable of the group, for example 'WEIGHT'. """
1411
+ uvw: Optional[str]
1412
+ """ Name of the UVW variable of the group, for example 'UVW'. """
1413
+ field_and_source: str
1414
+ """ Name of the field_and_source_xds, for example field_and_source_xds_base. """
1415
+ description: str
1416
+ """ More details about the data group. """
1417
+ date: str
1418
+ """ Creation date-time, in ISO 8601 format: 'YYYY-MM-DDTHH:mm:ss.SSS'. """
1419
+
1420
+
1421
+ @dict_schema
1422
+ class DataGroupsDict:
1423
+ """Dictionary of data group dictionaries."""
1424
+
1425
+ base: DataGroupDict
1426
+
1427
+
1428
+ @dict_schema
1429
+ class CreatorDict:
1430
+ software_name: str
1431
+ """ Software that created the Measurement Set (XRadio, etc.). """
1432
+ version: str
1433
+ """ Version of the software. """
1434
+
1435
+
1325
1436
  # Data Sets
1326
1437
 
1327
1438
 
@@ -1338,11 +1449,13 @@ class AntennaXds:
1338
1449
  """ Name of the station pad (relevant to arrays with moving antennas). """
1339
1450
  mount: Coord[AntennaName, str]
1340
1451
  """ Mount type of the antenna. Reserved keywords include: ”EQUATORIAL” - equatorial mount;
1341
- ”ALT-AZ - azimuth-elevation mount;
1342
- "ALT-AZ+ROTATOR" alt-az mount with feed rotator; introduced for ASKAP dishes;
1452
+ ”ALT-AZ”: azimuth-elevation mount;
1453
+ "ALT-AZ+ROTATOR": alt-az mount with feed rotator; introduced for ASKAP dishes;
1343
1454
  "ALT-AZ+NASMYTH-R": Nasmyth mount with receivers at the right-hand side of the cabin. Many high-frequency antennas used for VLBI have such a mount typel;
1344
- "ALT-AZ+NASMYTH-L:: Nasmyth mount with receivers at the left-hand side of the cabin.
1345
- ”X-Y” - x-y mount;
1455
+ "ALT-AZ+NASMYTH-L": Nasmyth mount with receivers at the left-hand side of the cabin.
1456
+ "ALT-AZ+BWG-R": alt-az mount that uses a Beam Wave Guide to bring the focus down to the pedestal. The receivers are at the right-hand side of the cabin (-R). Compared to the Nasmyth mounts there is an extra correction term because there are now two rotating mirrors. See https://arxiv.org/abs/2210.13381 for more details.
1457
+ "ALT-AZ+BWG-L": alt-az mount that uses a Beam Wave Guide, as above, but with receivers at the left-hand side of the cabin.
1458
+ ”X-Y”: x-y mount;
1346
1459
  ”SPACE-HALCA” - specific orientation model."""
1347
1460
  telescope_name: Coord[AntennaName, str]
1348
1461
  """ Useful when data is combined from mutiple arrays for example ACA + ALMA. """
@@ -1420,11 +1533,13 @@ class GainCurveXds:
1420
1533
  """ Name of the station pad (relevant to arrays with moving antennas). """
1421
1534
  mount: Coord[AntennaName, str]
1422
1535
  """ Mount type of the antenna. Reserved keywords include: ”EQUATORIAL” - equatorial mount;
1423
- ”ALT-AZ - azimuth-elevation mount;
1424
- "ALT-AZ+ROTATOR" alt-az mount with feed rotator; introduced for ASKAP dishes;
1536
+ ”ALT-AZ”: azimuth-elevation mount;
1537
+ "ALT-AZ+ROTATOR": alt-az mount with feed rotator; introduced for ASKAP dishes;
1425
1538
  "ALT-AZ+NASMYTH-R": Nasmyth mount with receivers at the right-hand side of the cabin. Many high-frequency antennas used for VLBI have such a mount typel;
1426
- "ALT-AZ+NASMYTH-L:: Nasmyth mount with receivers at the left-hand side of the cabin.
1427
- ”X-Y” - x-y mount;
1539
+ "ALT-AZ+NASMYTH-L": Nasmyth mount with receivers at the left-hand side of the cabin.
1540
+ "ALT-AZ+BWG-R": alt-az mount that uses a Beam Wave Guide to bring the focus down to the pedestal. The receivers are at the right-hand side of the cabin (-R). Compared to the Nasmyth mounts there is an extra correction term because there are now two rotating mirrors. See https://arxiv.org/abs/2210.13381 for more details.
1541
+ "ALT-AZ+BWG-L": alt-az mount that uses a Beam Wave Guide, as above, but with receivers at the left-hand side of the cabin.
1542
+ ”X-Y”: x-y mount;
1428
1543
  ”SPACE-HALCA” - specific orientation model."""
1429
1544
  telescope_name: Coord[AntennaName, str]
1430
1545
  """ Useful when data is combined from mutiple arrays for example ACA + ALMA. """
@@ -1477,12 +1592,14 @@ class PhaseCalibrationXds:
1477
1592
  """ Name of the station pad (relevant to arrays with moving antennas). """
1478
1593
  mount: Coord[AntennaName, str]
1479
1594
  """ Mount type of the antenna. Reserved keywords include: ”EQUATORIAL” - equatorial mount;
1480
- ”ALT-AZ - azimuth-elevation mount;
1481
- "ALT-AZ+ROTATOR" alt-az mount with feed rotator; introduced for ASKAP dishes;
1595
+ ”ALT-AZ”: azimuth-elevation mount;
1596
+ "ALT-AZ+ROTATOR": alt-az mount with feed rotator; introduced for ASKAP dishes;
1482
1597
  "ALT-AZ+NASMYTH-R": Nasmyth mount with receivers at the right-hand side of the cabin. Many high-frequency antennas used for VLBI have such a mount typel;
1483
- "ALT-AZ+NASMYTH-L:: Nasmyth mount with receivers at the left-hand side of the cabin.
1484
- ”X-Y” - x-y mount;
1485
- ”SPACE-HALCA” - specific orientation model."""
1598
+ "ALT-AZ+NASMYTH-L": Nasmyth mount with receivers at the left-hand side of the cabin.
1599
+ "ALT-AZ+BWG-R": alt-az mount that uses a Beam Wave Guide to bring the focus down to the pedestal. The receivers are at the right-hand side of the cabin (-R). Compared to the Nasmyth mounts there is an extra correction term because there are now two rotating mirrors. See https://arxiv.org/abs/2210.13381 for more details.
1600
+ "ALT-AZ+BWG-L": alt-az mount that uses a Beam Wave Guide, as above, but with receivers at the left-hand side of the cabin.
1601
+ ”X-Y”: x-y mount;
1602
+ ”SPACE-HALCA”: specific orientation model."""
1486
1603
  telescope_name: Coord[AntennaName, str]
1487
1604
  """ Useful when data is combined from mutiple arrays for example ACA + ALMA. """
1488
1605
  receptor_label: Coord[ReceptorLabel, str]
@@ -1778,7 +1895,7 @@ class SystemCalibrationXds:
1778
1895
  """ Midpoint of time for which this set of parameters is accurate. Labeled 'time' when interpolating to main time axis """
1779
1896
  time_system_cal: Optional[Coordof[TimeSystemCalCoordArray]] = None
1780
1897
  """ Midpoint of time for which this set of parameters is accurate. Labeled 'time_system_cal' when not interpolating to main time axis """
1781
- frequency: Optional[Coordof[FrequencySystemCalArray]] = None
1898
+ frequency: Optional[Coordof[FrequencyArray]] = None
1782
1899
  """ """
1783
1900
  frequency_system_cal: Optional[Coord[FrequencySystemCal, int]] = None
1784
1901
  """TODO: What is this?"""
@@ -1915,6 +2032,8 @@ class VisibilityXds:
1915
2032
  """
1916
2033
  Labels for polarization types, e.g. ``['XX','XY','YX','YY']``, ``['RR','RL','LR','LL']``.
1917
2034
  """
2035
+ field_name: Coordof[Coord[Time, str]]
2036
+ """Field name."""
1918
2037
 
1919
2038
  # --- Required data variables ---
1920
2039
 
@@ -1927,17 +2046,21 @@ class VisibilityXds:
1927
2046
  """Antenna name for 2nd antenna in baseline. Maps to ``attrs['antenna_xds'].antenna_name``"""
1928
2047
 
1929
2048
  # --- Required Attributes ---
1930
- partition_info: Attr[PartitionInfoDict]
2049
+ # partition_info: Attr[PartitionInfoDict]
1931
2050
  observation_info: Attr[ObservationInfoDict]
1932
2051
  processor_info: Attr[ProcessorInfoDict]
1933
- antenna_xds: Attr[AntennaXds]
2052
+
2053
+ data_groups: Attr[DataGroupsDict]
2054
+ """ Defines groups of correlated data + flag + weight + uvw variables. """
1934
2055
 
1935
2056
  schema_version: Attr[str]
1936
- """Semantic version of xradio data format"""
2057
+ """Semantic version of MSv4 data format."""
2058
+ creator: Attr[CreatorDict]
2059
+ """Creator information (software, version)."""
1937
2060
  creation_date: Attr[str]
1938
- """Date visibility dataset was created . Format: YYYY-MM-DDTHH:mm:ss.SSS (ISO 8601)"""
2061
+ """Date visibility dataset was created. Format: YYYY-MM-DDTHH:mm:ss.SSS (ISO 8601)"""
1939
2062
 
1940
- type: Attr[Literal["visibility"]] = "visibility"
2063
+ type: Attr[Literal["visibility", "radiometer"]] = "visibility"
1941
2064
  """
1942
2065
  Dataset type
1943
2066
  """
@@ -1953,7 +2076,7 @@ class VisibilityXds:
1953
2076
  uvw_label: Optional[Coordof[UvwLabelArray]] = None
1954
2077
  """ u,v,w """
1955
2078
  scan_name: Optional[Coord[Time, str]] = None
1956
- """Arbitary scan name to identify data taken in the same logical scan."""
2079
+ """Scan name to identify data taken in the same logical scan."""
1957
2080
 
1958
2081
  # --- Optional data variables / arrays ---
1959
2082
 
@@ -1967,7 +2090,6 @@ class VisibilityXds:
1967
2090
  Data[
1968
2091
  Union[
1969
2092
  tuple[Time, BaselineId],
1970
- tuple[Time, BaselineId, Frequency],
1971
2093
  tuple[Time, BaselineId, Frequency, Polarization],
1972
2094
  ],
1973
2095
  QuantityInSecondsArray,
@@ -1985,21 +2107,12 @@ class VisibilityXds:
1985
2107
  """
1986
2108
  TIME_CENTROID_EXTRA_PRECISION: Optional[Dataof[TimeSamplingArray]] = None
1987
2109
  """Additional precision for ``TIME_CENTROID``"""
1988
- EFFECTIVE_CHANNEL_WIDTH: Optional[Dataof[FreqSamplingArray]] = None
2110
+ EFFECTIVE_CHANNEL_WIDTH: Optional[Dataof[EffectiveChannelWidthArray]] = None
1989
2111
  """The channel bandwidth that includes the effects of missing data."""
1990
- FREQUENCY_CENTROID: Optional[Dataof[FreqSamplingArray]] = None
2112
+ FREQUENCY_CENTROID: Optional[Dataof[FrequencyCentroidArray]] = None
1991
2113
  """Includes the effects of missing data unlike ``frequency``."""
1992
2114
 
1993
2115
  # --- Optional Attributes ---
1994
- pointing_xds: Optional[Attr[PointingXds]] = None
1995
- system_calibration_xds: Optional[Attr[SystemCalibrationXds]] = None
1996
- gain_curve_xds: Optional[Attr[GainCurveXds]] = None
1997
- phase_calibration_xds: Optional[Attr[PhaseCalibrationXds]] = None
1998
- weather_xds: Optional[Attr[WeatherXds]] = None
1999
- phased_array_xds: Optional[Attr[PhasedArrayXds]] = None
2000
-
2001
- xradio_version: Optional[Attr[str]] = None
2002
- """ Version of XRADIO used if converted from MSv2. """
2003
2116
 
2004
2117
 
2005
2118
  @xarray_dataset_schema
@@ -2020,21 +2133,27 @@ class SpectrumXds:
2020
2133
  """
2021
2134
  Labels for polarization types, e.g. ``['XX','XY','YX','YY']``, ``['RR','RL','LR','LL']``.
2022
2135
  """
2136
+ field_name: Coordof[Coord[Time, str]]
2137
+ """Field name."""
2023
2138
 
2024
2139
  # --- Required data variables ---
2025
2140
  SPECTRUM: Dataof[SpectrumArray]
2026
2141
  """Single dish data, either simulated or measured by an antenna."""
2027
2142
 
2028
2143
  # --- Required Attributes ---
2029
- partition_info: Attr[PartitionInfoDict]
2144
+ # partition_info: Attr[PartitionInfoDict]
2030
2145
  observation_info: Attr[ObservationInfoDict]
2031
2146
  processor_info: Attr[ProcessorInfoDict]
2032
- antenna_xds: Attr[AntennaXds]
2147
+
2148
+ data_groups: Attr[DataGroupsDict]
2149
+ """ Defines groups of correlated data + flag + weight variables. """
2033
2150
 
2034
2151
  schema_version: Attr[str]
2035
- """Semantic version of xradio data format"""
2152
+ """Semantic version of MSv4 data format."""
2153
+ creator: Attr[CreatorDict]
2154
+ """Creator information (software, version)."""
2036
2155
  creation_date: Attr[str]
2037
- """Date MSv4 was created . Format: YYYY-MM-DDTHH:mm:ss.SSS (ISO 8601)"""
2156
+ """Date spectrum dataset was created . Format: YYYY-MM-DDTHH:mm:ss.SSS (ISO 8601)"""
2038
2157
 
2039
2158
  type: Attr[Literal["spectrum"]] = "spectrum"
2040
2159
  """
@@ -2062,7 +2181,6 @@ class SpectrumXds:
2062
2181
  Data[
2063
2182
  Union[
2064
2183
  tuple[Time, AntennaName],
2065
- tuple[Time, AntennaName, Frequency],
2066
2184
  tuple[Time, AntennaName, Frequency, Polarization],
2067
2185
  ],
2068
2186
  QuantityInSecondsArray,
@@ -2080,18 +2198,7 @@ class SpectrumXds:
2080
2198
  """
2081
2199
  TIME_CENTROID_EXTRA_PRECISION: Optional[Dataof[TimeSamplingArray]] = None
2082
2200
  """Additional precision for ``TIME_CENTROID``"""
2083
- EFFECTIVE_CHANNEL_WIDTH: Optional[Dataof[FreqSamplingArray]] = None
2201
+ EFFECTIVE_CHANNEL_WIDTH: Optional[Dataof[EffectiveChannelWidthArray]] = None
2084
2202
  """The channel bandwidth that includes the effects of missing data."""
2085
- FREQUENCY_CENTROID: Optional[Dataof[FreqSamplingArray]] = None
2203
+ FREQUENCY_CENTROID: Optional[Dataof[FrequencyCentroidArray]] = None
2086
2204
  """Includes the effects of missing data unlike ``frequency``."""
2087
-
2088
- # --- Optional Attributes ---
2089
- pointing_xds: Optional[Attr[PointingXds]] = None
2090
- system_calibration_xds: Optional[Attr[SystemCalibrationXds]] = None
2091
- gain_curve_xds: Optional[Attr[GainCurveXds]] = None
2092
- phase_calibration_xds: Optional[Attr[PhaseCalibrationXds]] = None
2093
- weather_xds: Optional[Attr[WeatherXds]] = None
2094
- phased_array_xds: Optional[Attr[PhasedArrayXds]] = None
2095
-
2096
- xradio_version: Optional[Attr[str]] = None
2097
- """ Version of XRADIO used if converted from MSv2. """
xradio/schema/bases.py CHANGED
@@ -397,11 +397,15 @@ def xarray_dataset_schema(cls):
397
397
  cls = dataclasses.dataclass(cls, init=True, repr=False, eq=False, frozen=True)
398
398
 
399
399
  # Make schema
400
- cls.__xradio_dataset_schema = dataclass.xarray_dataclass_to_dataset_schema(cls)
400
+ schema = dataclass.xarray_dataclass_to_dataset_schema(cls)
401
+ cls.__xradio_dataset_schema = schema
401
402
 
402
403
  # Replace __new__
403
404
  cls.__new__ = _dataset_new
404
405
 
406
+ # Register type
407
+ check.register_dataset_type(schema)
408
+
405
409
  return cls
406
410
 
407
411
 
xradio/schema/check.py CHANGED
@@ -2,6 +2,7 @@ import dataclasses
2
2
  import typing
3
3
  import inspect
4
4
  import functools
5
+ import warnings
5
6
 
6
7
  import xarray
7
8
  import numpy
@@ -173,7 +174,9 @@ def check_array(
173
174
 
174
175
 
175
176
  def check_dataset(
176
- dataset: xarray.Dataset, schema: typing.Union[type, metamodel.DatasetSchema]
177
+ dataset: xarray.Dataset,
178
+ schema: typing.Union[type, metamodel.DatasetSchema],
179
+ allow_superflous_dims: typing.Set[str] = frozenset(),
177
180
  ) -> SchemaIssues:
178
181
  """
179
182
  Check whether an xarray DataArray conforms to a schema
@@ -196,7 +199,12 @@ def check_dataset(
196
199
  )
197
200
 
198
201
  # Check dimensions. Order does not matter on datasets
199
- issues = check_dimensions(dataset.dims, schema.dimensions, check_order=False)
202
+ issues = check_dimensions(
203
+ dataset.dims,
204
+ schema.dimensions,
205
+ check_order=False,
206
+ allow_superflous=allow_superflous_dims,
207
+ )
200
208
 
201
209
  # Check attributes
202
210
  issues += check_attributes(dataset.attrs, schema.attributes)
@@ -211,7 +219,10 @@ def check_dataset(
211
219
 
212
220
 
213
221
  def check_dimensions(
214
- dims: [str], expected: [[str]], check_order: bool = True
222
+ dims: [str],
223
+ expected: [[str]],
224
+ check_order: bool = True,
225
+ allow_superflous: typing.Set[str] = frozenset(),
215
226
  ) -> SchemaIssues:
216
227
  """
217
228
  Check whether a dimension list conforms to a schema
@@ -230,7 +241,7 @@ def check_dimensions(
230
241
  exp_dims_set = set(exp_dims)
231
242
 
232
243
  # No match? Continue, but take note of best match
233
- if exp_dims_set != dims_set:
244
+ if exp_dims_set - allow_superflous != dims_set - allow_superflous:
234
245
  diff = len(dims_set.symmetric_difference(exp_dims_set))
235
246
  if best is None or diff < best_diff:
236
247
  best = exp_dims_set
@@ -253,7 +264,7 @@ def check_dimensions(
253
264
  )
254
265
 
255
266
  # Dimensionality not supported - try to give a helpful suggestion
256
- hint_remove = [f"'{hint}'" for hint in dims_set - best]
267
+ hint_remove = [f"'{hint}'" for hint in (dims_set - best) - allow_superflous]
257
268
  hint_add = [f"'{hint}'" for hint in best - dims_set]
258
269
  if hint_remove and hint_add:
259
270
  message = f"Unexpected coordinates, replace {','.join(hint_remove)} by {','.join(hint_add)}?"
@@ -560,6 +571,87 @@ def _check_value_union(val, ann):
560
571
  return args_issues
561
572
 
562
573
 
574
+ _DATASET_TYPES = {}
575
+
576
+
577
+ def register_dataset_type(schema: metamodel.DatasetSchema):
578
+ """
579
+ Registers the given schema for usage with :py:meth:`check_datatree`
580
+
581
+ This looks for a ``type`` attribute in the dataset schema, which
582
+ must have a :py:class:`typing.Literal` type annotation specifying
583
+ the type name of the dataset
584
+
585
+ :param schema: Schema to register
586
+ """
587
+
588
+ # Find type attribute
589
+ for attr in schema.attributes:
590
+ if attr.name != "type":
591
+ continue
592
+
593
+ # Type should be a kind of literal
594
+ if typing.get_origin(attr.typ) is not typing.Literal:
595
+ warnings.warn(
596
+ f"In dataset schema {schema.schema_name}:"
597
+ 'Attribute "type" should be a literal!'
598
+ )
599
+ continue
600
+
601
+ # Register type names
602
+ for typ in typing.get_args(attr.typ):
603
+ _DATASET_TYPES[typ] = schema
604
+
605
+
606
+ def check_datatree(
607
+ datatree: xarray.DataTree,
608
+ ):
609
+ """
610
+ Check datatree for schema conformance
611
+
612
+ This is the case if all nodes containing data
613
+
614
+ This looks for a ``type`` attribute in the dataset schema, which
615
+ must have a :py:class:`typing.Literal` type annotation specifying
616
+ the type name of the dataset
617
+
618
+ :param schema: Schema to register
619
+ """
620
+
621
+ # Loop through all groups in datatree
622
+ issues = SchemaIssues()
623
+ for xds_name in datatree.groups:
624
+
625
+ # Ignore any leaf without data
626
+ node = datatree[xds_name]
627
+ if not node.has_data:
628
+ continue
629
+
630
+ # Look up schema
631
+ schema = _DATASET_TYPES.get(node.attrs.get("type"))
632
+ if schema is None:
633
+ issues.add(
634
+ SchemaIssue(
635
+ [("", xds_name)],
636
+ message="Unknown dataset type!",
637
+ found=typ,
638
+ expected=list(schemas.keys()),
639
+ )
640
+ )
641
+ continue
642
+
643
+ # Determine dimensions inherited from parent
644
+ # (they might show up as "superflous" for the child schema)
645
+ parent_dims = frozenset()
646
+ if node.parent is not None:
647
+ parent_dims = set(node.parent.dims)
648
+
649
+ # Check schema
650
+ issues += check_dataset(node.dataset, schema, parent_dims).at_path("", xds_name)
651
+
652
+ return issues
653
+
654
+
563
655
  def schema_checked(fn, check_parameters: bool = True, check_return: bool = True):
564
656
  """
565
657
  Function decorator to check parameters and return value for