Repository: RadioAstronomySoftwareGroup/pyradiosky

Issue:
healpix should support either ring or nested ordering
Currently there is no order parameter and it is apparently assumed to be ring (according to @aelanman). Order should be a required parameter for healpix type objects.

Comments:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6402c66..50163c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,11 +3,13 @@
 ## [Unreleased]
 
 ### Added
+- Support for ring / nested healpix ordering.
 - New `SkyModel.concat` method to support concatenating catalogs.
 - A new optional parameters `stokes_error` to track errors on the fluxes reported by catalogs.
 
 ### Fixed
 - Some bugs related to writing & reading skyh5 files after converting object using `healpix_to_point` method.
+
 ## [0.1.1] - 2021-02-17
 
 ### Added
diff --git a/pyradiosky/skymodel.py b/pyradiosky/skymodel.py
index 7c5ce17..4a00dd9 100644
--- a/pyradiosky/skymodel.py
+++ b/pyradiosky/skymodel.py
@@ -153,6 +153,9 @@ class SkyModel(UVBase):
         nside parameter for HEALPix maps.
     hpx_inds : array_like of int
         Indices for HEALPix maps, only used if nside is set.
+    hpx_order : str
+        For HEALPix maps, pixel ordering parameter. Can be "ring" or "nested".
+        Defaults to "ring" if unset in init keywords.
     extended_model_group : array_like of str
         Identifier that groups components of an extended source model.
         Empty string for point sources, shape (Ncomponents,).
@@ -192,6 +195,7 @@ class SkyModel(UVBase):
         nside=None,
         hpx_inds=None,
         stokes_error=None,
+        hpx_order=None,
         extended_model_group=None,
         beam_amp=None,
         history="",
@@ -262,8 +266,19 @@ class SkyModel(UVBase):
             expected_type=int,
             required=False,
         )
+        desc = (
+            "Healpix pixel ordering (ring or nested). Only required for HEALPix maps."
+        )
+        self._hpx_order = UVParameter(
+            "hpx_order",
+            description=desc,
+            value=None,
+            expected_type=str,
+            required=False,
+            acceptable_vals=["ring", "nested"],
+        )
 
-        desc = "Healpix index, only reqired for HEALPix maps. shape (Ncomponents,)"
+        desc = "Healpix indices, only required for HEALPix maps."
         self._hpx_inds = UVParameter(
             "hpx_inds",
             description=desc,
@@ -464,7 +479,7 @@ class SkyModel(UVBase):
             self._set_component_type_params("point")
 
         if self.component_type == "healpix":
-            req_args = ["nside", "hpx_inds", "stokes", "spectral_type"]
+            req_args = ["nside", "hpx_inds", "stokes", "spectral_type", "hpx_order"]
             args_set_req = [
                 nside is not None,
                 hpx_inds is not None,
@@ -508,16 +523,27 @@ class SkyModel(UVBase):
                 self.nside = nside
             if hpx_inds is not None:
                 self.hpx_inds = np.atleast_1d(hpx_inds)
+            if hpx_order is not None:
+                self.hpx_order = str(hpx_order).lower()
+
+                # Ensure that the value can be used in healpix_to_lonlat below.
+                self._hpx_order.check_acceptability()
 
             if self.component_type == "healpix":
-                self.Ncomponents = self.hpx_inds.size
                 try:
                     import astropy_healpix
                 except ImportError as e:
                     raise ImportError(
                         "The astropy-healpix module must be installed to use HEALPix methods"
                     ) from e
-                ra, dec = astropy_healpix.healpix_to_lonlat(hpx_inds, nside)
+
+                if self.hpx_order is None:
+                    self.hpx_order = "ring"
+
+                self.Ncomponents = self.hpx_inds.size
+                ra, dec = astropy_healpix.healpix_to_lonlat(
+                    hpx_inds, nside, order=self.hpx_order
+                )
                 self.ra = ra
                 self.dec = dec
 
@@ -3770,7 +3796,7 @@ def write_healpix_hdf5(filename, hpmap, indices, freqs, nside=None, history=None
                 fileobj.attrs[k] = d
 
 
-def healpix_to_sky(hpmap, indices, freqs):
+def healpix_to_sky(hpmap, indices, freqs, hpx_order="ring"):
     """
     Convert a healpix map in K to a set of point source components in Jy.
 
@@ -3785,6 +3811,9 @@ def healpix_to_sky(hpmap, indices, freqs):
         Corresponding HEALPix indices for hpmap.
     freqs : array_like, float
         Frequencies in Hz. Shape (Nfreqs)
+    hpx_order : str
+        HEALPix map ordering parameter: ring or nested.
+        Defaults to ring.
 
     Returns
     -------
@@ -3808,10 +3837,13 @@ def healpix_to_sky(hpmap, indices, freqs):
         "This function will be removed in version 0.2.0.",
         category=DeprecationWarning,
     )
+    hpx_order = str(hpx_order).lower()
+    if hpx_order not in ["ring", "nested"]:
+        raise ValueError("order must be 'nested' or 'ring'")
 
     nside = int(astropy_healpix.npix_to_nside(hpmap.shape[-1]))
 
-    ra, dec = astropy_healpix.healpix_to_lonlat(indices, nside)
+    ra, dec = astropy_healpix.healpix_to_lonlat(indices, nside, order=hpx_order)
     freq = Quantity(freqs, "hertz")
 
     stokes = Quantity(np.zeros((4, len(freq), len(indices))), "K")
@@ -3825,6 +3857,7 @@ def healpix_to_sky(hpmap, indices, freqs):
         freq_array=freq,
         nside=nside,
         hpx_inds=indices,
+        hpx_order=hpx_order,
     )
     return sky
 


Test patch:
diff --git a/pyradiosky/tests/test_skymodel.py b/pyradiosky/tests/test_skymodel.py
index d5d122b..cd9b717 100644
--- a/pyradiosky/tests/test_skymodel.py
+++ b/pyradiosky/tests/test_skymodel.py
@@ -1607,6 +1607,30 @@ def test_units_healpix_to_sky(healpix_data, healpix_disk_old):
     assert units.quantity.allclose(sky.stokes[0, 0], stokes[0])
 
 
+@pytest.mark.filterwarnings("ignore:This method reads an old 'healvis' style healpix")
+@pytest.mark.parametrize("hpx_order", ["none", "ring", "nested"])
+def test_order_healpix_to_sky(healpix_data, hpx_order):
+
+    inds = np.arange(healpix_data["npix"])
+    hmap_orig = np.zeros_like(inds)
+    hmap_orig[healpix_data["ipix_disc"]] = healpix_data["npix"] - 1
+    hmap_orig = np.repeat(hmap_orig[None, :], 10, axis=0)
+    with pytest.warns(
+        DeprecationWarning,
+        match="This function is deprecated, use `SkyModel.read_skyh5` or `SkyModel.read_healpix_hdf5` instead.",
+    ):
+        if hpx_order == "none":
+            with pytest.raises(ValueError, match="order must be 'nested' or 'ring'"):
+                sky = skymodel.healpix_to_sky(
+                    hmap_orig, inds, healpix_data["frequencies"], hpx_order=hpx_order
+                )
+        else:
+            sky = skymodel.healpix_to_sky(
+                hmap_orig, inds, healpix_data["frequencies"], hpx_order=hpx_order
+            )
+            assert sky.hpx_order == hpx_order
+
+
 @pytest.mark.filterwarnings("ignore:recarray flux columns will no longer be labeled")
 def test_healpix_recarray_loop(healpix_data, healpix_disk_new):
 
@@ -3059,3 +3083,37 @@ def test_skyh5_read_errors_oldstyle_healpix():
 def test_healpix_hdf5_read_errors_newstyle_healpix():
     with pytest.raises(ValueError, match="This is a skyh5 file"):
         SkyModel.from_healpix_hdf5(os.path.join(SKY_DATA_PATH, "healpix_disk.skyh5"))
+
+
+def test_hpx_ordering():
+    # Setting the hpx_order parameter
+    pytest.importorskip("astropy_healpix")
+    nside = 16
+    npix = 12 * nside ** 2
+    stokes = np.zeros((4, 1, npix)) * units.K
+
+    with pytest.raises(ValueError, match="order must be 'nested' or 'ring'"):
+        sky = SkyModel(
+            hpx_inds=np.arange(npix),
+            nside=nside,
+            hpx_order="none",
+            stokes=stokes,
+            spectral_type="flat",
+        )
+
+    sky = SkyModel(
+        hpx_inds=np.arange(npix),
+        nside=16,
+        hpx_order="Ring",
+        stokes=stokes,
+        spectral_type="flat",
+    )
+    assert sky.hpx_order == "ring"
+    sky = SkyModel(
+        hpx_inds=np.arange(npix),
+        nside=16,
+        hpx_order="NESTED",
+        stokes=stokes,
+        spectral_type="flat",
+    )
+    assert sky.hpx_order == "nested"