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
@@ -1,803 +0,0 @@
1
- import pandas as pd
2
- from xradio._utils.list_and_array import to_list
3
- import numbers
4
- import numpy as np
5
- import toolviper.utils.logger as logger
6
- import xarray as xr
7
-
8
-
9
- class ProcessingSet(dict):
10
- """
11
- A dictionary subclass representing a Processing Set (PS) containing Measurement Sets v4 (MS).
12
-
13
- This class extends the built-in `dict` class to provide additional methods for
14
- manipulating and selecting subsets of the Processing Set. It includes functionality
15
- for summarizing metadata, selecting subsets based on various criteria, and
16
- exporting the data to storage formats.
17
-
18
- Parameters
19
- ----------
20
- *args : dict, optional
21
- Variable length argument list passed to the base `dict` class.
22
- **kwargs : dict, optional
23
- Arbitrary keyword arguments passed to the base `dict` class.
24
- """
25
-
26
- def __init__(self, *args, **kwargs):
27
- """
28
- Initialize the ProcessingSet instance.
29
-
30
- Parameters
31
- ----------
32
- *args : dict, optional
33
- Variable length argument list passed to the base `dict` class.
34
- **kwargs : dict, optional
35
- Arbitrary keyword arguments passed to the base `dict` class.
36
- """
37
- super().__init__(*args, **kwargs)
38
- self.meta = {"summary": {}}
39
-
40
- def summary(self, data_group="base"):
41
- """
42
- Generate and retrieve a summary of the Processing Set.
43
-
44
- The summary includes information such as the names of the Measurement Sets,
45
- their intents, polarizations, spectral window names, field names, source names,
46
- field coordinates, start frequencies, and end frequencies.
47
-
48
- Parameters
49
- ----------
50
- data_group : str, optional
51
- The data group to summarize. Default is "base".
52
-
53
- Returns
54
- -------
55
- pandas.DataFrame
56
- A DataFrame containing the summary information of the specified data group.
57
- """
58
-
59
- if data_group in self.meta["summary"]:
60
- return self.meta["summary"][data_group]
61
- else:
62
- self.meta["summary"][data_group] = self._summary(data_group).sort_values(
63
- by=["name"], ascending=True
64
- )
65
- return self.meta["summary"][data_group]
66
-
67
- def get_ps_max_dims(self):
68
- """
69
- Determine the maximum dimensions across all Measurement Sets in the Processing Set.
70
-
71
- This method examines each Measurement Set's dimensions and computes the maximum
72
- size for each dimension across the entire Processing Set.
73
-
74
- For example, if the Processing Set contains two MSs with dimensions (50, 20, 30) and (10, 30, 40),
75
- the maximum dimensions will be (50, 30, 40).
76
-
77
- Returns
78
- -------
79
- dict
80
- A dictionary containing the maximum dimensions of the Processing Set, with dimension names as keys
81
- and their maximum sizes as values.
82
- """
83
- if "max_dims" in self.meta:
84
- return self.meta["max_dims"]
85
- else:
86
- self.meta["max_dims"] = self._get_ps_max_dims()
87
- return self.meta["max_dims"]
88
-
89
- def get_ps_freq_axis(self):
90
- """
91
- Combine the frequency axes of all Measurement Sets in the Processing Set.
92
-
93
- This method aggregates the frequency information from each Measurement Set to create
94
- a unified frequency axis for the entire Processing Set.
95
-
96
- Returns
97
- -------
98
- xarray.DataArray
99
- The combined frequency axis of the Processing Set.
100
- """
101
- if "freq_axis" in self.meta:
102
- return self.meta["freq_axis"]
103
- else:
104
- self.meta["freq_axis"] = self._get_ps_freq_axis()
105
- return self.meta["freq_axis"]
106
-
107
- def _summary(self, data_group="base"):
108
- summary_data = {
109
- "name": [],
110
- "intents": [],
111
- "shape": [],
112
- "polarization": [],
113
- "scan_name": [],
114
- "spw_name": [],
115
- "field_name": [],
116
- "source_name": [],
117
- "line_name": [],
118
- "field_coords": [],
119
- "start_frequency": [],
120
- "end_frequency": [],
121
- }
122
- from astropy.coordinates import SkyCoord
123
- import astropy.units as u
124
-
125
- for key, value in self.items():
126
- summary_data["name"].append(key)
127
- summary_data["intents"].append(value.attrs["partition_info"]["intents"])
128
- summary_data["spw_name"].append(
129
- value.attrs["partition_info"]["spectral_window_name"]
130
- )
131
- summary_data["polarization"].append(value.polarization.values)
132
- summary_data["scan_name"].append(value.attrs["partition_info"]["scan_name"])
133
- data_name = value.attrs["data_groups"][data_group]["correlated_data"]
134
-
135
- if "VISIBILITY" in data_name:
136
- center_name = "FIELD_PHASE_CENTER"
137
-
138
- if "SPECTRUM" in data_name:
139
- center_name = "FIELD_REFERENCE_CENTER"
140
-
141
- summary_data["shape"].append(value[data_name].shape)
142
-
143
- summary_data["field_name"].append(
144
- value.attrs["partition_info"]["field_name"]
145
- )
146
- summary_data["source_name"].append(
147
- value.attrs["partition_info"]["source_name"]
148
- )
149
-
150
- summary_data["line_name"].append(value.attrs["partition_info"]["line_name"])
151
-
152
- summary_data["start_frequency"].append(
153
- to_list(value["frequency"].values)[0]
154
- )
155
- summary_data["end_frequency"].append(to_list(value["frequency"].values)[-1])
156
-
157
- if (
158
- value[data_name].attrs["field_and_source_xds"].attrs["type"]
159
- == "field_and_source_ephemeris"
160
- ):
161
- summary_data["field_coords"].append("Ephemeris")
162
- # elif (
163
- # "time"
164
- # in value[data_name].attrs["field_and_source_xds"][center_name].coords
165
- # ):
166
- elif (
167
- value[data_name]
168
- .attrs["field_and_source_xds"][center_name]["field_name"]
169
- .size
170
- > 1
171
- ):
172
- summary_data["field_coords"].append("Multi-Phase-Center")
173
- else:
174
- ra_dec_rad = (
175
- value[data_name]
176
- .attrs["field_and_source_xds"][center_name]
177
- .values[0, :]
178
- )
179
- frame = (
180
- value[data_name]
181
- .attrs["field_and_source_xds"][center_name]
182
- .attrs["frame"]
183
- .lower()
184
- )
185
-
186
- coord = SkyCoord(
187
- ra=ra_dec_rad[0] * u.rad, dec=ra_dec_rad[1] * u.rad, frame=frame
188
- )
189
-
190
- summary_data["field_coords"].append(
191
- [
192
- frame,
193
- coord.ra.to_string(unit=u.hour, precision=2),
194
- coord.dec.to_string(unit=u.deg, precision=2),
195
- ]
196
- )
197
-
198
- summary_df = pd.DataFrame(summary_data)
199
- return summary_df
200
-
201
- def _get_ps_freq_axis(self):
202
-
203
- spw_ids = []
204
- freq_axis_list = []
205
- frame = self.get(0).frequency.attrs["observer"]
206
- for ms_xds in self.values():
207
- assert (
208
- frame == ms_xds.frequency.attrs["observer"]
209
- ), "Frequency reference frame not consistent in Processing Set."
210
- if ms_xds.frequency.attrs["spectral_window_id"] not in spw_ids:
211
- spw_ids.append(ms_xds.frequency.attrs["spectral_window_id"])
212
- freq_axis_list.append(ms_xds.frequency)
213
-
214
- freq_axis = xr.concat(freq_axis_list, dim="frequency").sortby("frequency")
215
- return freq_axis
216
-
217
- def _get_ps_max_dims(self):
218
- max_dims = None
219
- for ms_xds in self.values():
220
- if max_dims is None:
221
- max_dims = dict(ms_xds.sizes)
222
- else:
223
- for dim_name, size in ms_xds.sizes.items():
224
- if dim_name in max_dims:
225
- if max_dims[dim_name] < size:
226
- max_dims[dim_name] = size
227
- else:
228
- max_dims[dim_name] = size
229
- return max_dims
230
-
231
- def get(self, id):
232
- return self[list(self.keys())[id]]
233
-
234
- def sel(self, string_exact_match: bool = True, query: str = None, **kwargs):
235
- """
236
- Select a subset of the Processing Set based on specified criteria.
237
-
238
- This method allows filtering the Processing Set by matching column names and values
239
- or by applying a Pandas query string. The selection criteria can target various
240
- attributes of the Measurement Sets such as intents, polarization, spectral window names, etc.
241
-
242
- Note
243
- ----
244
- This selection does not modify the actual data within the Measurement Sets. For example, if
245
- a Measurement Set has `field_name=['field_0','field_10','field_08']` and `ps.sel(field_name='field_0')`
246
- is invoked, the resulting subset will still contain the original list `['field_0','field_10','field_08']`.
247
-
248
- Parameters
249
- ----------
250
- string_exact_match : bool, optional
251
- If `True`, string matching will require exact matches for string and string list columns.
252
- If `False`, partial matches are allowed. Default is `True`.
253
- query : str, optional
254
- A Pandas query string to apply additional filtering. Default is `None`.
255
- **kwargs : dict
256
- Keyword arguments representing column names and their corresponding values to filter the Processing Set.
257
-
258
- Returns
259
- -------
260
- ProcessingSet
261
- A new `ProcessingSet` instance containing only the Measurement Sets that match the selection criteria.
262
-
263
- Examples
264
- --------
265
- >>> # Select all MSs with intents 'OBSERVE_TARGET#ON_SOURCE' and polarization 'RR' or 'LL'
266
- >>> selected_ps = ps.sel(intents='OBSERVE_TARGET#ON_SOURCE', polarization=['RR', 'LL'])
267
-
268
- >>> # Select all MSs with start_frequency greater than 100 GHz and less than 200 GHz
269
- >>> selected_ps = ps.sel(query='start_frequency > 100e9 AND end_frequency < 200e9')
270
- """
271
- import numpy as np
272
-
273
- def select_rows(df, col, sel_vals, string_exact_match):
274
- def check_selection(row_val):
275
- row_val = to_list(
276
- row_val
277
- ) # make sure that it is a list so that we can iterate over it.
278
-
279
- for rw in row_val:
280
- for s in sel_vals:
281
- if string_exact_match:
282
- if rw == s:
283
- return True
284
- else:
285
- if s in rw:
286
- return True
287
- return False
288
-
289
- return df[df[col].apply(check_selection)]
290
-
291
- summary_table = self.summary()
292
- for key, value in kwargs.items():
293
- value = to_list(value) # make sure value is a list.
294
-
295
- if len(value) == 1 and isinstance(value[0], slice):
296
- summary_table = summary_table[
297
- summary_table[key].between(value[0].start, value[0].stop)
298
- ]
299
- else:
300
- summary_table = select_rows(
301
- summary_table, key, value, string_exact_match
302
- )
303
-
304
- if query is not None:
305
- summary_table = summary_table.query(query)
306
-
307
- sub_ps = ProcessingSet()
308
- for key, val in self.items():
309
- if key in summary_table["name"].values:
310
- sub_ps[key] = val
311
-
312
- return sub_ps
313
-
314
- def ms_sel(self, **kwargs):
315
- """
316
- Select a subset of the Processing Set by applying the `xarray.Dataset.sel` method to each Measurement Set.
317
-
318
- This method allows for selection based on label-based indexing for each dimension of the datasets.
319
-
320
- Parameters
321
- ----------
322
- **kwargs : dict
323
- Keyword arguments representing dimension names and the labels to select along those dimensions.
324
- These are passed directly to the `xarray.Dataset.sel <https://docs.xarray.dev/en/latest/generated/xarray.Dataset.sel.html>`__ method.
325
-
326
- Returns
327
- -------
328
- ProcessingSet
329
- A new `ProcessingSet` instance containing the selected subsets of each Measurement Set.
330
- """
331
- sub_ps = ProcessingSet()
332
- for key, val in self.items():
333
- sub_ps[key] = val.sel(kwargs)
334
- return sub_ps
335
-
336
- def ms_isel(self, **kwargs):
337
- """
338
- Select a subset of the Processing Set by applying the `isel` method to each Measurement Set.
339
-
340
- This method allows for selection based on integer-based indexing for each dimension of the datasets.
341
-
342
- Parameters
343
- ----------
344
- **kwargs : dict
345
- Keyword arguments representing dimension names and the integer indices to select along those dimensions.
346
- These are passed directly to the `xarray.Dataset.isel <https://docs.xarray.dev/en/latest/generated/xarray.Dataset.isel.html>`__ method.
347
-
348
- Returns
349
- -------
350
- ProcessingSet
351
- A new `ProcessingSet` instance containing the selected subsets of each Measurement Set.
352
- """
353
- sub_ps = ProcessingSet()
354
- for key, val in self.items():
355
- sub_ps[key] = val.isel(kwargs)
356
- return sub_ps
357
-
358
- def to_store(self, store, **kwargs):
359
- """
360
- Write the Processing Set to a Zarr store.
361
-
362
- This method serializes each Measurement Set within the Processing Set to a separate Zarr group
363
- within the specified store directory. Note that writing to cloud storage is not supported yet.
364
-
365
- Parameters
366
- ----------
367
- store : str
368
- The filesystem path to the Zarr store directory where the data will be saved.
369
- **kwargs : dict, optional
370
- Additional keyword arguments to be passed to the `xarray.Dataset.to_zarr` method.
371
- Refer to the `xarray.Dataset.to_zarr <https://docs.xarray.dev/en/latest/generated/xarray.Dataset.to_zarr.html>`__
372
- for available options.
373
-
374
- Returns
375
- -------
376
- None
377
-
378
- Raises
379
- ------
380
- OSError
381
- If the specified store path is invalid or not writable.
382
-
383
- Examples
384
- --------
385
- >>> # Save the Processing Set to a local Zarr store
386
- >>> ps.to_store('/path/to/zarr_store')
387
- """
388
- import os
389
-
390
- for key, value in self.items():
391
- value.to_store(os.path.join(store, key), **kwargs)
392
-
393
- def get_combined_field_and_source_xds(self, data_group="base"):
394
- """
395
- Combine all non-ephemeris `field_and_source_xds` datasets from a Processing Set for a datagroup into a single dataset.
396
-
397
- Parameters
398
- ----------
399
- data_group : str, optional
400
- The data group to process. Default is "base".
401
-
402
- Returns
403
- -------
404
- xarray.Dataset
405
- combined_field_and_source_xds: Combined dataset for standard fields.
406
-
407
- Raises
408
- ------
409
- ValueError
410
- If the `field_and_source_xds` attribute is missing or improperly formatted in any Measurement Set.
411
- """
412
-
413
- combined_field_and_source_xds = xr.Dataset()
414
- for ms_name, ms_xds in self.items():
415
- correlated_data_name = ms_xds.attrs["data_groups"][data_group][
416
- "correlated_data"
417
- ]
418
-
419
- field_and_source_xds = (
420
- ms_xds[correlated_data_name]
421
- .attrs["field_and_source_xds"]
422
- .copy(deep=True)
423
- )
424
-
425
- if not field_and_source_xds.attrs["type"] == "field_and_source_ephemeris":
426
-
427
- if (
428
- "line_name" in field_and_source_xds.coords
429
- ): # Not including line info since it is a function of spw.
430
- field_and_source_xds = field_and_source_xds.drop_vars(
431
- ["LINE_REST_FREQUENCY", "LINE_SYSTEMIC_VELOCITY"],
432
- errors="ignore",
433
- )
434
- del field_and_source_xds["line_name"]
435
- del field_and_source_xds["line_label"]
436
-
437
- if len(combined_field_and_source_xds.data_vars) == 0:
438
- combined_field_and_source_xds = field_and_source_xds
439
- else:
440
- combined_field_and_source_xds = xr.concat(
441
- [combined_field_and_source_xds, field_and_source_xds],
442
- dim="field_name",
443
- )
444
-
445
- if (len(combined_field_and_source_xds.data_vars) > 0) and (
446
- "FIELD_PHASE_CENTER" in combined_field_and_source_xds
447
- ):
448
- combined_field_and_source_xds = (
449
- combined_field_and_source_xds.drop_duplicates("field_name")
450
- )
451
-
452
- combined_field_and_source_xds["MEAN_PHASE_CENTER"] = (
453
- combined_field_and_source_xds["FIELD_PHASE_CENTER"].mean(
454
- dim=["field_name"]
455
- )
456
- )
457
-
458
- ra1 = (
459
- combined_field_and_source_xds["FIELD_PHASE_CENTER"]
460
- .sel(sky_dir_label="ra")
461
- .values
462
- )
463
- dec1 = (
464
- combined_field_and_source_xds["FIELD_PHASE_CENTER"]
465
- .sel(sky_dir_label="dec")
466
- .values
467
- )
468
- ra2 = (
469
- combined_field_and_source_xds["MEAN_PHASE_CENTER"]
470
- .sel(sky_dir_label="ra")
471
- .values
472
- )
473
- dec2 = (
474
- combined_field_and_source_xds["MEAN_PHASE_CENTER"]
475
- .sel(sky_dir_label="dec")
476
- .values
477
- )
478
-
479
- from xradio._utils.coord_math import haversine
480
-
481
- distance = haversine(ra1, dec1, ra2, dec2)
482
- min_index = distance.argmin()
483
-
484
- combined_field_and_source_xds.attrs["center_field_name"] = (
485
- combined_field_and_source_xds.field_name[min_index].values
486
- )
487
-
488
- return combined_field_and_source_xds
489
-
490
- def get_combined_field_and_source_xds_ephemeris(self, data_group="base"):
491
- """
492
- Combine all ephemeris `field_and_source_xds` datasets from a Processing Set for a datagroup into a single dataset.
493
-
494
- Parameters
495
- ----------
496
- data_group : str, optional
497
- The data group to process. Default is "base".
498
-
499
- Returns
500
- -------
501
- xarray.Dataset
502
- - combined_ephemeris_field_and_source_xds: Combined dataset for ephemeris fields.
503
-
504
- Raises
505
- ------
506
- ValueError
507
- If the `field_and_source_xds` attribute is missing or improperly formatted in any Measurement Set.
508
- """
509
-
510
- combined_ephemeris_field_and_source_xds = xr.Dataset()
511
- for ms_name, ms_xds in self.items():
512
-
513
- correlated_data_name = ms_xds.attrs["data_groups"][data_group][
514
- "correlated_data"
515
- ]
516
-
517
- field_and_source_xds = (
518
- ms_xds[correlated_data_name]
519
- .attrs["field_and_source_xds"]
520
- .copy(deep=True)
521
- )
522
-
523
- if field_and_source_xds.attrs["type"] == "field_and_source_ephemeris":
524
-
525
- if (
526
- "line_name" in field_and_source_xds.coords
527
- ): # Not including line info since it is a function of spw.
528
- field_and_source_xds = field_and_source_xds.drop_vars(
529
- ["LINE_REST_FREQUENCY", "LINE_SYSTEMIC_VELOCITY"],
530
- errors="ignore",
531
- )
532
- del field_and_source_xds["line_name"]
533
- del field_and_source_xds["line_label"]
534
-
535
- from xradio.measurement_set._utils._msv2.msv4_sub_xdss import (
536
- interpolate_to_time,
537
- )
538
-
539
- if "time_ephemeris" in field_and_source_xds:
540
- field_and_source_xds = interpolate_to_time(
541
- field_and_source_xds,
542
- field_and_source_xds.time,
543
- "field_and_source_xds",
544
- "time_ephemeris",
545
- )
546
- del field_and_source_xds["time_ephemeris"]
547
- field_and_source_xds = field_and_source_xds.rename(
548
- {"time_ephemeris": "time"}
549
- )
550
-
551
- if "OBSERVER_POSITION" in field_and_source_xds:
552
- field_and_source_xds = field_and_source_xds.drop_vars(
553
- ["OBSERVER_POSITION"], errors="ignore"
554
- )
555
-
556
- if len(combined_ephemeris_field_and_source_xds.data_vars) == 0:
557
- combined_ephemeris_field_and_source_xds = field_and_source_xds
558
- else:
559
-
560
- combined_ephemeris_field_and_source_xds = xr.concat(
561
- [combined_ephemeris_field_and_source_xds, field_and_source_xds],
562
- dim="time",
563
- )
564
-
565
- if (len(combined_ephemeris_field_and_source_xds.data_vars) > 0) and (
566
- "FIELD_PHASE_CENTER" in combined_ephemeris_field_and_source_xds
567
- ):
568
-
569
- from xradio._utils.coord_math import wrap_to_pi
570
-
571
- offset = (
572
- combined_ephemeris_field_and_source_xds["FIELD_PHASE_CENTER"]
573
- - combined_ephemeris_field_and_source_xds["SOURCE_LOCATION"]
574
- )
575
- combined_ephemeris_field_and_source_xds["FIELD_OFFSET"] = xr.DataArray(
576
- wrap_to_pi(offset.sel(sky_pos_label=["ra", "dec"])).values,
577
- dims=["time", "sky_dir_label"],
578
- )
579
- combined_ephemeris_field_and_source_xds["FIELD_OFFSET"].attrs = (
580
- combined_ephemeris_field_and_source_xds["FIELD_PHASE_CENTER"].attrs
581
- )
582
- combined_ephemeris_field_and_source_xds["FIELD_OFFSET"].attrs["units"] = (
583
- combined_ephemeris_field_and_source_xds["FIELD_OFFSET"].attrs["units"][
584
- :2
585
- ]
586
- )
587
-
588
- ra1 = (
589
- combined_ephemeris_field_and_source_xds["FIELD_OFFSET"]
590
- .sel(sky_dir_label="ra")
591
- .values
592
- )
593
- dec1 = (
594
- combined_ephemeris_field_and_source_xds["FIELD_OFFSET"]
595
- .sel(sky_dir_label="dec")
596
- .values
597
- )
598
- ra2 = 0.0
599
- dec2 = 0.0
600
-
601
- from xradio._utils.coord_math import haversine
602
-
603
- distance = haversine(ra1, dec1, ra2, dec2)
604
- min_index = distance.argmin()
605
-
606
- combined_ephemeris_field_and_source_xds.attrs["center_field_name"] = (
607
- combined_ephemeris_field_and_source_xds.field_name[min_index].values
608
- )
609
-
610
- return combined_ephemeris_field_and_source_xds
611
-
612
- def plot_phase_centers(self, label_all_fields=False, data_group="base"):
613
- """
614
- Plot the phase center locations of all fields in the Processing Set.
615
-
616
- This method is primarily used for visualizing mosaics. It generates scatter plots of
617
- the phase center coordinates for both standard and ephemeris fields. The central field
618
- is highlighted in red based on the closest phase center calculation.
619
-
620
- Parameters
621
- ----------
622
- label_all_fields : bool, optional
623
- If `True`, all fields will be labeled on the plot. Default is `False`.
624
- data_group : str, optional
625
- The data group to use for processing. Default is "base".
626
-
627
- Returns
628
- -------
629
- None
630
-
631
- Raises
632
- ------
633
- ValueError
634
- If the combined datasets are empty or improperly formatted.
635
- """
636
- combined_field_and_source_xds = self.get_combined_field_and_source_xds(
637
- data_group
638
- )
639
- combined_ephemeris_field_and_source_xds = (
640
- self.get_combined_field_and_source_xds_ephemeris(data_group)
641
- )
642
- from matplotlib import pyplot as plt
643
-
644
- if (len(combined_field_and_source_xds.data_vars) > 0) and (
645
- "FIELD_PHASE_CENTER" in combined_field_and_source_xds
646
- ):
647
- plt.figure()
648
- plt.title("Field Phase Center Locations")
649
- plt.scatter(
650
- combined_field_and_source_xds["FIELD_PHASE_CENTER"].sel(
651
- sky_dir_label="ra"
652
- ),
653
- combined_field_and_source_xds["FIELD_PHASE_CENTER"].sel(
654
- sky_dir_label="dec"
655
- ),
656
- )
657
-
658
- center_field_name = combined_field_and_source_xds.attrs["center_field_name"]
659
- center_field = combined_field_and_source_xds.sel(
660
- field_name=center_field_name
661
- )
662
- plt.scatter(
663
- center_field["FIELD_PHASE_CENTER"].sel(sky_dir_label="ra"),
664
- center_field["FIELD_PHASE_CENTER"].sel(sky_dir_label="dec"),
665
- color="red",
666
- label=center_field_name,
667
- )
668
- plt.xlabel("RA (rad)")
669
- plt.ylabel("DEC (rad)")
670
- plt.legend()
671
- plt.show()
672
-
673
- if (len(combined_ephemeris_field_and_source_xds.data_vars) > 0) and (
674
- "FIELD_PHASE_CENTER" in combined_ephemeris_field_and_source_xds
675
- ):
676
-
677
- plt.figure()
678
- plt.title(
679
- "Offset of Field Phase Center from Source Location (Ephemeris Data)"
680
- )
681
- plt.scatter(
682
- combined_ephemeris_field_and_source_xds["FIELD_OFFSET"].sel(
683
- sky_dir_label="ra"
684
- ),
685
- combined_ephemeris_field_and_source_xds["FIELD_OFFSET"].sel(
686
- sky_dir_label="dec"
687
- ),
688
- )
689
-
690
- center_field_name = combined_ephemeris_field_and_source_xds.attrs[
691
- "center_field_name"
692
- ]
693
-
694
- combined_ephemeris_field_and_source_xds = (
695
- combined_ephemeris_field_and_source_xds.set_xindex("field_name")
696
- )
697
-
698
- center_field = combined_ephemeris_field_and_source_xds.sel(
699
- field_name=center_field_name
700
- )
701
- plt.scatter(
702
- center_field["FIELD_OFFSET"].sel(sky_dir_label="ra"),
703
- center_field["FIELD_OFFSET"].sel(sky_dir_label="dec"),
704
- color="red",
705
- label=center_field_name,
706
- )
707
- plt.xlabel("RA Offset (rad)")
708
- plt.ylabel("DEC Offset (rad)")
709
- plt.legend()
710
- plt.show()
711
-
712
- def get_combined_antenna_xds(self):
713
- """
714
- Combine the `antenna_xds` datasets from all Measurement Sets into a single dataset.
715
-
716
- This method concatenates the antenna datasets from each Measurement Set along the 'antenna_name' dimension.
717
-
718
- Returns
719
- -------
720
- xarray.Dataset
721
- A combined `xarray.Dataset` containing antenna information from all Measurement Sets.
722
-
723
- Raises
724
- ------
725
- ValueError
726
- If antenna datasets are missing required variables or improperly formatted.
727
- """
728
- combined_antenna_xds = xr.Dataset()
729
- for cor_name, ms_xds in self.items():
730
- antenna_xds = ms_xds.antenna_xds.copy(deep=True)
731
-
732
- if len(combined_antenna_xds.data_vars) == 0:
733
- combined_antenna_xds = antenna_xds
734
- else:
735
- combined_antenna_xds = xr.concat(
736
- [combined_antenna_xds, antenna_xds],
737
- dim="antenna_name",
738
- data_vars="minimal",
739
- coords="minimal",
740
- )
741
-
742
- # ALMA WVR antenna_xds data has a NaN value for the antenna receptor angle.
743
- if "ANTENNA_RECEPTOR_ANGLE" in combined_antenna_xds.data_vars:
744
- combined_antenna_xds = combined_antenna_xds.dropna("antenna_name")
745
-
746
- combined_antenna_xds = combined_antenna_xds.drop_duplicates("antenna_name")
747
-
748
- return combined_antenna_xds
749
-
750
- def plot_antenna_positions(self):
751
- """
752
- Plot the antenna positions of all antennas in the Processing Set.
753
-
754
- This method generates three scatter plots displaying the antenna positions in different planes:
755
- - X vs Y
756
- - X vs Z
757
- - Y vs Z
758
-
759
- Parameters
760
- ----------
761
- None
762
-
763
- Returns
764
- -------
765
- None
766
-
767
- Raises
768
- ------
769
- ValueError
770
- If the combined antenna dataset is empty or missing required coordinates.
771
- """
772
- combined_antenna_xds = self.get_combined_antenna_xds()
773
- from matplotlib import pyplot as plt
774
-
775
- plt.figure()
776
- plt.title("Antenna Positions")
777
- plt.scatter(
778
- combined_antenna_xds["ANTENNA_POSITION"].sel(cartesian_pos_label="x"),
779
- combined_antenna_xds["ANTENNA_POSITION"].sel(cartesian_pos_label="y"),
780
- )
781
- plt.xlabel("x (m)")
782
- plt.ylabel("y (m)")
783
- plt.show()
784
-
785
- plt.figure()
786
- plt.title("Antenna Positions")
787
- plt.scatter(
788
- combined_antenna_xds["ANTENNA_POSITION"].sel(cartesian_pos_label="x"),
789
- combined_antenna_xds["ANTENNA_POSITION"].sel(cartesian_pos_label="z"),
790
- )
791
- plt.xlabel("x (m)")
792
- plt.ylabel("z (m)")
793
- plt.show()
794
-
795
- plt.figure()
796
- plt.title("Antenna Positions")
797
- plt.scatter(
798
- combined_antenna_xds["ANTENNA_POSITION"].sel(cartesian_pos_label="y"),
799
- combined_antenna_xds["ANTENNA_POSITION"].sel(cartesian_pos_label="z"),
800
- )
801
- plt.xlabel("y (m)")
802
- plt.ylabel("z (m)")
803
- plt.show()