Repository: sunpy/ndcube

Issue:
Validate WCS Function
<!--
We know asking good questions takes effort, and we appreciate your time.
Thank you.

Please be aware that everyone has to follow our code of conduct:
https://sunpy.org/coc

These comments are hidden when you submit this github issue.

Please have a search on our GitHub repository to see if a similar issue has already been posted.
If a similar issue is closed, have a quick look to see if you are satisfied by the resolution.
If not please go ahead and open an issue!
-->

### Description

<!--
Provide a general description of the feature you would like.
If you prefer, you can also suggest a draft design or API.
-->
Create a function that accepts two WCS objects, `target_wcs` and `initial_wcs`, and verifies that `target` is the same/equivalent to `initial`.  (Better variable names here are welcome.)

Initially, the `target_wcs` must describe all axes of the `initial_wcs`.  Future versions of this function should handle invariant axes.

The use case for this function is as part of verifying the input to the future `NDCube.resample` method.


Discussion:
pep8speaks: Hello @adwaitbhope! Thanks for updating this PR. 

* In the file [`ndcube/tests/test_utils_wcs.py`](https://github.com/sunpy/ndcube/blob/3b4a8b6213ce1692d8c69b3b012a6af26edd909a/ndcube/tests/test_utils_wcs.py):

> [Line 167:30](https://github.com/sunpy/ndcube/blob/3b4a8b6213ce1692d8c69b3b012a6af26edd909a/ndcube/tests/test_utils_wcs.py#L167): [E712](https://duckduckgo.com/?q=pep8%20E712) comparison to False should be 'if cond is False:' or 'if not cond:'
> [Line 164:30](https://github.com/sunpy/ndcube/blob/3b4a8b6213ce1692d8c69b3b012a6af26edd909a/ndcube/tests/test_utils_wcs.py#L164): [E712](https://duckduckgo.com/?q=pep8%20E712) comparison to True should be 'if cond is True:' or 'if cond:'




adwaitbhope: I've added test cases for this function, but for the positive test case, we have the same object as source and target. Should we introduce another WCS in the fixtures?
DanRyanIrish: > Should we introduce another WCS in the fixtures?

I don't think it's necessary at this stage.  The test as is adequately tests the current implementation.  If/when this function moves to a more complicated implementation, adapting the test should be part of the work.  Adding a new fixture would be part of that.

Patch:
diff --git a/changelog/433.trivial.rst b/changelog/433.trivial.rst
new file mode 100644
index 0000000..3ae9cab
--- /dev/null
+++ b/changelog/433.trivial.rst
@@ -0,0 +1,1 @@
+Adds a function to compare the physical types of two WCS objects.
diff --git a/ndcube/utils/wcs.py b/ndcube/utils/wcs.py
index 25a0e47..228fc24 100644
--- a/ndcube/utils/wcs.py
+++ b/ndcube/utils/wcs.py
@@ -8,7 +8,7 @@ import numbers
 from collections import UserDict
 
 import numpy as np
-from astropy.wcs.wcsapi import low_level_api
+from astropy.wcs.wcsapi import BaseHighLevelWCS, BaseLowLevelWCS, low_level_api
 
 __all__ = ['array_indices_for_world_objects', 'convert_between_array_and_pixel_axes',
            'calculate_world_indices_from_axes', 'wcs_ivoa_mapping',
@@ -429,3 +429,51 @@ def array_indices_for_world_objects(wcs, axes=None):
         array_index = convert_between_array_and_pixel_axes(pixel_index, wcs.pixel_n_dim)
         array_indices[oinds] = tuple(array_index[::-1])  # Invert from pixel order to array order
     return tuple(ai for ai in array_indices if ai)
+
+
+def get_low_level_wcs(wcs, name='wcs'):
+    """
+    Returns a low level WCS object from a low level or high level WCS.
+
+    Parameters
+    ----------
+    wcs: `astropy.wcs.wcsapi.BaseHighLevelWCS` or `astropy.wcs.wcsapi.BaseLowLevelWCS`
+        The input WCS for getting the low level WCS object.
+
+    name: `str`, optional
+        Any name for the wcs to be used in the exception that could be raised.
+
+    Returns
+    -------
+    wcs: `astropy.wcs.wcsapi.BaseLowLevelWCS`
+    """
+
+    if isinstance(wcs, BaseHighLevelWCS):
+        return wcs.low_level_wcs
+    elif isinstance(wcs, BaseLowLevelWCS):
+        return wcs
+    else:
+        raise(f'{name} must implement either BaseHighLevelWCS or BaseLowLevelWCS')
+
+
+def compare_wcs_physical_types(source_wcs, target_wcs):
+    """
+    Checks to see if two WCS objects have the same physical types in the same order.
+
+    Parameters
+    ----------
+    source_wcs : `astropy.wcs.wcsapi.BaseHighLevelWCS` or `astropy.wcs.wcsapi.BaseLowLevelWCS`
+        The WCS which is currently in use, usually `self.wcs`.
+
+    target_wcs : `astropy.wcs.wcsapi.BaseHighLevelWCS` or `astropy.wcs.wcsapi.BaseLowLevelWCS`
+        The WCS object on which the NDCube is to be reprojected.
+
+    Returns
+    -------
+    result : `bool`
+    """
+
+    source_wcs = get_low_level_wcs(source_wcs, 'source_wcs')
+    target_wcs = get_low_level_wcs(target_wcs, 'target_wcs')
+
+    return source_wcs.world_axis_physical_types == target_wcs.world_axis_physical_types


Test patch:
diff --git a/ndcube/utils/tests/test_utils_wcs.py b/ndcube/utils/tests/test_utils_wcs.py
index 9361bea..6d811a8 100644
--- a/ndcube/utils/tests/test_utils_wcs.py
+++ b/ndcube/utils/tests/test_utils_wcs.py
@@ -157,3 +157,8 @@ def test_array_indices_for_world_objects_2(wcs_4d_lt_t_l_ln):
     array_indices = utils.wcs.array_indices_for_world_objects(wcs_4d_lt_t_l_ln, ('lon', 'time'))
     assert len(array_indices) == 2
     assert array_indices == ((0, 3), (2,))
+
+
+def test_compare_wcs_physical_types(wcs_4d_t_l_lt_ln, wcs_3d_l_lt_ln):
+    assert utils.wcs.compare_wcs_physical_types(wcs_4d_t_l_lt_ln, wcs_4d_t_l_lt_ln) is True
+    assert utils.wcs.compare_wcs_physical_types(wcs_4d_t_l_lt_ln, wcs_3d_l_lt_ln) is False