Skip to content

Commit 237c90a

Browse files
committed
First complete draft of arithmetic docs.
1 parent 8eff1b5 commit 237c90a

File tree

1 file changed

+95
-5
lines changed

1 file changed

+95
-5
lines changed

docs/explaining_ndcube/arithmetic.rst

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,28 @@ For example:
199199
Raising NDCube to a Power
200200
-------------------------
201201

202+
`~ndcube.NDCube` can be raised to a power.
203+
204+
.. code-block:: python
205+
206+
>>> cube_with_unit.data
207+
array([[10, 11, 12],
208+
[13, 14, 15]])
209+
210+
>>> new_cube = cube_with_unit**2
211+
212+
>>> new_cube.data
213+
array([[100, 121, 144],
214+
[169, 196, 225]])
215+
>>> new_cube.unit
216+
Unit("ct2")
217+
>>> (new_cube.mask == cube_with_unit.mask).all()
218+
np.True_
219+
220+
Note that error propagation is delegated to the ``cube.uncertainty`` object.
221+
Therefore, if this class supports error propagation by power, then ``new_cube`` will include uncertainty.
222+
Otherwise, ``new_cube.uncertainty`` will be set to ``None``.
223+
202224

203225
.. _arithmetic_nddata:
204226

@@ -211,10 +233,10 @@ Alternatively, we may want to subtract one image from another, but exclude a cer
211233
In such cases, numbers, arrays and `~astropy.units.Quantity` are insufficient, and we would like to subtract two `~ndcube.NDCube` objects.
212234
This is not directly supported, but can still be achieved in practice, as we shall see below.
213235

214-
Why Arithmetic Operations with Coordinate-aware NDData Are Not Directly Supported, and How the Same Result Can Be Achieved
215-
--------------------------------------------------------------------------------------------------------------------------
236+
Why Arithmetic Operations with Coordinate-aware NDData Are Not Directly Supported, and How This Can Be Overcome
237+
---------------------------------------------------------------------------------------------------------------
216238

217-
Arithmetic operations between two `~ndcube.NDCube` instances (or equivalently, an `~ndcube.NDCube` and another coordinate-aware object) are not supported because of the possibility of supporting non-sensical operations.
239+
Arithmetic operations between two `~ndcube.NDCube` instances (or equivalently, an `~ndcube.NDCube` and another coordinate-aware `~astropy.nddata.NDData` subclass) are not supported because of the possibility of supporting non-sensical operations.
218240
For example, what does it mean to multiply a spectrum and an image in a coordinate-aware way?
219241
Getting the difference between two images may make physical sense, but only in certain circumstances.
220242
For example, subtracting two sequential images of the same region of the Sun is a common step in many solar image analysis workflows.
@@ -223,7 +245,7 @@ Even when subtracting two images of the Sun, drift in the telescope's pointing m
223245
In this case, it is questionable whether this operation makes physical sense after all.
224246
Moreover, in all of these cases, it is not at all clear what the resulting WCS object should be.
225247

226-
In many cases, a simple solution would be to extract the data (an optionally the unit) of one of the `~ndcube.NDCube` instances and perform the operation as described in the above section on :ref:`arithmetic_standard`:
248+
In many cases, a simple solution would be to extract the data (an optionally the unit) from one of the `~ndcube.NDCube` instances and perform the operation as described in the above section on :ref:`arithmetic_standard`:
227249

228250
.. expanding-code-block:: python
229251
:summary: Expand to see definition of cube1 and cube2.
@@ -235,7 +257,7 @@ In many cases, a simple solution would be to extract the data (an optionally the
235257
236258
>>> new_cube = cube1 - cube2.data * cube2.unit
237259
238-
However, this does not allow for the propagation of uncertainties or masks associated with the data in ``cube2``.
260+
However, this does not allow for the propagation of uncertainties or masks associated with ``cube2``.
239261
Therefore, `~ndcube.NDCube` does support arithmetic operations with instances of `~astropy.nddata.NDData` subclasses whose ``wcs`` attribute is ``None``.
240262
This makes users explicitly aware that they are dispensing with coordinate-awareness on one of their operands.
241263
It also leaves only one WCS involved in the operation, thus removing ambiguity regarding the WCS of the `~ndcube.NDCube` resulting from the operation.
@@ -251,3 +273,71 @@ Users who would like to drop coordinate-awareness from an `~ndcube.NDCube` can s
251273
252274
Performing Arithmetic Operations with Coordinate-less NDData
253275
------------------------------------------------------------
276+
277+
Addition, subtraction, multiplication and division between `~ndcube.NDCube` and coordinate-less `~astropy.nddata.NDData` classes are all supported via the ``+``, ``-``, ``*``, and ``/`` operators.
278+
With respect to the ``data`` and ``unit`` attributes, the behaviors are the same as for arrays and `~astropy.units.Quantity`.
279+
The power of using coordinate-less `~astropy.nddata.NDData` classes is the ability to handle uncertainties and masks.
280+
281+
Uncertainty Propagation
282+
***********************
283+
284+
The uncertainty associated with the `~ndcube.NDCube` resulting from the arithmetic operation depends on the uncertainty types of the operands:
285+
286+
- ``NDCube.uncertainty`` and ``NDData.uncertainty`` are both ``None`` => ``new_cube.uncertainty`` is ``None``;
287+
- ``NDCube`` or ``NDData`` have uncertainty, but not both => the existing uncertainty is assigned to ``new_cube`` as is;
288+
- ``NDCube`` and ``NDData`` both have uncertainty => uncertainty propagation is delegated to the ``NDCube.uncertainty.propagate`` method.
289+
290+
* Note that not all uncertainty classes support error propagation, e.g. `~astropy.nddata.UnknownUncertainty`. In such cases, uncertainties are dropped altogether and ``new_cube.uncertainty`` is set to ``None``.
291+
292+
If users would like to remove uncertainty from one of the operands in order to propagate the other without alteration, this can be done before the arithmetic operation via:
293+
294+
.. code-block:: python
295+
296+
>>> # Remove uncertainty from NDCube
297+
>>> cube1_nouncert = NDCube(cube2, wcs=None)
298+
>>> new_cube = cube1_nouncert + cube2_nocoords
299+
300+
>>> # Remove uncertainty from coordinate-less NDData
301+
>>> cube2_nocoords_nouncert = NDData(cube2, wcs=None, uncertainty=None)
302+
>>> new_cube = cube1 / cube2_nocoords_nouncert
303+
304+
Mask Operations
305+
***************
306+
307+
The mask associated with the `~ndcube.NDCube` resulting from the arithmetic operation depends on the mask types of the operands:
308+
309+
- ``NDCube.mask`` and ``NDData.mask`` are both ``None`` => ``new_cube.mask`` is ``None``;
310+
- ``NDCube`` or ``NDData`` have a mask, but not both => the existing mask is assigned to ``new_cube`` as is;
311+
- ``NDCube`` and ``NDData`` both have masks => The masks are combined via `numpy.logical_or`.
312+
313+
The mask values do not affect the ``data`` values output by the operation.
314+
However, in some cases, the mask may be used to identify regions of unreliable data that should not be included in the operation.
315+
This can be achieved by altering the masked data values before the operation via the `ndcube.NDCube.fill_masked` method.
316+
In the case of addition and subtraction, the ``fill_value`` should be ``0``.
317+
318+
.. code-block:: python
319+
320+
>>> cube_filled = cube1.fill_masked(0)
321+
>>> new_cube = cube_filled + cube2_nocoords
322+
323+
By replacing masked data values with ``0``, these pixels are effectively not included in the addition, and the data values from ``cube2_nocoords`` are passed into ``new_cube`` unchanged.
324+
In the above example, both operands have uncertainties, which means masked uncertainties are propagated through the addition, even though the masked data values have been set to ``0``.
325+
Propagation of masked uncertainties can also be suppressed by setting the optional kwarg, ``fill_uncertainty_value=0``.
326+
By default, the mask of ``cube_filled`` is not changed, and therefore is incorporated into the mask of the output cube.
327+
However, mask propagation can also be suppressed by setting the optional kwarg, ``unmask=True``, which sets ``cube_filled0.mask`` to ``False``.
328+
329+
In the case of multiplication and division, and ``fill_value`` of ``1`` will prevent masked values being including in the operations:
330+
331+
.. code-block:: python
332+
333+
>>> cube_filled = cube1.fill_masked(1, fill_uncertainty_value=0, unmask=True)
334+
>>> new_cube = cube_filled * cube2_nocoords
335+
336+
By default, `ndcube.NDCube.fill_masked` returns a new `~ndcube.NDCube` instance.
337+
However, in some case it may be preferable to fill the masked values in-place, e.g. because the data within the `~ndcube.NDCube` is very large and users want to control the number of copies in RAM.
338+
In this case, the ``fill_in_place`` can be used.
339+
340+
.. code-block:: python
341+
342+
>>> cube1.fill_masked(0, fill_in_place=True)
343+
>>> new_cube = cube1 + cube2_nocoords

0 commit comments

Comments
 (0)