imap-processing 1.0.0__py3-none-any.whl → 1.0.2__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.
- imap_processing/_version.py +2 -2
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +13 -1
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +97 -254
- imap_processing/cdf/config/imap_codice_l2-hi-omni_variable_attrs.yaml +635 -0
- imap_processing/cdf/config/imap_codice_l2-hi-sectored_variable_attrs.yaml +422 -0
- imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml +29 -22
- imap_processing/cdf/config/imap_enamaps_l2-healpix_variable_attrs.yaml +2 -0
- imap_processing/cdf/config/imap_enamaps_l2-rectangular_variable_attrs.yaml +12 -2
- imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +2 -13
- imap_processing/cdf/utils.py +2 -2
- imap_processing/cli.py +10 -27
- imap_processing/codice/codice_l1a_lo_angular.py +362 -0
- imap_processing/codice/codice_l1a_lo_species.py +282 -0
- imap_processing/codice/codice_l1b.py +62 -97
- imap_processing/codice/codice_l2.py +801 -174
- imap_processing/codice/codice_new_l1a.py +64 -0
- imap_processing/codice/constants.py +96 -0
- imap_processing/codice/utils.py +270 -0
- imap_processing/ena_maps/ena_maps.py +157 -95
- imap_processing/ena_maps/utils/coordinates.py +5 -0
- imap_processing/ena_maps/utils/corrections.py +450 -0
- imap_processing/ena_maps/utils/map_utils.py +143 -42
- imap_processing/ena_maps/utils/naming.py +3 -1
- imap_processing/hi/hi_l1c.py +34 -12
- imap_processing/hi/hi_l2.py +82 -44
- imap_processing/ialirt/constants.py +7 -1
- imap_processing/ialirt/generate_coverage.py +3 -1
- imap_processing/ialirt/l0/parse_mag.py +1 -0
- imap_processing/ialirt/l0/process_codice.py +66 -0
- imap_processing/ialirt/l0/process_hit.py +1 -0
- imap_processing/ialirt/l0/process_swapi.py +1 -0
- imap_processing/ialirt/l0/process_swe.py +2 -0
- imap_processing/ialirt/process_ephemeris.py +6 -2
- imap_processing/ialirt/utils/create_xarray.py +4 -2
- imap_processing/idex/idex_l2a.py +2 -2
- imap_processing/idex/idex_l2b.py +1 -1
- imap_processing/lo/l1c/lo_l1c.py +62 -4
- imap_processing/lo/l2/lo_l2.py +85 -15
- imap_processing/mag/l1a/mag_l1a.py +2 -2
- imap_processing/mag/l1a/mag_l1a_data.py +71 -13
- imap_processing/mag/l1c/interpolation_methods.py +34 -13
- imap_processing/mag/l1c/mag_l1c.py +117 -67
- imap_processing/mag/l1d/mag_l1d_data.py +3 -1
- imap_processing/quality_flags.py +1 -0
- imap_processing/spice/geometry.py +11 -9
- imap_processing/spice/pointing_frame.py +77 -50
- imap_processing/swapi/constants.py +4 -0
- imap_processing/swapi/l1/swapi_l1.py +59 -24
- imap_processing/swapi/l2/swapi_l2.py +17 -3
- imap_processing/swe/utils/swe_constants.py +7 -7
- imap_processing/ultra/l1a/ultra_l1a.py +121 -72
- imap_processing/ultra/l1b/de.py +57 -1
- imap_processing/ultra/l1b/extendedspin.py +1 -1
- imap_processing/ultra/l1b/ultra_l1b_annotated.py +0 -1
- imap_processing/ultra/l1b/ultra_l1b_culling.py +2 -2
- imap_processing/ultra/l1b/ultra_l1b_extended.py +25 -12
- imap_processing/ultra/l1c/helio_pset.py +29 -6
- imap_processing/ultra/l1c/l1c_lookup_utils.py +4 -2
- imap_processing/ultra/l1c/spacecraft_pset.py +10 -6
- imap_processing/ultra/l1c/ultra_l1c.py +6 -6
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +82 -20
- imap_processing/ultra/l2/ultra_l2.py +2 -2
- imap_processing-1.0.2.dist-info/METADATA +121 -0
- {imap_processing-1.0.0.dist-info → imap_processing-1.0.2.dist-info}/RECORD +67 -61
- imap_processing-1.0.0.dist-info/METADATA +0 -120
- {imap_processing-1.0.0.dist-info → imap_processing-1.0.2.dist-info}/LICENSE +0 -0
- {imap_processing-1.0.0.dist-info → imap_processing-1.0.2.dist-info}/WHEEL +0 -0
- {imap_processing-1.0.0.dist-info → imap_processing-1.0.2.dist-info}/entry_points.txt +0 -0
|
@@ -73,7 +73,7 @@ def match_coords_to_indices(
|
|
|
73
73
|
input_object: PointingSet | AbstractSkyMap,
|
|
74
74
|
output_object: PointingSet | AbstractSkyMap,
|
|
75
75
|
event_et: float | None = None,
|
|
76
|
-
) -> NDArray:
|
|
76
|
+
) -> NDArray | xr.DataArray:
|
|
77
77
|
"""
|
|
78
78
|
Find the output indices corresponding to each input coord between 2 spatial objects.
|
|
79
79
|
|
|
@@ -87,7 +87,7 @@ def match_coords_to_indices(
|
|
|
87
87
|
This function always "pushes" the pixels of the input object to corresponding pixels
|
|
88
88
|
in the output object's unwrapped rectangular grid or healpix tessellation;
|
|
89
89
|
however, by swapping the input and output objects, one can apply the "pull" method
|
|
90
|
-
of index
|
|
90
|
+
of index matching.
|
|
91
91
|
|
|
92
92
|
At present, the allowable inputs are either:
|
|
93
93
|
- A PointingSet object and a SkyMap object, in either order of input/output.
|
|
@@ -97,7 +97,7 @@ def match_coords_to_indices(
|
|
|
97
97
|
Parameters
|
|
98
98
|
----------
|
|
99
99
|
input_object : PointingSet | AbstractSkyMap
|
|
100
|
-
An object containing
|
|
100
|
+
An object containing spatial pixel centers in azimuth and elevation,
|
|
101
101
|
which will be matched to 1D indices of spatial pixels in the output frame.
|
|
102
102
|
Must contain the Spice frame in which the pixel centers are defined.
|
|
103
103
|
output_object : PointingSet | AbstractSkyMap
|
|
@@ -114,11 +114,13 @@ def match_coords_to_indices(
|
|
|
114
114
|
|
|
115
115
|
Returns
|
|
116
116
|
-------
|
|
117
|
-
flat_indices_input_grid_output_frame :
|
|
118
|
-
|
|
119
|
-
the
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
flat_indices_input_grid_output_frame : xr.DataArray
|
|
118
|
+
Array of pixel indices mapping each input object pixel center to a pixel
|
|
119
|
+
in the output object. The output xr.DataArray will have the same leading
|
|
120
|
+
dimension labels preserved. The shape of the output array is (..., n)
|
|
121
|
+
where ... matches the non-spatial dimensions of the input object, and n
|
|
122
|
+
is the number of spatial pixels in the input object. Output indices may
|
|
123
|
+
contain 0, 1, or multiple occurrences of the same output index.
|
|
122
124
|
|
|
123
125
|
Raises
|
|
124
126
|
------
|
|
@@ -166,14 +168,14 @@ def match_coords_to_indices(
|
|
|
166
168
|
# use ravel_multi_index to get the 1D indices of the pixels in the output frame.
|
|
167
169
|
az_indices = (
|
|
168
170
|
np.digitize(
|
|
169
|
-
input_obj_az_el_output_frame[
|
|
171
|
+
input_obj_az_el_output_frame[..., 0],
|
|
170
172
|
output_object.sky_grid.az_bin_edges,
|
|
171
173
|
)
|
|
172
174
|
- 1
|
|
173
175
|
)
|
|
174
176
|
el_indices = (
|
|
175
177
|
np.digitize(
|
|
176
|
-
input_obj_az_el_output_frame[
|
|
178
|
+
input_obj_az_el_output_frame[..., 1],
|
|
177
179
|
output_object.sky_grid.el_bin_edges,
|
|
178
180
|
)
|
|
179
181
|
- 1
|
|
@@ -191,8 +193,8 @@ def match_coords_to_indices(
|
|
|
191
193
|
# which directly returns the index on the output frame's Healpix tessellation.
|
|
192
194
|
flat_indices_input_grid_output_frame = hp.ang2pix(
|
|
193
195
|
nside=output_object.nside,
|
|
194
|
-
theta=input_obj_az_el_output_frame[
|
|
195
|
-
phi=input_obj_az_el_output_frame[
|
|
196
|
+
theta=input_obj_az_el_output_frame[..., 0], # Lon in degrees
|
|
197
|
+
phi=input_obj_az_el_output_frame[..., 1], # Lat in degrees
|
|
196
198
|
nest=output_object.nested,
|
|
197
199
|
lonlat=True,
|
|
198
200
|
)
|
|
@@ -202,6 +204,14 @@ def match_coords_to_indices(
|
|
|
202
204
|
f"Received: {output_object.tiling_type}"
|
|
203
205
|
)
|
|
204
206
|
|
|
207
|
+
# Wrap the output indices in a DataArray with the same leading dimensions as
|
|
208
|
+
# the input object az_el_points to preserve broadcasting information
|
|
209
|
+
input_dims = input_obj_az_el_input_frame.dims[:-1]
|
|
210
|
+
flat_indices_input_grid_output_frame = xr.DataArray(
|
|
211
|
+
flat_indices_input_grid_output_frame,
|
|
212
|
+
dims=input_dims,
|
|
213
|
+
)
|
|
214
|
+
|
|
205
215
|
return flat_indices_input_grid_output_frame
|
|
206
216
|
|
|
207
217
|
|
|
@@ -236,9 +246,10 @@ class PointingSet(ABC):
|
|
|
236
246
|
spice_reference_frame: geometry.SpiceFrame
|
|
237
247
|
|
|
238
248
|
# ======== Attributes required to be set in a subclass ========
|
|
239
|
-
# Azimuth and elevation coordinates of each spatial pixel.
|
|
240
|
-
#
|
|
241
|
-
|
|
249
|
+
# Azimuth and elevation coordinates of each spatial pixel. Must be an
|
|
250
|
+
# xr.DataArray with dimensions (..., spatial_dim, az_el_coord) to preserve
|
|
251
|
+
# dimension labels
|
|
252
|
+
az_el_points: xr.DataArray
|
|
242
253
|
# Tuple containing the names of each spatial coordinate of the xarray.Dataset
|
|
243
254
|
# stored in the data attribute
|
|
244
255
|
spatial_coords: tuple[str, ...]
|
|
@@ -274,7 +285,9 @@ class PointingSet(ABC):
|
|
|
274
285
|
num_points: int
|
|
275
286
|
The number of spatial pixels in the pointing set.
|
|
276
287
|
"""
|
|
277
|
-
|
|
288
|
+
# Last dimension is az/el vector, the second to last dimension is
|
|
289
|
+
# the number of pixels.
|
|
290
|
+
return self.az_el_points.shape[-2]
|
|
278
291
|
|
|
279
292
|
@property
|
|
280
293
|
def epoch(self) -> int:
|
|
@@ -430,11 +443,12 @@ class RectangularPointingSet(PointingSet):
|
|
|
430
443
|
# into shape (number of points in tiling of the sky, 2) where
|
|
431
444
|
# column 0 (az_el_points[:, 0]) is the azimuth of that point and
|
|
432
445
|
# column 1 (az_el_points[:, 1]) is the elevation of that point.
|
|
433
|
-
self.az_el_points =
|
|
434
|
-
(
|
|
435
|
-
self.sky_grid.az_grid.ravel(),
|
|
436
|
-
|
|
437
|
-
)
|
|
446
|
+
self.az_el_points = xr.DataArray(
|
|
447
|
+
np.stack(
|
|
448
|
+
(self.sky_grid.az_grid.ravel(), self.sky_grid.el_grid.ravel()),
|
|
449
|
+
axis=-1,
|
|
450
|
+
),
|
|
451
|
+
dims=[CoordNames.GENERIC_PIXEL.value, CoordNames.AZ_EL_VECTOR.value],
|
|
438
452
|
)
|
|
439
453
|
|
|
440
454
|
|
|
@@ -540,8 +554,9 @@ class UltraPointingSet(HealpixPointingSet):
|
|
|
540
554
|
# The coordinates of the healpix pixel centers are stored as a 2D array
|
|
541
555
|
# of shape (num_points, 2) where column 0 is the lon/az
|
|
542
556
|
# and column 1 is the lat/el.
|
|
543
|
-
self.az_el_points =
|
|
544
|
-
(azimuth_pixel_center, elevation_pixel_center)
|
|
557
|
+
self.az_el_points = xr.DataArray(
|
|
558
|
+
np.stack((azimuth_pixel_center, elevation_pixel_center), axis=-1),
|
|
559
|
+
dims=[CoordNames.GENERIC_PIXEL.value, CoordNames.AZ_EL_VECTOR.value],
|
|
545
560
|
)
|
|
546
561
|
|
|
547
562
|
@property
|
|
@@ -586,7 +601,45 @@ class UltraPointingSet(HealpixPointingSet):
|
|
|
586
601
|
)
|
|
587
602
|
|
|
588
603
|
|
|
589
|
-
class
|
|
604
|
+
class LoHiBasePointingSet(PointingSet):
|
|
605
|
+
"""
|
|
606
|
+
Base class for Lo and Hi pointing sets with HAE coordinate data.
|
|
607
|
+
|
|
608
|
+
This class provides common functionality for pointing sets that contain
|
|
609
|
+
hae_longitude and hae_latitude coordinates in the dataset.
|
|
610
|
+
"""
|
|
611
|
+
|
|
612
|
+
tiling_type: SkyTilingType = SkyTilingType.RECTANGULAR
|
|
613
|
+
|
|
614
|
+
def update_az_el_points(self) -> None:
|
|
615
|
+
"""
|
|
616
|
+
Update the az_el_points instance variable with new az/el coordinates.
|
|
617
|
+
|
|
618
|
+
The values store in the "hae_longitude" and "hae_latitude" variables
|
|
619
|
+
are used to construct the azimuth and elevation coordinates.
|
|
620
|
+
"""
|
|
621
|
+
# Get lon/lat coordinates, squeeze the epoch dimension and stack along
|
|
622
|
+
# the spatial dimensions. xarray.stack() takes possibly multiple spatial
|
|
623
|
+
# dimensions and reshapes those into a single dimension.
|
|
624
|
+
az_stacked = (
|
|
625
|
+
self.data["hae_longitude"]
|
|
626
|
+
.squeeze("epoch")
|
|
627
|
+
.stack({CoordNames.GENERIC_PIXEL.value: self.spatial_coords})
|
|
628
|
+
)
|
|
629
|
+
el_stacked = (
|
|
630
|
+
self.data["hae_latitude"]
|
|
631
|
+
.squeeze("epoch")
|
|
632
|
+
.stack({CoordNames.GENERIC_PIXEL.value: self.spatial_coords})
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
# Stack lon/lat along last axis to create shape (..., 2)
|
|
636
|
+
self.az_el_points = xr.DataArray(
|
|
637
|
+
np.stack([az_stacked.values, el_stacked.values], axis=-1),
|
|
638
|
+
dims=[*az_stacked.dims, CoordNames.AZ_EL_VECTOR.value],
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
class HiPointingSet(LoHiBasePointingSet):
|
|
590
643
|
"""
|
|
591
644
|
PointingSet object specific to Hi L1C PSet data.
|
|
592
645
|
|
|
@@ -594,28 +647,21 @@ class HiPointingSet(PointingSet):
|
|
|
594
647
|
----------
|
|
595
648
|
dataset : xarray.Dataset | str | Path
|
|
596
649
|
Hi L1C pointing set data loaded in a xarray.DataArray.
|
|
597
|
-
spin_phase : str
|
|
598
|
-
Include ENAs from "full", "ram" or "anti-ram" phases of the spin.
|
|
599
650
|
"""
|
|
600
651
|
|
|
601
|
-
def __init__(self, dataset: xr.Dataset | str | Path
|
|
602
|
-
super().__init__(dataset, spice_reference_frame=geometry.SpiceFrame.
|
|
652
|
+
def __init__(self, dataset: xr.Dataset | str | Path):
|
|
653
|
+
super().__init__(dataset, spice_reference_frame=geometry.SpiceFrame.IMAP_HAE)
|
|
654
|
+
|
|
655
|
+
self.spatial_coords = ("spin_angle_bin",)
|
|
603
656
|
|
|
604
|
-
#
|
|
605
|
-
|
|
606
|
-
|
|
657
|
+
# Naively generate the ram_mask variable assuming spacecraft frame
|
|
658
|
+
# binning. The ram_mask variable gets updated in the CG correction
|
|
659
|
+
# code if the CG correction is applied.
|
|
660
|
+
ram_mask = xr.zeros_like(self.data["spin_angle_bin"], dtype=bool)
|
|
607
661
|
# ram only includes spin-phase interval [0, 0.5)
|
|
608
662
|
# which is the first half of the spin_angle_bins
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
spin_angle_bin=slice(0, self.data["spin_angle_bin"].data.size // 2)
|
|
612
|
-
)
|
|
613
|
-
# anti-ram includes spin-phase interval [0.5, 1)
|
|
614
|
-
# which is the second half of the spin_angle_bins
|
|
615
|
-
elif spin_phase == "anti-ram":
|
|
616
|
-
self.data = self.data.isel(
|
|
617
|
-
spin_angle_bin=slice(self.data["spin_angle_bin"].data.size // 2, None)
|
|
618
|
-
)
|
|
663
|
+
ram_mask[slice(0, self.data["spin_angle_bin"].data.size // 2)] = True
|
|
664
|
+
self.data["ram_mask"] = ram_mask
|
|
619
665
|
|
|
620
666
|
# Rename some PSET vars to match L2 variables
|
|
621
667
|
self.data = self.data.rename(
|
|
@@ -631,16 +677,11 @@ class HiPointingSet(PointingSet):
|
|
|
631
677
|
self.data["exposure_factor"], self.data["epoch"].values[0]
|
|
632
678
|
)
|
|
633
679
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
np.squeeze(self.data["hae_longitude"]),
|
|
637
|
-
np.squeeze(self.data["hae_latitude"]),
|
|
638
|
-
)
|
|
639
|
-
)
|
|
640
|
-
self.spatial_coords = ("spin_angle_bin",)
|
|
680
|
+
# Update az_el_points using the base class method
|
|
681
|
+
self.update_az_el_points()
|
|
641
682
|
|
|
642
683
|
|
|
643
|
-
class LoPointingSet(
|
|
684
|
+
class LoPointingSet(LoHiBasePointingSet):
|
|
644
685
|
"""
|
|
645
686
|
PointingSet object specific to Lo L1C PSet data.
|
|
646
687
|
|
|
@@ -653,15 +694,11 @@ class LoPointingSet(PointingSet):
|
|
|
653
694
|
def __init__(self, dataset: xr.Dataset):
|
|
654
695
|
super().__init__(dataset, spice_reference_frame=geometry.SpiceFrame.IMAP_HAE)
|
|
655
696
|
|
|
656
|
-
# The HAE centers are stored in the pset as (1, 3600, 40) arrays
|
|
657
|
-
self.az_el_points = np.column_stack(
|
|
658
|
-
(
|
|
659
|
-
np.squeeze(self.data["hae_longitude"]).values.ravel(),
|
|
660
|
-
np.squeeze(self.data["hae_latitude"]).values.ravel(),
|
|
661
|
-
)
|
|
662
|
-
)
|
|
663
697
|
self.spatial_coords = ("spin_angle", "off_angle")
|
|
664
698
|
|
|
699
|
+
# Update az_el_points using the base class method
|
|
700
|
+
self.update_az_el_points()
|
|
701
|
+
|
|
665
702
|
|
|
666
703
|
# Define the Map classes
|
|
667
704
|
class AbstractSkyMap(ABC):
|
|
@@ -694,9 +731,10 @@ class AbstractSkyMap(ABC):
|
|
|
694
731
|
max_epoch: int
|
|
695
732
|
|
|
696
733
|
# ======== Attributes required to be set in a subclass ========
|
|
697
|
-
# Azimuth and elevation coordinates of each spatial pixel. The
|
|
698
|
-
# have the shape (n, 2) where n is the number of spatial pixels
|
|
699
|
-
|
|
734
|
+
# Azimuth and elevation coordinates of each spatial pixel. The xarray.DataArray
|
|
735
|
+
# should have the shape (n, 2) where n is the number of spatial pixels.
|
|
736
|
+
# Always a simple numpy array for maps (no need for multi-dimensional coords).
|
|
737
|
+
az_el_points: xr.DataArray
|
|
700
738
|
# Type of sky tiling
|
|
701
739
|
tiling_type: SkyTilingType
|
|
702
740
|
# Dictionary of xr.DataArray objects for each non-spatial coordinate in the SkyMap
|
|
@@ -763,12 +801,12 @@ class AbstractSkyMap(ABC):
|
|
|
763
801
|
"""
|
|
764
802
|
return self.az_el_points.shape[0]
|
|
765
803
|
|
|
766
|
-
def project_pset_values_to_map(
|
|
804
|
+
def project_pset_values_to_map( # noqa: PLR0912
|
|
767
805
|
self,
|
|
768
806
|
pointing_set: PointingSet,
|
|
769
807
|
value_keys: list[str] | None = None,
|
|
770
808
|
index_match_method: IndexMatchMethod = IndexMatchMethod.PUSH,
|
|
771
|
-
pset_valid_mask: NDArray | None = None,
|
|
809
|
+
pset_valid_mask: NDArray | xr.DataArray | None = None,
|
|
772
810
|
) -> None:
|
|
773
811
|
"""
|
|
774
812
|
Project a pointing set's values to the map grid.
|
|
@@ -790,7 +828,7 @@ class AbstractSkyMap(ABC):
|
|
|
790
828
|
index_match_method : IndexMatchMethod, optional
|
|
791
829
|
The method of index matching to use for all values.
|
|
792
830
|
Default is IndexMatchMethod.PUSH.
|
|
793
|
-
pset_valid_mask : NDArray, optional
|
|
831
|
+
pset_valid_mask : xarray.DataArray or NDArray, optional
|
|
794
832
|
A boolean mask of shape (number of pointing set pixels,) indicating
|
|
795
833
|
which pixels in the pointing set should be considered valid for projection.
|
|
796
834
|
If None, all pixels are considered valid. Default is None.
|
|
@@ -802,9 +840,9 @@ class AbstractSkyMap(ABC):
|
|
|
802
840
|
"""
|
|
803
841
|
if value_keys is None:
|
|
804
842
|
value_keys = list(pointing_set.data.data_vars.keys())
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
843
|
+
|
|
844
|
+
if missing_keys := set(value_keys) - set(pointing_set.data.data_vars):
|
|
845
|
+
raise KeyError(f"Value keys not found in pointing set: {missing_keys}")
|
|
808
846
|
|
|
809
847
|
if pset_valid_mask is None:
|
|
810
848
|
pset_valid_mask = np.ones(pointing_set.num_points, dtype=bool)
|
|
@@ -829,19 +867,14 @@ class AbstractSkyMap(ABC):
|
|
|
829
867
|
)
|
|
830
868
|
|
|
831
869
|
for value_key in value_keys:
|
|
832
|
-
|
|
870
|
+
if value_key not in pointing_set.data.data_vars:
|
|
871
|
+
raise ValueError(f"Value key {value_key} not found in pointing set.")
|
|
833
872
|
|
|
834
873
|
# If multiple spatial axes present
|
|
835
874
|
# (i.e (az, el) for rectangular coordinate PSET),
|
|
836
|
-
#
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
for key, size in pset_values.sizes.items()
|
|
840
|
-
if key not in pointing_set.spatial_coords
|
|
841
|
-
)
|
|
842
|
-
raveled_pset_data = pset_values.data.reshape(
|
|
843
|
-
*non_spatial_axes_shape,
|
|
844
|
-
pointing_set.num_points,
|
|
875
|
+
# stack them into a single coordinate to match the raveled indices
|
|
876
|
+
raveled_pset_data = pointing_set.data[value_key].stack(
|
|
877
|
+
{CoordNames.GENERIC_PIXEL.value: pointing_set.spatial_coords}
|
|
845
878
|
)
|
|
846
879
|
|
|
847
880
|
if value_key not in self.data_1d.data_vars:
|
|
@@ -864,11 +897,26 @@ class AbstractSkyMap(ABC):
|
|
|
864
897
|
if index_match_method is IndexMatchMethod.PUSH:
|
|
865
898
|
# Bin the values at the matched indices. There may be multiple
|
|
866
899
|
# pointing set pixels that correspond to the same sky map pixel.
|
|
900
|
+
# Broadcast all arrays together using xarray dimension alignment
|
|
901
|
+
data_bc, indices_bc = xr.broadcast(
|
|
902
|
+
raveled_pset_data, matched_indices_push
|
|
903
|
+
)
|
|
904
|
+
# If the valid mask is a xr.DataArray, broadcast it to the same shape
|
|
905
|
+
if isinstance(pset_valid_mask, xr.DataArray):
|
|
906
|
+
stacked_valid_mask = pset_valid_mask.stack(
|
|
907
|
+
{CoordNames.GENERIC_PIXEL.value: pointing_set.spatial_coords}
|
|
908
|
+
)
|
|
909
|
+
pset_valid_mask_bc, _ = xr.broadcast(data_bc, stacked_valid_mask)
|
|
910
|
+
pset_valid_mask_values = pset_valid_mask_bc.values
|
|
911
|
+
else:
|
|
912
|
+
pset_valid_mask_values = pset_valid_mask
|
|
913
|
+
|
|
914
|
+
# Extract numpy arrays for bincount operation
|
|
867
915
|
pointing_projected_values = map_utils.bin_single_array_at_indices(
|
|
868
|
-
value_array=
|
|
916
|
+
value_array=data_bc.values,
|
|
869
917
|
projection_grid_shape=self.binning_grid_shape,
|
|
870
|
-
projection_indices=
|
|
871
|
-
input_valid_mask=
|
|
918
|
+
projection_indices=indices_bc.values,
|
|
919
|
+
input_valid_mask=pset_valid_mask_values,
|
|
872
920
|
)
|
|
873
921
|
# TODO: we may need to allow for unweighted/weighted means here by
|
|
874
922
|
# dividing pointing_projected_values by some binned weights.
|
|
@@ -879,7 +927,7 @@ class AbstractSkyMap(ABC):
|
|
|
879
927
|
valid_map_mask = pset_valid_mask[matched_indices_pull]
|
|
880
928
|
# We know that there will only be one value per sky map pixel,
|
|
881
929
|
# so we can use the matched indices directly
|
|
882
|
-
pointing_projected_values = raveled_pset_data[
|
|
930
|
+
pointing_projected_values = raveled_pset_data.values[
|
|
883
931
|
..., matched_indices_pull[valid_map_mask]
|
|
884
932
|
]
|
|
885
933
|
# TODO: we may need to allow for unweighted/weighted means here by
|
|
@@ -889,10 +937,6 @@ class AbstractSkyMap(ABC):
|
|
|
889
937
|
self.data_1d[value_key].values[..., valid_map_mask] += (
|
|
890
938
|
pointing_projected_values
|
|
891
939
|
)
|
|
892
|
-
else:
|
|
893
|
-
raise NotImplementedError(
|
|
894
|
-
"Only PUSH and PULL index matching methods are supported."
|
|
895
|
-
)
|
|
896
940
|
|
|
897
941
|
# TODO: The max epoch needs to include the pset duration. Right now it
|
|
898
942
|
# is just capturing the start epoch. See issue #1747
|
|
@@ -1120,7 +1164,10 @@ class RectangularSkyMap(AbstractSkyMap):
|
|
|
1120
1164
|
el_points = self.sky_grid.el_grid.ravel()
|
|
1121
1165
|
|
|
1122
1166
|
# Stack so axis 0 is different pixels, and axis 1 is (az, el) of the pixel
|
|
1123
|
-
self.az_el_points =
|
|
1167
|
+
self.az_el_points = xr.DataArray(
|
|
1168
|
+
np.column_stack((az_points, el_points)),
|
|
1169
|
+
dims=[CoordNames.GENERIC_PIXEL.value, CoordNames.AZ_EL_VECTOR.value],
|
|
1170
|
+
)
|
|
1124
1171
|
|
|
1125
1172
|
# Calculate solid angles of each pixel in the map grid in units of steradians
|
|
1126
1173
|
self.solid_angle_grid = spatial_utils.build_solid_angle_map(
|
|
@@ -1218,12 +1265,13 @@ class RectangularSkyMap(AbstractSkyMap):
|
|
|
1218
1265
|
coords={**self.non_spatial_coords, **self.spatial_coords},
|
|
1219
1266
|
)
|
|
1220
1267
|
|
|
1221
|
-
def build_cdf_dataset(
|
|
1268
|
+
def build_cdf_dataset( # noqa: PLR0912
|
|
1222
1269
|
self,
|
|
1223
1270
|
instrument: str,
|
|
1224
1271
|
level: str,
|
|
1225
1272
|
descriptor: str,
|
|
1226
1273
|
sensor: str | None = None,
|
|
1274
|
+
drop_vars_with_no_attributes: bool = True,
|
|
1227
1275
|
) -> xr.Dataset:
|
|
1228
1276
|
"""
|
|
1229
1277
|
Format the data into a xarray.Dataset and add required CDF variables.
|
|
@@ -1238,6 +1286,12 @@ class RectangularSkyMap(AbstractSkyMap):
|
|
|
1238
1286
|
Descriptor for filename.
|
|
1239
1287
|
sensor : str, optional
|
|
1240
1288
|
Sensor number "45" or "90".
|
|
1289
|
+
drop_vars_with_no_attributes : bool, optional
|
|
1290
|
+
Default behavior is to drop any dataset variables that don't have
|
|
1291
|
+
attributes defined in the CDF attribute manager. This ensures that
|
|
1292
|
+
the output CDF doesn't have any of the intermedeiate variables left
|
|
1293
|
+
over from computations. Sometimes, it is useful to output the
|
|
1294
|
+
intermedeiate variables. To do so, set this to False.
|
|
1241
1295
|
|
|
1242
1296
|
Returns
|
|
1243
1297
|
-------
|
|
@@ -1267,7 +1321,7 @@ class RectangularSkyMap(AbstractSkyMap):
|
|
|
1267
1321
|
if ("L2" in name)
|
|
1268
1322
|
]
|
|
1269
1323
|
l2_coords.append(CoordNames.TIME.value)
|
|
1270
|
-
for map_coord in cdf_ds.dims
|
|
1324
|
+
for map_coord in cdf_ds.dims:
|
|
1271
1325
|
if map_coord not in l2_coords:
|
|
1272
1326
|
cdf_ds = cdf_ds.drop_dims(map_coord)
|
|
1273
1327
|
|
|
@@ -1341,13 +1395,18 @@ class RectangularSkyMap(AbstractSkyMap):
|
|
|
1341
1395
|
variable_name=name,
|
|
1342
1396
|
check_schema=check_schema,
|
|
1343
1397
|
)
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1398
|
+
cdf_ds[name].attrs.update(var_attrs)
|
|
1399
|
+
except KeyError:
|
|
1400
|
+
if drop_vars_with_no_attributes:
|
|
1401
|
+
logger.debug(
|
|
1402
|
+
f"Dropping variable '{name}' that has no attributes defined."
|
|
1403
|
+
)
|
|
1404
|
+
cdf_ds = cdf_ds.drop_vars(name)
|
|
1405
|
+
else:
|
|
1406
|
+
logger.debug(
|
|
1407
|
+
f"Variable '{name}' has no attributes defined. It will "
|
|
1408
|
+
f"be included in the output dataset with no attributes."
|
|
1409
|
+
)
|
|
1351
1410
|
|
|
1352
1411
|
# Manually adjust epoch attributes
|
|
1353
1412
|
cdf_ds["epoch"].attrs.update(
|
|
@@ -1431,7 +1490,10 @@ class HealpixSkyMap(AbstractSkyMap):
|
|
|
1431
1490
|
nside=nside, ipix=np.arange(hp.nside2npix(nside)), nest=nested, lonlat=True
|
|
1432
1491
|
)
|
|
1433
1492
|
# Stack so axis 0 is different pixels, and axis 1 is (az, el) of the pixel
|
|
1434
|
-
self.az_el_points =
|
|
1493
|
+
self.az_el_points = xr.DataArray(
|
|
1494
|
+
np.column_stack((pixel_az, pixel_el)),
|
|
1495
|
+
dims=[CoordNames.GENERIC_PIXEL.value, CoordNames.AZ_EL_VECTOR.value],
|
|
1496
|
+
)
|
|
1435
1497
|
|
|
1436
1498
|
self.spatial_coords = {
|
|
1437
1499
|
CoordNames.HEALPIX_INDEX.value: xr.DataArray(
|
|
@@ -1767,7 +1829,7 @@ class HealpixSkyMap(AbstractSkyMap):
|
|
|
1767
1829
|
value_array=healpix_values_array,
|
|
1768
1830
|
max_subdivision_depth=max_subdivision_depth,
|
|
1769
1831
|
)
|
|
1770
|
-
for lon_lat in rect_map.az_el_points
|
|
1832
|
+
for lon_lat in rect_map.az_el_points.values
|
|
1771
1833
|
]
|
|
1772
1834
|
|
|
1773
1835
|
# Separate the best value and the recursion depth for each pixel
|
|
@@ -18,3 +18,8 @@ class CoordNames(Enum):
|
|
|
18
18
|
ELEVATION_L1C = "latitude"
|
|
19
19
|
AZIMUTH_L2 = "longitude"
|
|
20
20
|
ELEVATION_L2 = "latitude"
|
|
21
|
+
|
|
22
|
+
# Common name for dimension along azimuth/elevation vector
|
|
23
|
+
AZ_EL_VECTOR = "az_el"
|
|
24
|
+
# Commoon name for dimension along Cartesian vector
|
|
25
|
+
CARTESIAN_VECTOR = "x_y_z"
|