Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NDCollection 2 #238

Merged
merged 61 commits into from
Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
cd76e2d
First draft of a ndcube collection class.
DanRyanIrish Feb 3, 2020
eb10cb8
Add notion of aligned axes to NDCollection.
DanRyanIrish Feb 4, 2020
2530e97
First draft supporting aligned axes in NDCollection slicing.
DanRyanIrish Feb 4, 2020
808a1ff
Fix some NDCollection slicing bugs.
DanRyanIrish Feb 17, 2020
6791f91
Changed variable name labels to keys.
DanRyanIrish Feb 19, 2020
574e341
Fix some more slicing bugs.
DanRyanIrish Feb 19, 2020
1c75187
Move sanitize_aligned_axes function to collection utils.
DanRyanIrish Feb 20, 2020
cc61a96
Add axis length check to NDCollection init.
DanRyanIrish Mar 6, 2020
47eae5e
Refactor NDCollection to inherit from dict.
DanRyanIrish Mar 6, 2020
72528ad
Checks if 2 NDCollections are equal.
DanRyanIrish Mar 7, 2020
f98a498
Fixes some NDCollection slicing bugs.
DanRyanIrish Mar 8, 2020
7a5d6a8
Make NDCollection aligned_axes optional.
DanRyanIrish Mar 8, 2020
5a3ee64
Fixes bug in NDCollection.pop().
DanRyanIrish Mar 8, 2020
c416510
Adds tests for NDCollection.
DanRyanIrish Mar 8, 2020
b34b651
Fixes PEP8 issues.
DanRyanIrish Mar 8, 2020
bbbf2f7
Fixes a string formatting bug.
DanRyanIrish Mar 9, 2020
a17551b
Merge branch 'master' of https://github.com/sunpy/ndcube into ndcolle…
DanRyanIrish Mar 9, 2020
4026c11
Adds changelog for PR #231.
DanRyanIrish Mar 10, 2020
2e670c3
Removes unused util function.
DanRyanIrish Mar 11, 2020
481cade
Apply suggestions from code review
DanRyanIrish Mar 18, 2020
f99a7eb
Changes a default to lower case.
DanRyanIrish Mar 18, 2020
a597000
Merge branch 'ndcollection' of https://github.com/DanRyanIrish/ndcube…
DanRyanIrish Mar 18, 2020
bfe1737
Adds more minor changes from code review.
DanRyanIrish Mar 18, 2020
3a90724
Removes data type enforcement in of cubes in NDCollection.
DanRyanIrish Mar 18, 2020
0e2ec29
Tidies NDCollection repr.
DanRyanIrish Mar 18, 2020
6860cc5
Fixes bug in checking NDCollection aligned_axes = all.
DanRyanIrish Mar 18, 2020
afb7895
Merge branch 'master' of https://github.com/sunpy/ndcube into HEAD
DanRyanIrish Mar 18, 2020
9d374c0
1st attempt at NDCollection init refactor.
DanRyanIrish Mar 19, 2020
83f5a3c
Fixes NDCollection init refactor.
DanRyanIrish Mar 19, 2020
67ba030
Adds changelog file for PR 238.
DanRyanIrish Mar 19, 2020
681dc26
Renames NDCollection.update to add_to_collection.
DanRyanIrish Mar 19, 2020
af95903
Implement dict-like update method on NDCollection.
DanRyanIrish Mar 19, 2020
7871504
Converts NDCollection._first_key to a property.
DanRyanIrish Mar 19, 2020
c62f36a
Extends NDCollection testing to include NDCubeSequence components.
DanRyanIrish Mar 19, 2020
5f0544e
PEP8 fixes.
DanRyanIrish Mar 20, 2020
edb94d0
Fix bug when NDCollection.aligned_axes is None.
DanRyanIrish Mar 20, 2020
999c69c
Change NDCollection aligned_axes default to None.
DanRyanIrish Mar 20, 2020
624213b
Changed NDCollection input to key-value pairs like dict.
DanRyanIrish Mar 20, 2020
f667049
Merge branch 'master' of https://github.com/sunpy/ndcube into ndcolle…
DanRyanIrish Mar 20, 2020
4f2d30a
Changed NDCollection.add_to_collection API to take key/value pair.
DanRyanIrish Mar 20, 2020
f0c3f1c
Unpin python version in tox.
DanRyanIrish Mar 20, 2020
a49ac2f
Apply suggestions from code review
DanRyanIrish Mar 20, 2020
de4c6cc
Fixes typo.
DanRyanIrish Mar 20, 2020
bde141e
Adds NDCollection guide to docs.
DanRyanIrish Mar 20, 2020
398094b
Merge branch 'ndcollection2' of https://github.com/DanRyanIrish/ndcub…
DanRyanIrish Mar 20, 2020
3b3bb2e
Adds missing import.
DanRyanIrish Mar 20, 2020
5587d0b
Adds docstrings to some NDCollection methods.
DanRyanIrish Mar 20, 2020
da2227d
Makes changes suggested in code review.
DanRyanIrish Mar 20, 2020
4c4d96e
Incorporated NDCollection.add_to_collection into NDCollection.update.
DanRyanIrish Mar 20, 2020
041c836
Makes NDCollection setitem raise NotImplementedError.
DanRyanIrish Mar 20, 2020
b68c502
Moves _sanitize_aligned_axes to collection utils.
DanRyanIrish Mar 20, 2020
d750792
Remove NDCollection.pop docstring so can be inherited from dict.
DanRyanIrish Mar 20, 2020
905c863
Changes NDCollection repr to __str__.
DanRyanIrish Mar 26, 2020
17e206a
Skip unnecessary doc test.
DanRyanIrish Mar 27, 2020
ac6b993
Merge branch 'master' of https://github.com/sunpy/ndcube into ndcolle…
DanRyanIrish Mar 27, 2020
5441e3b
Apply suggestions from code review
DanRyanIrish Mar 27, 2020
25deb91
Check invalid kwargs arent supplied to NDCollection.
DanRyanIrish Mar 27, 2020
6676139
Update NDCollection __str__.
DanRyanIrish Mar 27, 2020
596c9f4
Merge branch 'ndcollection2' of https://github.com/DanRyanIrish/ndcub…
DanRyanIrish Mar 27, 2020
2a812f5
Update ndcube/ndcollection.py
DanRyanIrish Mar 27, 2020
3062fd4
Bug fixes to fix tests.
DanRyanIrish Mar 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/238.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add new NDCollection class for linking and manipulating partially or non-aligned NDCubes or NDCubeSequences.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ by a WCS (World Coordinate System) translation.
installation
ndcube
ndcubesequence
ndcollection
contributing
getting_help
api
Expand Down
201 changes: 201 additions & 0 deletions docs/ndcollection.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
.. _ndcollection:

============
NDCollection
============

`~ndcube.NDCollection` is a container class for grouping `~ndcube.NDCube` or
`~ndcube.NDCubeSequence` instances together.
It does not imply an ordered relationship between its constituent ND objects
like `~ndcube.NDCubeSequence`.
Instead it links ND objects in an unordered way like a Python dictionary.
This has many possible uses, for example, linking observations with derived
data products.

Let's say we have a 3D `~ndcube.NDCube` representing space-space-wavelength.
Then let's say we fit a spectral line in each pixel's spectrum and extract
its linewidth.
Now we have a 2D spatial map of linewidth with the same spatial axes
as the original 3D cube.
However the physical properties represented by the data are different.
They do not have an order within their common coordinate space.
And they do not have the same dimensionality as the 2nd cube's spectral axis
has been collapsed.
Therefore is it not appropriate to combine them in an `~ndcube.NDCubeSequence`.
This is where `~ndcube.NDCollection` comes in handy.
It allows us to name each ND object and combine them into a single container,
just like a dictionary.
In fact `~ndcube.NDCollection` inherits from `dict`.

Initialization
--------------
To see how we initialize an `~ndcube.NDCollection`, let's first define a couple
of `~ndcube.NDCube` instances representing the situation above, i.e. a 3D
space-space-spectral cube and a 2D space-space cube that share spatial axes.
Let there be 10x20 spatial pixels and 30 pixels along the spectral axis.

.. code-block:: python

>>> import numpy as np
>>> from astropy.wcs import WCS
>>> from ndcube import NDCube

>>> # Define observations NDCube.
>>> data = np.ones((10, 20, 30)) # dummy data
>>> obs_wcs_dict = {
... 'CTYPE1': 'WAVE ', 'CUNIT1': 'Angstrom', 'CDELT1': 0.2, 'CRPIX1': 0, 'CRVAL1': 10, 'NAXIS1': 30,
... 'CTYPE2': 'HPLT-TAN', 'CUNIT2': 'deg', 'CDELT2': 0.5, 'CRPIX2': 2, 'CRVAL2': 0.5, 'NAXIS2': 20,
... 'CTYPE3': 'HPLN-TAN', 'CUNIT3': 'deg', 'CDELT3': 0.4, 'CRPIX3': 2, 'CRVAL3': 1, 'NAXIS3': 10}
>>> obs_wcs = WCS(obs_wcs_dict)
>>> obs_cube = NDCube(data, obs_wcs)

>>> # Define derived linewidth NDCube
>>> linewidth_data = np.ones((10, 20)) / 2 # dummy data
>>> linewidth_wcs_dict = {
... 'CTYPE1': 'HPLT-TAN', 'CUNIT1': 'deg', 'CDELT1': 0.5, 'CRPIX1': 2, 'CRVAL1': 0.5, 'NAXIS1': 20,
... 'CTYPE2': 'HPLN-TAN', 'CUNIT2': 'deg', 'CDELT2': 0.4, 'CRPIX2': 2, 'CRVAL2': 1, 'NAXIS2': 10}
>>> linewidth_wcs = WCS(linewidth_wcs_dict)
>>> linewidth_cube = NDCube(linewidth_data, linewidth_wcs)

Combine these ND objects into an `~ndcube.NDCollection` by supplying a sequence of
``(key, value)`` pairs in the same way that you initialize and dictionary.

.. code-block:: python

>>> from ndcube import NDCollection
>>> my_collection = NDCollection([("observations", obs_cube), ("linewidths", linewidth_cube)])

Data Access
-----------

Key Access
**********
To access each ND object in ``my_collection`` we can index with the name of the desired object,
just like a `dict`:

.. code-block:: python

>>> my_collection["observations"] # doctest: +SKIP

And just like a `dict` we can see the different names available using the ``keys`` method:

.. code-block:: python

>>> my_collection.keys()
dict_keys(['observations', 'linewidths'])

Aligned Axes & Slicing
**********************

Aligned Axes
^^^^^^^^^^^^

`~ndcube.NDCollection` is more powerful than a simple dictionary because it
allows us to link common aligned axes between the ND objects.
In our example above, the linewidth object's axes are aligned with the
first two axes of observation object. Let's instantiate our collection again,
but this time declare those axes to be aligned.

.. code-block:: python

>>> my_collection = NDCollection(
... [("observations", obs_cube), ("linewidths", linewidth_cube)], aligned_axes=(0, 1))

We can see which axes are aligned by inpecting the ``aligned_axes`` attribute:

.. code-block:: python

>>> my_collection.aligned_axes
{'observations': (0, 1), 'linewidths': (0, 1)}

As you can see, this gives us the aligned axes for each ND object separately.
We should read this as the 0th axes of both ND objects are aligned, as are the
1st axes of both objects.
Because each ND object's set of aligned axes is stored separately,
aligned axes do not have to be in the same order in both objects.
Let's say we reversed the axes of our ``linewidths`` ND object for some reason:

.. code-block:: python

>>> linewidth_wcs_dict_reversed = {
... 'CTYPE2': 'HPLT-TAN', 'CUNIT2': 'deg', 'CDELT2': 0.5, 'CRPIX2': 2, 'CRVAL2': 0.5, 'NAXIS2': 20,
... 'CTYPE1': 'HPLN-TAN', 'CUNIT1': 'deg', 'CDELT1': 0.4, 'CRPIX1': 2, 'CRVAL1': 1, 'NAXIS1': 10}
>>> linewidth_wcs_reversed = WCS(linewidth_wcs_dict_reversed)
>>> linewidth_cube_reversed = NDCube(linewidth_data.transpose(), linewidth_wcs_reversed)

We can still define an `~ndcube.NDCollection` with aligned axes by supplying
a tuple of tuples, giving the aligned axes of each ND object separately.
In this case, the 0th axis of the ``observations`` object is aligned with the 1st
axis of the ``linewidths`` object and vice versa.

.. code-block:: python

>>> my_collection_reversed = NDCollection(
... [("observations", obs_cube), ("linewidths", linewidth_cube_reversed)],
... aligned_axes=((0, 1), (1, 0)))
>>> my_collection_reversed.aligned_axes
{'observations': (0, 1), 'linewidths': (1, 0)}

Aligned axes must have the same lengths.
We can see the lengths of the aligned axes by using the ``aligned_dimensions``
property.

.. code-block:: python

>>> my_collection.aligned_dimensions
<Quantity [10., 20.] pix>

Note that this only tells us the lengths of the aligned axes. To see the
lengths of the non-aligned axes, e.g. the spectral axis of the ``observations``
object, you must inspect that ND object individually.

We can also see the physical properties to which the aligned axes correspond
by using the ``aligned_world_axis_physical_types`` property.

.. code-block:: python

>>> my_collection.aligned_world_axis_physical_types
('custom:pos.helioprojective.lon', 'custom:pos.helioprojective.lat')

Note that this method simply returns the world physical axis types of one of
the ND objects. However, there is no requirement that all aligned axes must
represent the same physical types.
They just have to be the same length.

Slicing
^^^^^^^

Defining aligned axes enables us to slice those axes of all the ND objects in
the collection by using the standard Python slicing API.

.. code-block:: python

>>> sliced_collection = my_collection[1:3, 3:8]
>>> sliced_collection.keys()
dict_keys(['observations', 'linewidths'])
>>> sliced_collection.aligned_dimensions
<Quantity [2., 5.] pix>

Note that we still have the same number of ND objects, but both have
been sliced using the inputs provided by the user.
Also note that slicing takes account of and updates the aligned axis information.
Therefore a self-consistent result would be obtained even if the aligned axes
are not in order.

.. code-block:: python

>>> sliced_collection_reversed = my_collection_reversed[1:3, 3:8]
>>> sliced_collection_reversed.keys()
dict_keys(['observations', 'linewidths'])
>>> sliced_collection_reversed.aligned_dimesions
<Quantity [10., 20.] pix>

Editing NDCollection
--------------------

Because `~ndcube.NDCollection` inherits from `dict`, we can edit the
collection using many of the same methods.
These have the same or analagous APIs to the ``dict`` versions and
include ``del``, `~ndcube.NDCollection.pop`, and `~ndcube.NDCollection.update`.
Some `dict` methods may not be implemented on `~ndcube.NDCollection`
if they are note consistent with its design.
DanRyanIrish marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion ndcube/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

__minimum_python_version__ = "3.6"

__all__ = ['NDCube', 'NDCubeSequence']
__all__ = ['NDCube', 'NDCubeSequence', "NDCollection"]


class UnsupportedPythonError(Exception):
Expand All @@ -28,3 +28,4 @@ class UnsupportedPythonError(Exception):
# For egg_info test builds to pass, put package imports here.
from .ndcube import NDCube, NDCubeOrdered
from .ndcube_sequence import NDCubeSequence
from .ndcollection import NDCollection
Loading