Code Monkey home page Code Monkey logo

ndcube's Introduction

ndcube

It is up to date, we promise Best code cov this side of mars join us on #ndcube:openastronom.org on matrix Go give them money SunPy

JOSS

ndcube is an open-source SunPy affiliated package for manipulating, inspecting and visualizing multi-dimensional contiguous and non-contiguous coordinate-aware data arrays.

It combines data, uncertainties, units, metadata, masking, and coordinate transformations into classes with unified slicing and generic coordinate transformations and plotting/animation capabilities. It is designed to handle data of any number of dimensions and axis types (e.g. spatial, temporal, spectral, etc.) whose relationship between the array elements and the real world can be described by World Coordinate System (WCS) translations.

Acknowledging ndcube

If you use ndcube is your work, we kindly ask you to acknowledge ndcube in your publications and presentations.

Installation

For detailed installation instructions, see the installation guide in the ndcube docs.

Getting Help

For more information or to ask questions about ndcube, check out:

Contributing

If you would like to get involved, check out the Newcomers Guide section of the sunpy docs. This shows how to get setup with a "sunpy" workflow but the same applies for ndcube, you will just need to replace sunpy with ndcube.

Help is always welcome so let us know what you like to work on, or check out the issues page for the list of known outstanding items.

Code of Conduct

When you are interacting with the SunPy community you are asked to follow our Code of Conduct.

ndcube's People

Contributors

abit2 avatar adwaitbhope avatar alrobbertz avatar aoife-maria avatar baptistepellorceastro avatar byrdie avatar cadair avatar ciaokitty avatar cyclingninja avatar danryanirish avatar dfm avatar dhomeier avatar dstansby avatar hayesla avatar jmbhughes avatar kate028 avatar mateoi avatar mbankovm avatar mihailbankov avatar mwest007 avatar nabobalis avatar pllim avatar pre-commit-ci[bot] avatar rosteen avatar shelbej avatar solardrew avatar wtbarnes avatar yashrsharma44 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ndcube's Issues

Remodel NDCubeSequence.common_axis_extra_coords on NDCubeSequence.sequence_axis_extra_coords

Currently NDCubeSequence.common_axis_extra_coords assumes that all cubes within the sequence have the same extra_coords and so gets the "full" list of extra_coords from the first cube in the sequence. By contrast, NDCubeSequence.common_axis_extra_coords more thoroughly checks all cubes to compile a record of all extra_coords. NDCubeSequence.common_axis_extra_coords should be remodelled to be as rigorous as NDCubeSequence.common_axis_extra_coords.

NDCube Should Always Have a Mask

NDCube should always have a mask so that operations can assume one exists without checking. If a mask is not supplied during instantiation, a False mask should be generated. This will make operations over NDCubeSequences not have to always check that a mask exists.

NDCubeSequence.plot() sometimes gives ValueError

Here is the case where I found this bug. There may be discussion about whether this is an ndcube or SunPy bug.

Assume we have an NDCubeSequence, x, of dimensions (<Quantity 25. pix>, <Quantity 8. pix>, <Quantity 548. pix>, <Quantity 333. pix>), and world axis types ('meta.obs.sequence', 'custom:pos.helioprojective.lon', 'custom:pos.helioprojective.lat', 'em.wl'). Let's slice the NDCubeSequence to create another one:
>>> y = x[:, :, 250]
So now y has dimensions (<Quantity 25. pix>, <Quantity 8. pix>, <Quantity 333. pix>), world axis type ('meta.obs.sequence', 'custom:pos.helioprojective.lon', 'em.wl') and missing axes in each sub-cube of [False, True, False].

Animating this sequence then gives the following error:

>>> y.plot()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-32-b608f375098f> in <module>()
----> 1 sg_data_slitsummed.data[window].plot()

~/sunpy_dev/ndcube/ndcube/ndcube_sequence.py in plot(self, *args, **kwargs)
    168 
    169     def plot(self, *args, **kwargs):
--> 170         i = ani.ImageAnimatorNDCubeSequence(self, *args, **kwargs)
    171         return i
    172 

~/sunpy_dev/ndcube/ndcube/visualization/animation.py in __init__(self, seq, wcs, **kwargs)
     67         data_concat = np.concatenate([ndcube.data for ndcube in seq.data], axis=0)
     68         super(ImageAnimatorNDCubeSequence, self).__init__(
---> 69             data_concat, wcs=self.sequence[0].wcs, **kwargs)
     70 
     71     def update_plot(self, val, im, slider):

~/sunpy_dev/sunpy/sunpy/visualization/imageanimator.py in __init__(self, data, wcs, image_axes, unit_x_axis, unit_y_axis, axis_ranges, **kwargs)
    949             raise ValueError("wcs data should be provided.")
    950         if wcs.wcs.naxis is not data.ndim:
--> 951             raise ValueError("Dimensions of data and wcs not matching")
    952         self.wcs = wcs
    953         list_slices_wcsaxes = [0 for i in range(self.wcs.naxis)]

ValueError: Dimensions of data and wcs not matching

I believe this is something to do with the existence of missing axes causing the number of data dimensions to not equal the number of wcs dimensions.

Method to extract sub-NDCube using physical coords

A method enabling the user to define a region of interest based on a range of physical units.

  • The ranges should be input as astropy Quantities with units consistent with the WCS axes.
  • The ranges should be entered in order of the NDCube axes.
  • NDCube.world_to_pixel can then be used to determine the upper and lower limits in pixel coordinates and then slice the NDCube accordingly.
  • If the physical coordinates correspond to non-integer pixel values, they should be rounded up or down to the nearest integer.

New get_all_coords method for NDCube

A request for a very useful convenience function as a method on NDCube that would return the real world coords for all axes.

It could return a list of named tuples each with properties “axes” and “coords”, where “axes” was a tuple of the data axes numbers to which the coord corresponded and “coords” gave the coordinates for the whole cube as a quantity.

Make NDCubeSequence plotting a mixin

Currently, ndcube depends on sunpy for its animation functionality. However, one could argue that the visualization method is not fundamental to ndcube. Some such arguments are:

  • It may be desirable to either build a visualization capability based on another package (or not use the visualization at all) without having any knock on effects to the rest of ndcube.
  • The current visualization implementation makes ndcube dependent on sunpy which is otherwise unnecessary.

These points are good arguments for making the plotting capabilities for NDCube and NDCubeSequence pluggable so you can use them with or without the plotting, or with your own plotting mixin. The plotting for NDCube is currently a mixin but this is not the case for NDCubeSequence.

This approach would also mean that ndcube would not fundamentally depend on sunpy. Based on some feedback for a non-sunpy person, the sunpy dependency could be a barrier to people beyond the solar community using ndcube.

utils.cube.data_axis_to_wcs_axis gives wrong values for negative indices

Given missing_axis = [False, False, False, True],
>>> ndcube.utils.cube.data_axis_to_wcs_axis(-1, missing_axis)
returns
3. However, it should return 0. If the equivalent positive data axis is entered (2), the correct value is returned.

There may be a similar issue with utils.cube.wcs_axis_to_data_axis, but this has not yet been checked.

Issue installing ndcube

When I install ndcube and then import I get the following message:

RuntimeError                              Traceback (most recent call last)
RuntimeError: module compiled against API version 0xc but this version of numpy is 0xb

If I update numpy using the conda-forge channel, and try to import ndcube again, I get an import error:

ImportError: Numpy version 1.9.0 or later must be installed to use Astropy

I am doing all of this in a new clean conda environment that I create with python 3.6, astropy and matplotlib. I then install sunpy from the conda-forge channel and then install ndcube, also from the conda-forge channel as in the readme.

Some NDCubeSequence methods error when sub-NDCube's data are scalar

Currently if you slice an NDCubeSequence such that the sub-cubes' data is a scalar rather than an array, an error occurs. However, this should be a valid slicing operations as an NDCube can have a scalar as its data.

Example: Given an NDCubeSequence, nds of dimensions (<Quantity 25 pix>, <Quantity 8 pix.>), the following slicing operation raises the following traceback:

>>> nds[:, 0]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~/sunpy_dev/ndcube/ndcube/mixins/ndslicing.py in __getitem__(self, item)
     29             raise IndexError("None indices not supported")
     30 
---> 31         return super().__getitem__(item)
     32 
     33     def _slice(self, item):

~/anaconda3/envs/irispy-dev/lib/python3.5/site-packages/astropy/nddata/mixins/ndslicing.py in __getitem__(self, item)
     57         # Abort slicing if the data is a single scalar.
     58         if self.data.shape == ():
---> 59             raise TypeError('scalars cannot be sliced.')
     60 
     61         # Let the other methods handle slicing.

TypeError: scalars cannot be sliced.

Problem with extra coordinate on 32-bit system

While running tests on a 32-bit system, I got the following errors (this is with version 1.0.1, but I obtain similar results with the master version):

============================= test session starts ==============================
platform linux -- Python 3.7.1, pytest-3.6.4, py-1.7.0, pluggy-0.8.0
rootdir: /build/1st/ndcube-1.0.1, inifile: setup.cfg
plugins: remotedata-0.3.1, openfiles-0.3.0, doctestplus-0.1.3, arraydiff-0.2
collected 95 items / 4 errors

==================================== ERRORS ====================================
_________________ ERROR collecting ndcube/tests/test_ndcube.py _________________
../../../ndcube/tests/test_ndcube.py:123: in <module>
    [(cubem[:, 1],
../../../ndcube/mixins/ndslicing.py:31: in __getitem__
    return super().__getitem__(item)
/usr/lib/python3/dist-packages/astropy/nddata/mixins/ndslicing.py:61: in __getitem__
    return self.__class__(**kwargs)
../../../ndcube/ndcube.py:185: in __init__
    extra_coords, self.missing_axis, data.shape)
../../../ndcube/utils/cube.py:90: in _format_input_extra_coords_to_extra_coords_wcs_axis
    raise ValueError(coord_1_format_error)
E   ValueError: 2nd element of extra coordinate tuple must be None or an int giving the data axis to which the coordinate corresponds.
------------------------------- Captured stdout --------------------------------
[{'coordinate_type': None, 'scale': 'linear', 'group': 0, 'number': 0}, {'coordinate_type': 'spectral', 'scale': 'linear', 'group': 0, 'number': 0}, {'coordinate_type': 'celestial', 'scale': 'non-linear celestial', 'group': 0, 'number': 1}, {'coordinate_type': 'celestial', 'scale': 'non-linear celestial', 'group': 0, 'number': 0}]
INFO: uncertainty should have attribute uncertainty_type. [astropy.nddata.nddata]
INFO: uncertainty should have attribute uncertainty_type. [astropy.nddata.nddata]
INFO: uncertainty should have attribute uncertainty_type. [astropy.nddata.nddata]
INFO: uncertainty should have attribute uncertainty_type. [astropy.nddata.nddata]
INFO: uncertainty should have attribute uncertainty_type. [astropy.nddata.nddata]
------------------------------- Captured stderr --------------------------------
WARNING: FITSFixedWarning: 'celfix' made the change 'Unmatched celestial axes'. [astropy.wcs.wcs]
/build/1st/ndcube-1.0.1/ndcube/tests/test_ndcube.py:65: RuntimeWarning: invalid value encountered in sqrt
  uncertainty = np.sqrt(data)
_________________ ERROR collecting ndcube/tests/test_ndcube.py _________________
../../../ndcube/tests/test_ndcube.py:123: in <module>
    [(cubem[:, 1],
../../../ndcube/mixins/ndslicing.py:31: in __getitem__
    return super().__getitem__(item)
/usr/lib/python3/dist-packages/astropy/nddata/mixins/ndslicing.py:61: in __getitem__
    return self.__class__(**kwargs)
../../../ndcube/ndcube.py:185: in __init__
    extra_coords, self.missing_axis, data.shape)
../../../ndcube/utils/cube.py:90: in _format_input_extra_coords_to_extra_coords_wcs_axis
    raise ValueError(coord_1_format_error)
E   ValueError: 2nd element of extra coordinate tuple must be None or an int giving the data axis to which the coordinate corresponds.
------------------------------- Captured stdout --------------------------------
[{'coordinate_type': None, 'scale': 'linear', 'group': 0, 'number': 0}, {'coordinate_type': 'spectral', 'scale': 'linear', 'group': 0, 'number': 0}, {'coordinate_type': 'celestial', 'scale': 'non-linear celestial', 'group': 0, 'number': 1}, {'coordinate_type': 'celestial', 'scale': 'non-linear celestial', 'group': 0, 'number': 0}]
INFO: uncertainty should have attribute uncertainty_type. [astropy.nddata.nddata]
INFO: uncertainty should have attribute uncertainty_type. [astropy.nddata.nddata]
INFO: uncertainty should have attribute uncertainty_type. [astropy.nddata.nddata]
INFO: uncertainty should have attribute uncertainty_type. [astropy.nddata.nddata]
INFO: uncertainty should have attribute uncertainty_type. [astropy.nddata.nddata]
------------------------------- Captured stderr --------------------------------
/build/1st/ndcube-1.0.1/ndcube/tests/test_ndcube.py:65: RuntimeWarning: invalid value encountered in sqrt
  uncertainty = np.sqrt(data)
_____________ ERROR collecting ndcube/tests/test_ndcubesequence.py _____________
../../../ndcube/tests/test_ndcubesequence.py:115: in <module>
    (seq[0], NDCube),
../../../ndcube/ndcube_sequence.py:84: in __getitem__
    return utils.sequence.slice_sequence(self, item)
../../../ndcube/utils/sequence.py:57: in slice_sequence
    return slice_sequence_by_sequence_items(cubesequence, sequence_items)
../../../ndcube/utils/sequence.py:211: in slice_sequence_by_sequence_items
    return result.data[sequence_items[0].sequence_index][sequence_items[0].cube_item]
../../../ndcube/mixins/ndslicing.py:31: in __getitem__
    return super().__getitem__(item)
/usr/lib/python3/dist-packages/astropy/nddata/mixins/ndslicing.py:61: in __getitem__
    return self.__class__(**kwargs)
../../../ndcube/ndcube.py:185: in __init__
    extra_coords, self.missing_axis, data.shape)
../../../ndcube/utils/cube.py:90: in _format_input_extra_coords_to_extra_coords_wcs_axis
    raise ValueError(coord_1_format_error)
E   ValueError: 2nd element of extra coordinate tuple must be None or an int giving the data axis to which the coordinate corresponds.
------------------------------- Captured stdout --------------------------------
[{'coordinate_type': None, 'scale': 'linear', 'group': 0, 'number': 0}, {'coordinate_type': 'spectral', 'scale': 'linear', 'group': 0, 'number': 0}, {'coordinate_type': 'celestial', 'scale': 'non-linear celestial', 'group': 0, 'number': 1}, {'coordinate_type': 'celestial', 'scale': 'non-linear celestial', 'group': 0, 'number': 0}]
_____________ ERROR collecting ndcube/tests/test_ndcubesequence.py _____________
../../../ndcube/tests/test_ndcubesequence.py:115: in <module>
    (seq[0], NDCube),
../../../ndcube/ndcube_sequence.py:84: in __getitem__
    return utils.sequence.slice_sequence(self, item)
../../../ndcube/utils/sequence.py:57: in slice_sequence
    return slice_sequence_by_sequence_items(cubesequence, sequence_items)
../../../ndcube/utils/sequence.py:211: in slice_sequence_by_sequence_items
    return result.data[sequence_items[0].sequence_index][sequence_items[0].cube_item]
../../../ndcube/mixins/ndslicing.py:31: in __getitem__
    return super().__getitem__(item)
/usr/lib/python3/dist-packages/astropy/nddata/mixins/ndslicing.py:61: in __getitem__
    return self.__class__(**kwargs)
../../../ndcube/ndcube.py:185: in __init__
    extra_coords, self.missing_axis, data.shape)
../../../ndcube/utils/cube.py:90: in _format_input_extra_coords_to_extra_coords_wcs_axis
    raise ValueError(coord_1_format_error)
E   ValueError: 2nd element of extra coordinate tuple must be None or an int giving the data axis to which the coordinate corresponds.
------------------------------- Captured stdout --------------------------------
[{'coordinate_type': None, 'scale': 'linear', 'group': 0, 'number': 0}, {'coordinate_type': 'spectral', 'scale': 'linear', 'group': 0, 'number': 0}, {'coordinate_type': 'celestial', 'scale': 'non-linear celestial', 'group': 0, 'number': 1}, {'coordinate_type': 'celestial', 'scale': 'non-linear celestial', 'group': 0, 'number': 0}]
!!!!!!!!!!!!!!!!!!! Interrupted: 4 errors during collection !!!!!!!!!!!!!!!!!!!!

NDCubeSequence Indexing Bug

# Imports
import datetime
import numpy as np
from astropy.io import fits
from astropy.wcs import WCS
from ndcube import NDCube, NDCubeSequence
from sunpy.time import parse_time

# Define files with which bug was found.
file0 = 'iris_l2_20141211_191222_3803105278_raster_t000_r00000.fits'
file1 = 'iris_l2_20141211_191222_3803105278_raster_t000_r00001.fits'

# Build NDCubes from C II 1336 data
ext_num = 1
hdulist0 = fits.open(file0)
times = np.array([parse_time(hdulist0[0].header["STARTOBS"]) + datetime.timedelta(seconds=s) for s in hdulist0[-2].data[:,hdulist0[-2].header["TIME"]]])
window_extra_coords = [("time", 0, times)]
ndc0 = NDCube(hdulist0[ext_num].data, WCS(hdulist0[ext_num].header), extra_coords=window_extra_coords)
hdulist0.close()

hdulist1 = fits.open(file1)
times = np.array([parse_time(hdulist1[0].header["STARTOBS"]) + datetime.timedelta(seconds=s) for s in hdulist1[-2].data[:,hdulist1[-2].header["TIME"]]])
window_extra_coords = [("time", 0, times)]
ndc1 = NDCube(hdulist1[ext_num].data, WCS(hdulist1[ext_num].header), extra_coords=window_extra_coords)
hdulist1.close()

# Build NDCubeSequence
nds = NDCubeSequence([ndc0, ndc1], common_axis=0)

nds.index_as_cube[2] errors with the following traceback.

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-56-f3fe4c7f3673> in <module>()
----> 1 nds.index_as_cube[2]

~/sunpy_dev/ndcube/ndcube/ndcube.py in __getitem__(self, item)
    695 
    696     def __getitem__(self, item):
--> 697         return cube_utils.index_sequence_as_cube(self.seq, item)
    698 
    699 

~/sunpy_dev/ndcube/ndcube/cube_utils.py in index_sequence_as_cube(cubesequence, item)
    183     # (int, rest of the information) not (int, int , rest of the information)
    184     if isinstance(item_tuple[0], int) and isinstance(item_tuple[1], int):
--> 185         return get_cube_from_sequence(cubesequence, item_tuple[1::])
    186     else:
    187         return get_cube_from_sequence(cubesequence, item_tuple, type_slice='sequence_as_cube')

~/sunpy_dev/ndcube/ndcube/cube_utils.py in get_cube_from_sequence(cubesequence, item, type_slice)
     61                 result = result.data[item[0]][item[1]]
     62             else:
---> 63                 result = result.data[item[0]][item[1::]]
     64         # the 0th index of tuple will have the slice that will be applied across cubes
     65         # the 1st index of tuple contains the information of 1st cube to slice and last

IndexError: list index out of range

Add Arithmetic Capabilities to NDCube

NDCube Arithmetic Capabilities

Overload the following arithmetic operations for NDCube which requires creating the following methods:

  • +: __add__, __radd__
  • -: __sub__, __rsub__
  • *: __mul__, __rmul__
  • /: __truediv__, __rtruediv__
  • **: __pow__ (Should not support two NDCubes or an NDCube and NDData.)
  • ==: __eq__
  • !=: __ne__
  • =+: __iadd__
  • =-: __isub__
  • =*: __imul__
  • =/: __itruediv__

This will enable NDCubes to be operated on by a scalar, array, astropy.units.Quantity, numpy.ma.MaskedArray, astropy.nddata.NDData or NDCube with an equivalent/compatible WCS object. These are defined as the secondary object, while the NDCube is the primary object. The advantage of using NDData or an NDCube as the secondary object is that you can combine all or some of data, uncertainty, mask and unit as part of the operation whereas Quantity and MaskedArray instances can only give an inflexible subset of these components.

+, -, *, /, =+, =-, =*, =/

The different attributes of NDCube will be treated in the following ways by these operators:

Data, Unit and Mask
When the secondary object is a MaskedArray, non-scalar Quantity, NDData and NDCube secondary objects:

  • If the secondary object is an NDCube or and NDData with a WCS, the WCS will be compared to that of the primary NDCube. If they are not equivalent or compatible, a ValueError will be raised. WCSs can be not equal but compatible if the secondary WCS has fewer dimensions that are a subset of the primary, WCS.
  • A primary MaskedArray will be formed from the primary NDCube's data and mask attributes.
  • If the second object is a Quantity, NDData or NDCube, a second Quantity will be created, converted to the primary NDCube's unit, and the values extracted as an array. This will then be converted to a MaskedArray using the secondary object's mask attribute. If there is no mask all pixels will be set to unmasked.
  • The primary and secondary MaskedArrays will then be combined as the operator dictates.
  • The result will be be broken out into new_data and new_mask variables. The new_unit variable will be equivalent to the unit of the primary NDCube as everything was converted to that unit at the start.

When the secondary object is a scalar, scalar Quantity or array:

  • If the secondary is a scalar Quantity, it is converted to the primary NDCube unit and its value is extracted as a non-Quantity scalar.
  • The primary NDCube.data is combined with the secondary object as dictated by the operator.
  • The new_mask and new_unit variables are the same as those from the primary NDCube.

Uncertainty
Combining the uncertainties correctly may not always be the same depending on how the uncertainties were created. A more flexibly API may be required. See below for a discussion. However, for the purpose of overloading the operators, we can employ algorithms based on the standard propagation of errors.

When the secondary object is a scalar, array, Quantity, MaskedArray or NDData or NDCube without uncertainties or with uncertainties set to 0 will be treated as constants. Therefore the following behaviour holds:

  • +, -, =+, =-: uncertainties will not be changed;
  • *, /, =* and =/: uncertainties are simply scaled;
  • //, =//: uncertainties treated the same as / and =/ it is equivalent to subtracting a scalar after the division.

When the secondary objects are an NDData or NDCube with non-zero uncertainties, they are combined along the standard propagation of error rules depending on the operator.

Meta
For all operations discussed in this section, the NDCube.meta dictionaries will be combined into a new variable, new_meta. For common keys will the same value, only one key will be retained. Their are a few possible ways of handling keys with differing values:

  • Raise ValueError: The operation can be aborted. In order to proceed the user will have alter the two metadatas so all common keys agree or remove common keys from at least one metadata dictionary.
  • Alter key names and raise Warning: If common keys have differing values, set the value to a tuple where the value from the first NDCube is the 0th entry and the value from the second NDCube is in the 1st entry.
  • force_combine_metas kwarg: Create and set this kwarg to None, "both" or "first".
    • None: ValueError raised as above.
    • "both": both values are kept as above.
    • "first": Only the value of the first NDCube is kept.

Which of these strategies should be adopted or whether an amended strategy should be used is still open for debate. At time of writing, the force_combine_metas option is slightly preferred.

Extra_coords
Extra coords only need to be handled in two NDCube instances are being combined. For all operations in this case, the NDCube.extra_coords will be combined into a new variable new_extra_coords_wcs_axis so all coords are kept.
Similar to the Meta section, there are a few ways to handle common extra coords that do not have the same values:

  • Raise ValueError.
  • force_combine_extra_coords kwarg: Create and set this kwarg to
    • False: ValueError raised as above as for the None option in the Meta section.
    • True: Use the value from the first NDCube only.

WCS and missing_axis
As above, WCS only needs to be handled if two NDCubes are being combined. In this case is should be checked whether the NDCubes have equivalent WCS object. This means the same WCS when units are accounted for. Or the second WCS has fewer dimensions than the first, but within those dimensions, the WCSs are equivalent. If not, a ValueError should be raised. Otherwise, the operation can proceed and the new_wcs and new_missing_axis variable will be taken from the primary NDCube.

NOTES
More Detailed Handling of Uncertainties
As there is not necessarily a single way to handle uncertainties. This may require non-hidden add, sub, etc. methods with an API for users to supply their own function to handle the uncertainties, e.g. a kwarg like uncertainty_handler=None. Defaults can remain the same as used when overloading the operators as above. Perhaps these methods will also have a kwarg for altering how the mask is handled? See below.

Other Options for Handling Mask?
There are at least two ways in which the masks can be combined.

  • Masks do not affect how the data are combined, only how the output mask. In this case True is dominant, i.e. if a pixel is masked (True) in one NDCube, but not the other (False), the resultant NDCube.mask value for that pixel will be True. This will be done by always inverting the two masks, multiplying them, then reinverting them. This is required because True implies masked, but True * False == False.
  • The mask of the primary NDCube remains unchanged. The data/uncertainty elements that are masked in the second object are simply not applied to the primary NDCube.

On of these must be the default when operating on mask arrays. To do: determine which it is!

**

The ** should only accept a scalar. It will only affect the data and the uncertainty. The data array will be raised to that power (new_data = self.data ** x) and the uncertainty will follow the standard propagation rule for powers.

==, !=

== can only be True is both objects are NDCubes. The WCS and extra_coords must all be equal when taking into account units. Meanwhile the data arrays must be equal within uncertainties when taking into account units. The uncertainties and unit can only be combined correctly if the uncertainty type is known. If the uncertainty type is not set and not all uncertainties are 0, a warning should be raised saying that uncertainties will not be taken into account in the comparison. If the uncertainty attribute is None or all uncertainties are 0, then do not raise a warning and compare data without uncertainties.

The metadata and mask do not have to be the same.

!= will simply be the Boolean inverse of the output of == and likely inherited, so required no redefinition.

Returned Result

Finally, a new NDCube will be instantiated:

>>> new_cube = NDCube(new_data, wcs=new_wcs, uncertainty=new_uncertainty, mask=new_mask, 
                      meta=new_meta, unit=new_unit, extra_coords=None, 
                      missing_axis=new_missing_axis)
>>> new_cube._extra_coords_wcs_axis = new_extra_coords_wcs_axis # If the new_extra_coords_wcs_axis is already is the correct dictionary format, this way is more efficient than converting is to the input format only reconvert it back to this format.
>>> return new_cube

This the cases of =+, +-, =*, =/, the new_cube will overwrite the primary cube. This should be done by resetting the attributed of the primary cube rather than instantiating a whole new new cube which is less memory efficient.

NDCubeSequence Arithmetic Capabilities

The above arithmetic capabilities must also be implemented for NDCubeSequence. All the same secondary objects must be supported. Except in these cases, the NDCubeSequence operators will simply be wrappers, passing the operation of to the constituent NDCubes. Interpreting two other cases must be considered: combing of two NDCubeSequences and an NDCubeSequence with an iterable of NDData instances.

Combining an NDCubeSequence with an NDCubeSequence or iterable of NDData with +, -, *, /, =+, =-, =*, =/

For these operators to be applied, the NDCubeSequences and/or iterable of NDDatas must have the same number of NDCubes/NDDatas. Each pair of corresponding NDCube/NDData will then be combined as the operator dictates. If any of these pairs are incompatible under the operation, an error is thrown.

Comparing Two NDCubeSequences with ==

This should simply pass on the == operator to the constituent NDCubes. Just like the metadata of NDCubes does not have to be identical, nor should the metadata of NDCubeSequences

Adopt API from Astropy's WCS APE

There is an open proposal for a WCS API here: astropy/astropy-APEs#34 I think it would make sense if the NDCube API mirror that specification where appropriate, I am specifically suggesting the following methods be changed:

  1. Change the signature of NDCube.pixel_to_world to:
    def pixel_to_world(self, *pixel_arrays):

Which would mean accepting arrays of Quantity objects in u.pixel and dropping the origin=0 argument (and always assuming origin=0).

  1. and the same for NDCube.world_to_pixel:
    def world_to_pixel(self, *world_objects):

Which would mean only accepting SkyCoord or Quantity objects as appropriate (this might be harder in the short-term, we might just have to accept Quantity).

  1. Change NDCube.dimensions so that it just returns the shape of the axes (with units e.g. GenericMap.dimensions). Then implement a NDCube.world_axis_physical_types method following the APE: (see this)
        @property
        def world_axis_physical_types(self):
            """
            Returns an iterable of strings describing the physical type for each
            world axis. They should be names from the VO UCD1+ controlled
            Vocabulary (http://www.ivoa.net/documents/latest/UCDlist.html).
            If no matching UCD type exists, this can instead be "custom:xxx",
            where xxx is an arbitrary string.  Alternatively, if the physical
            type is unknown/undefined, an element can be `None`.
            """

Make a decision about extra_coords in NDCubeBase

We should decide if we want the whole extra_coords machinery to be an integral part of the NDCubeBase API or if we want to allow it to be an optional extra implemented by NDCube but not other things that adhere to the protocol.

Pros:

  • Not all data will need extra_coords so as more use cases appear it might become more of a burden.
  • In theory the functionality of extra_coords should be provided by the wcs object itself, not bolted on in NDCube.

Cons:

  • extra_coords as currently implemented is pretty deeply integrated with NDCube and a subclass of NDCubeBase that doesn't have it would not be supported by the likes of IRISPy.

MetaData class or property for NDCubeSequence

A new feature request for a metadata class for NDCubeSequence along the lines of the SunPy TimeSeries metadata class. Analagous to (but not the same as) the SunPy TimeSeries class, NDCubeSequence is made up of a collection of objects each with their own meta data. In the case of TimeSeries it may be that columns and/or different periods come from different files, each with different metadata. In the case of NDCubeSequence, it is made up of different NDCubes each with its own meta attribute. Instead of duplicating the common cube metadata in the NDCubeSequence.meta attribute, I suggest a metadata class or property that can dynamically return information from all the sub-cubes as well as any sequence level metadata. The implementation could look something like this:

  • sequence level metadata entered by the user during the initialisation of the NDCubeSequence could be stored in a hidden attribute NDCubeSequence._meta.
  • The meta attribute would then be a class or property that returned a class/dict containing the metadata from all cubes and the hidden _meta sequence attribute.
  • Metadata keys that are common to all cubes can be returned as a key with a single value/Quantity.
  • Metadata keys whose values differ between cubes could be returned as a array/Quantity, with values in the same order as the cubes in the sequence.
  • Sequence level metadata would be returned as single value keys.

The advantages to a system like this include that:

  • all the metadata could be accessed from the NDCubeSequence level in a neat fashion.
  • NDCube metas are not altered in the tidying up process and so does not introduce complications when slicing, e.g. because common meta has been moved out of a sub-cube.
  • If this property is slow, users can still write code accessing the sub-cube metas of the sequence _meta directly.
  • What metadata is common to or varying through sub-cubes can be quickly determined.

There may be other advantages I have forgotten to list here too.

Make decision on whether to remove NDCubeOrdered for 1.0

Should NDCubeOrdered be removed for 1.0. The idea of an ordered WCS is a legacy from sunpycube which assumed an axes order in carrying out its operations. It was added as an easy "tag-on" early in the development or ndcube and hasn't received much attention since. Do we want to maintain this object and any testing it requires?

Furthermore, should we remove utils.wcs.reindex (used in the __init__ of NDCubeOrdered) and utils.wcs.add_celestial_axes (not used at all). These are also carry-overs from sunpycube. They are potentially useful functions, but at this time are extraneous with ndcube. Removing them would also reduce maintenance and testing.

Add example to docs

Adding a few examples to the docs of how to instantiate and manipulate NDCube objects would be really helpful.

Thanks for ndcube. Seems like it could be quite useful for instruments (e.g. Hinode/EIS) as well as 3D spatial data (e.g. solarbextrapolation). I think a few examples would show easily how it could be used in these other projects.

A little more infos in the docs?

I just saw the release announcement:
https://groups.google.com/forum/#!topic/sunpy/EcT8kWIfVrU

Could you please add some install instructions to the docs, i.e. the pip and conda install you have in the release email?
http://docs.sunpy.org/projects/ndcube/en/stable/index.html

The other thing I was surprised about is that ndcube depends on sunpy.
I see that you use it (https://github.com/sunpy/ndcube/search?p=2&q=sunpy&type=&utf8=%E2%9C%93), but it wasn't obvious from the description on the docs page that / why it's needed. Maybe you can add a little more info about dependencies for ndcube there? I presume sunpy is and will remain a required dependency for ndcube?

PS: In Gammapy we have been developing something similar, hence my interest:
http://docs.gammapy.org/dev/maps/index.html
I'll look at your class in the coming days and see if we can steal some good ideas.
:-)

Out of curiosity: did you look at xarray.pydata.org for guidance? It's one of the packages we looked at. It seems a lot of the concepts you have in ndcube are very close to xarray.

Handling of NDCube.coords by NDCube.__getitem__

  1. NDCube.coords should be renamed to NDCube._extra_coords

  2. NDCube.__getitem__ must be extended to slice NDCube._extra_coords properly for all valid slicing formats. At the moment it crashes if no slice is provided for the axes the coords are linked to.

  3. The slicing of NDCube._extra_coords must then be included in testing.

Add Save Method to NDCube

Add a save method to NDCube with the following API:
NDCube.save(self, filename, file_type="FITS"):
For now it should just support FITS. More file types can be added later. The method should save the information in an NDCube, including data, mask, uncertainty, unit, extra coordinates, WCS, etc., in a standardized way.

Interpretting WCS Time as Datetime

WCS handles time as seconds since an epoch. This epoch is typically defined somewhere in the meta data. Users are certainly going to want to view and index by time in date/time format as well as seconds since epoch. The question is then, how can we provide that functionality? Currently this can be done by adding date/time formatted time as an extra coordinate. This seems very repetitious and not ideal for RAM considerations. Is there a better way?

Ideas

  • Perhaps a property WCS_time_to_datetime property can be added that takes an epoch arg and returns an array of datetime objects derived from the WCS time? This would have to check if a time WCS axis exists before executing and would thus mean that in some cases, the method would not be useable.
  • Have a base_time kwarg in pixel_to_world, world_to_pixel and axis_world_coords that gets used if "TIME" is one of the axes. If the kwarg is None and "TIME" is an axis, time since epoch is returned.

Any other ideas?

`_plot_2D_cube` fails after slicing 3D cube

Slicing a 3D ndcube into a 2D cube throws an error which I do not understand.

Here is a minimal, working example using the code from the documentation

import numpy as np
import astropy.wcs
from ndcube import NDCube
import matplotlib.pyplot as plt

data = np.ones((3, 4, 5))
wcs_input_dict = {'CTYPE1': 'WAVE    ', 'CUNIT1': 'Angstrom', 'CDELT1': 0.2, 'CRPIX1': 0, 'CRVAL1': 10, 'NAXIS1': 5, 
	'CTYPE2': 'HPLT-TAN', 'CUNIT2': 'deg', 'CDELT2': 0.5, 'CRPIX2': 2, 'CRVAL2': 0.5, 'NAXIS2': 4, 
	'CTYPE3': 'HPLN-TAN', 'CUNIT3': 'deg', 'CDELT3': 0.4, 'CRPIX3': 2, 'CRVAL3': 1, 'NAXIS3': 3}
input_wcs = astropy.wcs.WCS(wcs_input_dict)
my_cube = NDCube(data, input_wcs)

my_2d_cube = my_cube[:,:,0]

my_2d_cube.plot()
plt.show()

The above code throws the error

Traceback (most recent call last):
  File "test_plot_2d.py", line 15, in <module>
    my_2d_cube.plot()
  File "/home/byrdie/sunpy/ndcube_dev/ndcube/mixins/plotting.py", line 83, in plot
    axes_units, data_unit, **kwargs)
  File "/home/byrdie/sunpy/ndcube_dev/ndcube/mixins/plotting.py", line 252, in _plot_2D_cube
    new_axes_coordinates[plot_axis_indices[1]], data)
  File "/home/byrdie/.local/lib/python3.5/site-packages/matplotlib/image.py", line 971, in set_data
    raise TypeError("Axes don't match array shape")
TypeError: Axes don't match array shape

From my investigations, one of the problems seems to be that new_axes_coordinates in the _plot_2D_cube function is a list of 2D arrays instead of 1D arrays. I haven't yet established why this is, I think the answer lies inside the _derive_axes_coordinates, but I thought I'd post this here to see if anyone else has any input before tearing apart _derive_axes_coordinates.

For what it's worth, I checked to make sure the _plot_2D_cube function worked when defining a 2D ndcube (instead of slicing a 3D ndcube), and it did indeed work, so this is something specific to the slicing operation.

NDCube.plot deficiencies

NDCube.plot should, but does not do the following things:

  • Take into account the mask
  • Plot errorbars on 1D plots
  • Allow an extra coord to describe an axis
  • Generate x and y labels from wcs or extra coord
  • Allow coordinates for sliders to be entered
  • Allow units for sliders to be entered
  • Set units of data
  • Handle missing axes when formulating inputs for ImageAnimatorWCS

cube_utils.get_cube_from_sequence doesn't slice extra_coords

When slicing an NDCubeSequence using index_as_cube, cube_utils.get_cube_from_sequence, which does the heavy lifting, simply deep copies the original NDCubeSequence and alters the data. This leaves the _extra_coords of each NDCube intact. This is wrong. The _extra_coords dictionary of each NDCube in the sequence must also be sliced.

This could be incorporated into a fix for Issue #24

Simplify the indexing NDCubeSequence as cube

NDCubeSequence.index_as_cube calls cube_utils.index_sequence_as_cube which creates an item_tuple containing the information needed to slice the NDCubeSequence. But depending on whether the information requested as part of this slicing spans multiple cubes or not, and the way in which the user input the slice, item_tuple can take multiple forms which makes the code difficult to understand and debug. Therefore, the code should be simplified/make more intuitive, without changing the user-facing functionality.

One possible solution is to have a new class, say SequenceSlice, whose __init__ takes an slicing item and returns an object with the slice for the first, last, and intermediate cubes which are requested as part of the slicing. This object would have this information is the same format regardless of how the user input the original slicing information and so would make the under-the-hood code easier to follow and maintain.

Other suggestions are welcome.

Docs link to IVOA Vocab not working

The link to the page explaining the physical meaning of the IVOA UCD+ vocabulary used in NDCube.world_axis_physical_types is not working as a link when rendered online. The link in question is in the Dimensions section of the ndcube/docs/ndcube.rst file.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.