xradio 0.0.33__py3-none-any.whl → 0.0.36__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.
@@ -5,9 +5,7 @@ from typing import Tuple, Union
5
5
  import numpy as np
6
6
  import xarray as xr
7
7
 
8
- from xradio.vis._vis_utils._ms.msv2_to_msv4_meta import (
9
- column_description_casacore_to_msv4_measure,
10
- )
8
+ from xradio._utils.schema import column_description_casacore_to_msv4_measure
11
9
  from xradio.vis._vis_utils._ms.msv4_sub_xdss import interpolate_to_time
12
10
  from xradio.vis._vis_utils._ms.subtables import subt_rename_ids
13
11
  from xradio.vis._vis_utils._ms._tables.read import (
@@ -46,6 +44,10 @@ def create_field_and_source_xds(
46
44
  The field ids to select.
47
45
  spectral_window_id : int
48
46
  The ID of the spectral window.
47
+ field_times: list
48
+ Time data for field. It is the same as the time axis in the main MSv4 dataset and is used if more than one field is present.
49
+ is_single_dish: bool
50
+ whether the main xds has single-dish (SPECTRUM) data
49
51
  time_min_max : Tuple[np.float64, np.float46]
50
52
  Min / max times to constrain loading (usually to the time range relevant to an MSv4)
51
53
  ephemeris_interp_time : Union[xr.DataArray, None]
@@ -62,7 +64,7 @@ def create_field_and_source_xds(
62
64
  field_and_source_xds = xr.Dataset()
63
65
 
64
66
  field_and_source_xds, ephemeris_path, ephemeris_table_name, source_id = (
65
- create_field_info_and_check_ephemeris(
67
+ extract_field_info_and_check_ephemeris(
66
68
  field_and_source_xds, in_file, field_id, field_times, is_single_dish
67
69
  )
68
70
  )
@@ -77,7 +79,7 @@ def create_field_and_source_xds(
77
79
  ephemeris_interp_time,
78
80
  )
79
81
 
80
- field_and_source_xds = extract_source_info(
82
+ field_and_source_xds, num_lines = extract_source_info(
81
83
  field_and_source_xds, in_file, source_id, spectral_window_id
82
84
  )
83
85
 
@@ -95,7 +97,7 @@ def create_field_and_source_xds(
95
97
  if np.unique(field_and_source_xds[center_dv], axis=0).shape[0] == 1:
96
98
  field_and_source_xds = field_and_source_xds.isel(time=0).drop_vars("time")
97
99
 
98
- return field_and_source_xds, source_id
100
+ return field_and_source_xds, source_id, num_lines
99
101
 
100
102
 
101
103
  def extract_ephemeris_info(
@@ -119,7 +121,7 @@ def extract_ephemeris_info(
119
121
  The name of the ephemeris table.
120
122
  time_min_max : Tuple[np.float46, np.float64]
121
123
  Min / max times to constrain loading (usually to the time range relevant to an MSv4)
122
- ephemeris_interp_time : Union[xr.DataArray, None]
124
+ interp_time : Union[xr.DataArray, None]
123
125
  Time axis to interpolate the data vars to (usually main MSv4 time)
124
126
 
125
127
  Returns:
@@ -172,18 +174,17 @@ def extract_ephemeris_info(
172
174
  else:
173
175
  unit_keyword = "QuantumUnits"
174
176
 
177
+ # We are using the "time_ephemeris_axis" label because it might not match the optional time axis of the source and field info. If ephemeris_interpolate=True then rename it to time.
175
178
  coords = {
176
179
  "ellipsoid_pos_label": ["lon", "lat", "dist"],
177
- "ephem_time": ephemeris_xds[
178
- "time"
179
- ].data, # We are using the "ephem_time" label because it might not match the optional time axis of the source and field info. If ephemeris_interpolate=True then rename it to time.
180
+ "time_ephemeris_axis": ephemeris_xds["time"].data,
180
181
  "sky_pos_label": ["ra", "dec", "dist"],
181
182
  }
182
183
 
183
184
  temp_xds = xr.Dataset()
184
185
 
185
- # Add mandatory data: SOURCE_POSITION
186
- temp_xds["SOURCE_POSITION"] = xr.DataArray(
186
+ # Add mandatory data: SOURCE_LOCATION (POSITION / sky_pos_label)
187
+ temp_xds["SOURCE_LOCATION"] = xr.DataArray(
187
188
  np.column_stack(
188
189
  (
189
190
  ephemeris_xds["RA"].data,
@@ -191,7 +192,7 @@ def extract_ephemeris_info(
191
192
  ephemeris_xds["Rho"].data,
192
193
  )
193
194
  ),
194
- dims=["ephem_time", "sky_pos_label"],
195
+ dims=["time_ephemeris_axis", "sky_pos_label"],
195
196
  )
196
197
  # Have to use cast_to_str because the ephemeris table units are not consistently in a list or a string.
197
198
  sky_coord_units = [
@@ -199,13 +200,13 @@ def extract_ephemeris_info(
199
200
  cast_to_str(ephemris_column_description["DEC"]["keywords"][unit_keyword]),
200
201
  cast_to_str(ephemris_column_description["Rho"]["keywords"][unit_keyword]),
201
202
  ]
202
- temp_xds["SOURCE_POSITION"].attrs.update(
203
+ temp_xds["SOURCE_LOCATION"].attrs.update(
203
204
  {"type": "sky_coord", "frame": sky_coord_frame, "units": sky_coord_units}
204
205
  )
205
206
 
206
207
  # Add mandatory data: SOURCE_RADIAL_VELOCITY
207
208
  temp_xds["SOURCE_RADIAL_VELOCITY"] = xr.DataArray(
208
- ephemeris_xds["RadVel"].data, dims=["ephem_time"]
209
+ ephemeris_xds["RadVel"].data, dims=["time_ephemeris_axis"]
209
210
  )
210
211
  temp_xds["SOURCE_RADIAL_VELOCITY"].attrs.update(
211
212
  {
@@ -241,7 +242,7 @@ def extract_ephemeris_info(
241
242
  # Add optional data NORTH_POLE_POSITION_ANGLE and NORTH_POLE_ANGULAR_DISTANCE
242
243
  if "NP_ang" in ephemeris_xds.data_vars:
243
244
  temp_xds["NORTH_POLE_POSITION_ANGLE"] = xr.DataArray(
244
- ephemeris_xds["NP_ang"].data, dims=["ephem_time"]
245
+ ephemeris_xds["NP_ang"].data, dims=["time_ephemeris_axis"]
245
246
  )
246
247
  temp_xds["NORTH_POLE_POSITION_ANGLE"].attrs.update(
247
248
  {
@@ -256,7 +257,7 @@ def extract_ephemeris_info(
256
257
 
257
258
  if "NP_dist" in ephemeris_xds.data_vars:
258
259
  temp_xds["NORTH_POLE_ANGULAR_DISTANCE"] = xr.DataArray(
259
- ephemeris_xds["NP_dist"].data, dims=["ephem_time"]
260
+ ephemeris_xds["NP_dist"].data, dims=["time_ephemeris_axis"]
260
261
  )
261
262
  temp_xds["NORTH_POLE_ANGULAR_DISTANCE"].attrs.update(
262
263
  {
@@ -286,7 +287,7 @@ def extract_ephemeris_info(
286
287
  np.zeros(ephemeris_xds[key_lon].shape),
287
288
  )
288
289
  ),
289
- dims=["ephem_time", "ellipsoid_pos_label"],
290
+ dims=["time_ephemeris_axis", "ellipsoid_pos_label"],
290
291
  )
291
292
 
292
293
  temp_xds["SUB_OBSERVER_POSITION"].attrs.update(
@@ -316,7 +317,7 @@ def extract_ephemeris_info(
316
317
  ephemeris_xds["r"].data,
317
318
  )
318
319
  ),
319
- dims=["ephem_time", "ellipsoid_pos_label"],
320
+ dims=["time_ephemeris_axis", "ellipsoid_pos_label"],
320
321
  )
321
322
  temp_xds["SUB_SOLAR_POSITION"].attrs.update(
322
323
  {
@@ -341,7 +342,7 @@ def extract_ephemeris_info(
341
342
  # Add optional data: HELIOCENTRIC_RADIAL_VELOCITY
342
343
  if "rdot" in ephemeris_xds.data_vars:
343
344
  temp_xds["HELIOCENTRIC_RADIAL_VELOCITY"] = xr.DataArray(
344
- ephemeris_xds["rdot"].data, dims=["ephem_time"]
345
+ ephemeris_xds["rdot"].data, dims=["time_ephemeris_axis"]
345
346
  )
346
347
  temp_xds["HELIOCENTRIC_RADIAL_VELOCITY"].attrs.update(
347
348
  {
@@ -357,7 +358,7 @@ def extract_ephemeris_info(
357
358
  # Add optional data: OBSERVER_PHASE_ANGLE
358
359
  if "phang" in ephemeris_xds.data_vars:
359
360
  temp_xds["OBSERVER_PHASE_ANGLE"] = xr.DataArray(
360
- ephemeris_xds["phang"].data, dims=["ephem_time"]
361
+ ephemeris_xds["phang"].data, dims=["time_ephemeris_axis"]
361
362
  )
362
363
  temp_xds["OBSERVER_PHASE_ANGLE"].attrs.update(
363
364
  {
@@ -371,24 +372,33 @@ def extract_ephemeris_info(
371
372
  )
372
373
 
373
374
  temp_xds = temp_xds.assign_coords(coords)
374
- temp_xds["ephem_time"].attrs.update(
375
- {"type": "time", "units": ["s"], "scale": "UTC", "format": "UNIX"}
376
- )
375
+ time_coord_attrs = {
376
+ "type": "time",
377
+ "units": ["s"],
378
+ "scale": "UTC",
379
+ "format": "UNIX",
380
+ }
381
+ temp_xds["time_ephemeris_axis"].attrs.update(time_coord_attrs)
377
382
 
378
383
  # Convert to si units and interpolate if ephemeris_interpolate=True:
379
384
  temp_xds = convert_to_si_units(temp_xds)
380
385
  temp_xds = interpolate_to_time(
381
- temp_xds, interp_time, "field_and_source_xds", time_name="ephem_time"
386
+ temp_xds, interp_time, "field_and_source_xds", time_name="time_ephemeris_axis"
382
387
  )
383
388
 
384
- # If we interpolate rename the ephem_time axis to time.
389
+ # If we interpolate rename the time_ephemeris_axis axis to time.
385
390
  if interp_time is not None:
386
- temp_xds = temp_xds.swap_dims({"ephem_time": "time"}).drop_vars("ephem_time")
391
+ time_coord = {"time": ("time_ephemeris_axis", interp_time.data)}
392
+ temp_xds = temp_xds.assign_coords(time_coord)
393
+ temp_xds.coords["time"].attrs.update(time_coord_attrs)
394
+ temp_xds = temp_xds.swap_dims({"time_ephemeris_axis": "time"}).drop_vars(
395
+ "time_ephemeris_axis"
396
+ )
387
397
 
388
398
  xds = xr.merge([xds, temp_xds])
389
399
 
390
- # Add the SOURCE_POSITION to the FIELD_PHASE_CENTER or FIELD_REFERENCE_CENTER. Ephemeris obs: When loaded from the MSv2 field table the FIELD_REFERENCE_CENTER or FIELD_PHASE_CENTER only contain an offset from the SOURCE_POSITION.
391
- # We also need to add a distance dimension to the FIELD_PHASE_CENTER or FIELD_REFERENCE_CENTER to match the SOURCE_POSITION.
400
+ # Add the SOURCE_LOCATION to the FIELD_PHASE_CENTER or FIELD_REFERENCE_CENTER. Ephemeris obs: When loaded from the MSv2 field table the FIELD_REFERENCE_CENTER or FIELD_PHASE_CENTER only contain an offset from the SOURCE_LOCATION.
401
+ # We also need to add a distance dimension to the FIELD_PHASE_CENTER or FIELD_REFERENCE_CENTER to match the SOURCE_LOCATION.
392
402
  # FIELD_PHASE_CENTER is used for interferometer data and FIELD_REFERENCE_CENTER is used for single dish data.
393
403
  if is_single_dish:
394
404
  center_dv = "FIELD_REFERENCE_CENTER"
@@ -405,20 +415,20 @@ def extract_ephemeris_info(
405
415
  np.column_stack(
406
416
  (xds[center_dv].values, np.zeros(xds[center_dv].values.shape[0]))
407
417
  ),
408
- xds["SOURCE_POSITION"].values,
418
+ xds["SOURCE_LOCATION"].values,
409
419
  ),
410
- dims=[xds["SOURCE_POSITION"].dims[0], "sky_pos_label"],
420
+ dims=[xds["SOURCE_LOCATION"].dims[0], "sky_pos_label"],
411
421
  )
412
422
  else:
413
423
  xds[center_dv] = xr.DataArray(
414
424
  add_position_offsets(
415
425
  np.append(xds[center_dv].values, 0),
416
- xds["SOURCE_POSITION"].values,
426
+ xds["SOURCE_LOCATION"].values,
417
427
  ),
418
- dims=[xds["SOURCE_POSITION"].dims[0], "sky_pos_label"],
428
+ dims=[xds["SOURCE_LOCATION"].dims[0], "sky_pos_label"],
419
429
  )
420
430
 
421
- xds[center_dv].attrs.update(xds["SOURCE_POSITION"].attrs)
431
+ xds[center_dv].attrs.update(xds["SOURCE_LOCATION"].attrs)
422
432
 
423
433
  return xds
424
434
 
@@ -442,6 +452,8 @@ def extract_source_info(xds, path, source_id, spectral_window_id):
442
452
  -------
443
453
  xds : xr.Dataset
444
454
  The xarray dataset with the added source information.
455
+ num_lines : int
456
+ Sum of num_lines for all unique sources extracted.
445
457
  """
446
458
  coords = {}
447
459
  is_ephemeris = xds.attrs[
@@ -455,7 +467,14 @@ def extract_source_info(xds, path, source_id, spectral_window_id):
455
467
  xds = xds.assign_coords(
456
468
  {"source_name": "Unknown"}
457
469
  ) # Need to add this for ps.summary() to work.
458
- return xds
470
+ return xds, 0
471
+
472
+ if not os.path.isdir(os.path.join(path, "SOURCE")):
473
+ logger.warning(
474
+ f"Could not find SOURCE table for source_id {source_id}. Source information will not be included in the field_and_source_xds."
475
+ )
476
+ xds = xds.assign_coords({"source_name": "Unknown"})
477
+ return xds, 0
459
478
 
460
479
  unique_source_id = unique_1d(source_id)
461
480
  taql_where = f"where (SOURCE_ID IN [{','.join(map(str, unique_source_id))}]) AND (SPECTRAL_WINDOW_ID = {spectral_window_id})"
@@ -474,7 +493,7 @@ def extract_source_info(xds, path, source_id, spectral_window_id):
474
493
  xds = xds.assign_coords(
475
494
  {"source_name": "Unknown"}
476
495
  ) # Need to add this for ps.summary() to work.
477
- return xds
496
+ return xds, 0
478
497
 
479
498
  assert (
480
499
  len(source_xds.SPECTRAL_WINDOW_ID) == 1
@@ -530,8 +549,12 @@ def extract_source_info(xds, path, source_id, spectral_window_id):
530
549
  else:
531
550
  direction_var = source_xds[direction_msv2_col]
532
551
 
533
- xds["SOURCE_DIRECTION"] = xr.DataArray(direction_var.data, dims=direction_dims)
534
- xds["SOURCE_DIRECTION"].attrs.update(msv4_measure)
552
+ # SOURCE_LOCATION (DIRECTION / sky_dir_label)
553
+ xds["SOURCE_LOCATION"] = xr.DataArray(direction_var.data, dims=direction_dims)
554
+ location_msv4_measure = column_description_casacore_to_msv4_measure(
555
+ source_column_description[direction_msv2_col]
556
+ )
557
+ xds["SOURCE_LOCATION"].attrs.update(location_msv4_measure)
535
558
 
536
559
  # Do we have line data:
537
560
  if source_xds["NUM_LINES"].data.ndim == 0:
@@ -557,12 +580,19 @@ def extract_source_info(xds, path, source_id, spectral_window_id):
557
580
  transition_var_data, max(transition_var_data.shape, vars_shape)
558
581
  )
559
582
 
583
+ line_label_data = np.arange(coords_lines_data.shape[-1]).astype(str)
560
584
  if len(source_id) == 1:
561
- coords_lines = {"line_name": coords_lines_data}
585
+ coords_lines = {
586
+ "line_name": ("line_label", coords_lines_data),
587
+ "line_label": line_label_data,
588
+ }
562
589
  xds = xds.assign_coords(coords_lines)
563
590
  line_dims = ["line_label"]
564
591
  else:
565
- coords_lines = {"line_name": (("time", "line_label"), coords_lines_data)}
592
+ coords_lines = {
593
+ "line_name": (("time", "line_label"), coords_lines_data),
594
+ "line_label": line_label_data,
595
+ }
566
596
  xds = xds.assign_coords(coords_lines)
567
597
  line_dims = ["time", "line_label"]
568
598
 
@@ -594,10 +624,13 @@ def extract_source_info(xds, path, source_id, spectral_window_id):
594
624
  pass
595
625
 
596
626
  xds = xds.assign_coords(coords)
597
- return xds
598
627
 
628
+ _, unique_source_ids_indices = np.unique(source_xds.SOURCE_ID, return_index=True)
599
629
 
600
- def create_field_info_and_check_ephemeris(
630
+ return xds, np.sum(num_lines[unique_source_ids_indices])
631
+
632
+
633
+ def extract_field_info_and_check_ephemeris(
601
634
  field_and_source_xds, in_file, field_id, field_times, is_single_dish
602
635
  ):
603
636
  """
@@ -738,4 +771,13 @@ def create_field_info_and_check_ephemeris(
738
771
  field_and_source_xds[msv4_name].attrs["type"] = field_measures_type
739
772
 
740
773
  field_and_source_xds = field_and_source_xds.assign_coords(coords)
774
+ if "time" in field_and_source_xds:
775
+ time_column_description = field_xds.attrs["other"]["msv2"]["ctds_attrs"][
776
+ "column_descriptions"
777
+ ]["TIME"]
778
+ time_msv4_measure = column_description_casacore_to_msv4_measure(
779
+ time_column_description
780
+ )
781
+ field_and_source_xds.coords["time"].attrs.update(time_msv4_measure)
782
+
741
783
  return field_and_source_xds, ephemeris_path, ephemeris_table_name, source_id
@@ -1,4 +1,5 @@
1
1
  import graphviper.utils.logger as logger
2
+ from xradio._utils.schema import column_description_casacore_to_msv4_measure
2
3
 
3
4
  col_to_data_variable_names = {
4
5
  "FLOAT_DATA": "SPECTRUM",
@@ -30,111 +31,6 @@ col_to_coord_names = {
30
31
  "ANTENNA2": "baseline_ant2_id",
31
32
  }
32
33
 
33
- # Map casacore measures to astropy
34
- casacore_to_msv4_measure_type = {
35
- "quanta": {
36
- "type": "quantity",
37
- },
38
- "direction": {"type": "sky_coord", "Ref": "frame", "Ref_map": {"J2000": "fk5"}},
39
- "epoch": {"type": "time", "Ref": "scale", "Ref_map": {"UTC": "utc"}},
40
- "frequency": {
41
- "type": "spectral_coord",
42
- "Ref": "frame",
43
- "Ref_map": {
44
- "REST": "REST",
45
- "LSRK": "LSRK",
46
- "LSRD": "LSRD",
47
- "BARY": "BARY",
48
- "GEO": "GEO",
49
- "TOPO": "TOPO",
50
- "GALACTO": "GALACTO",
51
- "LGROUP": "LGROUP",
52
- "CMB": "CMB",
53
- "Undefined": "Undefined",
54
- },
55
- },
56
- "position": {
57
- "type": "earth_location",
58
- "Ref": "ellipsoid",
59
- "Ref_map": {"ITRF": "GRS80"},
60
- },
61
- "uvw": {"type": "uvw", "Ref": "frame", "Ref_map": {"ITRF": "GRS80"}},
62
- "radialvelocity": {"type": "quantity"},
63
- }
64
-
65
- casa_frequency_frames = [
66
- "REST",
67
- "LSRK",
68
- "LSRD",
69
- "BARY",
70
- "GEO",
71
- "TOPO",
72
- "GALACTO",
73
- "LGROUP",
74
- "CMB",
75
- "Undefined",
76
- ]
77
-
78
- casa_frequency_frames_codes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 64]
79
-
80
-
81
- def column_description_casacore_to_msv4_measure(
82
- casacore_column_description, ref_code=None, time_format="UNIX"
83
- ):
84
- import numpy as np
85
-
86
- msv4_measure = {}
87
- if "MEASINFO" in casacore_column_description["keywords"]:
88
- measinfo = casacore_column_description["keywords"]["MEASINFO"]
89
-
90
- # Get conversion information
91
- msv4_measure_conversion = casacore_to_msv4_measure_type[measinfo["type"]]
92
-
93
- # Convert type, copy unit
94
- msv4_measure["type"] = msv4_measure_conversion["type"]
95
- msv4_measure["units"] = list(
96
- casacore_column_description["keywords"]["QuantumUnits"]
97
- )
98
-
99
- # Reference frame to convert?
100
- if "Ref" in msv4_measure_conversion:
101
- # Find reference frame
102
- if "TabRefCodes" in measinfo:
103
- ref_index = np.where(measinfo["TabRefCodes"] == ref_code)[0][0]
104
- casa_ref = measinfo["TabRefTypes"][ref_index]
105
- elif "Ref" in measinfo:
106
- casa_ref = measinfo["Ref"]
107
- elif measinfo["type"] == "frequency":
108
- # Some MSv2 don't have the "TabRefCodes".
109
- ref_index = np.where(casa_frequency_frames_codes == ref_code)[0][0]
110
- casa_ref = casa_frequency_frames[ref_index]
111
- else:
112
- logger.debug(
113
- f"Could not determine {measinfo['type']} measure "
114
- "reference frame!"
115
- )
116
-
117
- # Convert into MSv4 representation of reference frame, warn if unknown
118
- if casa_ref in msv4_measure_conversion.get("Ref_map", {}):
119
- casa_ref = msv4_measure_conversion["Ref_map"][casa_ref]
120
- else:
121
- logger.debug(
122
- f"Unknown reference frame for {measinfo['type']} "
123
- f"measure, using verbatim: {casa_ref}"
124
- )
125
-
126
- msv4_measure[msv4_measure_conversion["Ref"]] = casa_ref
127
-
128
- if msv4_measure["type"] == "time":
129
- msv4_measure["format"] = time_format
130
- elif "QuantumUnits" in casacore_column_description["keywords"]:
131
- msv4_measure = {
132
- "type": "quantity",
133
- "units": list(casacore_column_description["keywords"]["QuantumUnits"]),
134
- }
135
-
136
- return msv4_measure
137
-
138
34
 
139
35
  def create_attribute_metadata(col, main_column_descriptions):
140
36
  attrs_metadata = column_description_casacore_to_msv4_measure(