Add support for functional uncertainty and mask
Description
As demonstrated in IRISpy issue #82 the uncertainty array can add a significant memory burden to an NDCube-based object. In that issue the option of making NDCube.uncertainty a lazy property was raised.
Another way forward may be to support functional and array-based uncertainty and mask attributes. Users could supply an array or function that only takes self as an arg to the uncertainty and mask kwargs during initialization. If an array is passed, behaviour remains as now. If a function is passed, it is stored in a hidden method, def _uncertainty(self) (def _mask(self)) and the uncertainty (mask) becomes a property that simply returns the output of _uncertainty() (_mask()).
A generate_uncertainty_array (generate_mask_array) method could be added to NDCube. This would allow the user to convert the uncertainty (mask) attribute to the output array of the _uncertainty() (_mask()) method if they are accessing these attributes a lot and have plenty of memory. Likewise, a functionalize_uncertainty(uncertainty_function=None) (functionalize_mask(mask_function=None)) method could be implemented to enable the user to purge the array from memory and return to the functional model once that becomes more efficient. A kwarg could enable users to overwrite the _uncertainty (_mask) method or supply one for the first time if they originally initialized the NDCube with an uncertainty (mask) array.
This approach would require the type of the uncertainty and mask attributes to be checked before slicing. During visualization, it may also require the uncertainty and masks to be put in an internal variable so that these are not recalculated every time they are called during the visualization. This may only be needed for line plots/animations where the uncertainty bars are shown.
Additional context
@Cadair, since you originally suggested the lazy property idea, any thoughts on this one?