From 96cb14438680dfff00fab0f853e6be8af7ec8a90 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Tue, 20 Mar 2018 16:08:52 -0400 Subject: [PATCH 01/30] First draft NDCubeSequence plottling mixin API. --- ndcube/mixins/sequence_plotting.py | 104 +++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 ndcube/mixins/sequence_plotting.py diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py new file mode 100644 index 000000000..5aa917eeb --- /dev/null +++ b/ndcube/mixins/sequence_plotting.py @@ -0,0 +1,104 @@ +import numpy as np +import matplotlib.pyplot as plt + +import astropy.units as u +from sunpy.visualization.imageanimator import ImageAnimatorWCS +import sunpy.visualization.wcsaxes_compat as wcsaxes_compat + +__all__ = ['NDCubePlotMixin'] + +class NDCubeSequencePlotMixin: + def plot(self, axes=None, plot_as_cube=False, image_axes=None, axis_ranges=None, + unit_x_axis=None, unit_y_axis=None, data_unit=None, **kwargs): + """ + Visualizes data in the NDCubeSequence. + + Based on the dimensionality of the sequence and value of image_axes kwarg, + a Line/Image Animation/Plot is produced. + + Parameters + ---------- + axes: `astropy.visualization.wcsaxes.core.WCSAxes` or ??? or None. + The axes to plot onto. If None the current axes will be used. + + plot_as_cube: `bool` + If the sequence has a common axis, visualize the sequence as a single + cube where the sequence sub-cubes are sequential along the common axis. + This will result in the sequence being treated as a cube with N-1 dimensions + where N is the number of dimensions of the sequence, including the sequence + dimension. + Default=False + + image_axes: `int` or iterable of one or two `int` + Default is images_axes=[-1, -2]. If sequence only has one dimension, + default is images_axes=0. + + axis_ranges: `list` of physical coordinates for image axes and sliders or `None` + If None coordinates derived from the WCS objects will be used for all axes. + If a list, it should contain one element for each axis. Each element should + be either an `astropy.units.Quantity` or a `numpy.ndarray` of coordinates for + each pixel, or a `str` denoting a valid extra coordinate. + + unit_x_axis: `astropy.unit.Unit` or valid unit `str` + Unit in which X-axis should be displayed. Only used if corresponding entry in + axis_ranges is a Quantity or None. + + unit_y_axis: `astropy.unit.Unit` or valid unit `str` + Unit in which Y-axis should be displayed. Only used if corresponding entry in + axis_ranges is a Quantity or None. + + unit: `astropy.unit.Unit` or valid unit `str` + Unit in which data in a 2D image or animation should be displayed. Only used if + visualization is a 2D image or animation, i.e. if image_axis has length 2. + + """ + raise NotImplementedError() + + def _plot_1D_sequence(self, axes=None, x_axis_range=None, unit_x_axis=None, unit_y_axis=None): + """ + Visualizes an NDCubeSequence of scalar NDCubes as a line plot. + + """ + raise NotImplementedError() + + def _plot_2D_sequence_as_1Dline(self, axes=None, x_axis_range=None, unit_x_axis=None, unit_y_axis=None, + **kwargs)): + """ + Visualizes an NDCubeSequence of 1D NDCubes with a common axis as a line plot. + + Called if plot_as_cube=True. + + """ + raise NotImplementedError() + + def _plot_2D_sequence(self, *args **kwargs): + """ + Visualizes an NDCubeSequence of 1D NDCubes as a 2D image. + + """ + raise NotImplementedError() + + def _plot_3D_sequence_as_2Dimage(self, *args **kwargs): + """ + Visualizes an NDCubeSequence of 2D NDCubes with a common axis as a 2D image. + + Called if plot_as_cube=True. + + """ + raise NotImplementedError() + + def _animate_ND_sequence(self, *args **kwargs): + """ + Visualizes an NDCubeSequence of >2D NDCubes as 2D an animation with N-2 sliders. + + """ + raise NotImplementedError() + + def _animate_ND_sequence_as_Nminus1Danimation(self, *args **kwargs): + """ + Visualizes a common axis NDCubeSequence of >3D NDCubes as 2D animation with N-3 sliders. + + Called if plot_as_cube=True. + + """ + raise NotImplementedError() From b9d1af81f193495ed257c9ba0dc1d828b0d381e4 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Thu, 22 Mar 2018 00:28:48 -0400 Subject: [PATCH 02/30] First working version of NDCubeSequence plotting mixin. --- ndcube/mixins/sequence_plotting.py | 504 +++++++++++++++++++++++++++-- ndcube/visualization/animation.py | 3 - 2 files changed, 476 insertions(+), 31 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 5aa917eeb..09c354d4a 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -1,15 +1,20 @@ import numpy as np +import matplotlib as mpl import matplotlib.pyplot as plt import astropy.units as u -from sunpy.visualization.imageanimator import ImageAnimatorWCS -import sunpy.visualization.wcsaxes_compat as wcsaxes_compat + +from ndcube import utils +from ndcube.visualization import animation as ani __all__ = ['NDCubePlotMixin'] +NON_COMPATIBLE_UNIT_MESSAGE = \ + "All sequence sub-cubes' unit attribute are not compatible with unit_y_axis set by user." + class NDCubeSequencePlotMixin: - def plot(self, axes=None, plot_as_cube=False, image_axes=None, axis_ranges=None, - unit_x_axis=None, unit_y_axis=None, data_unit=None, **kwargs): + def plot(self, axes=None, plot_as_cube=False, plot_axes=None, axes_coordinates=None, + axes_units=None, data_unit=None, **kwargs): """ Visualizes data in the NDCubeSequence. @@ -29,76 +34,519 @@ def plot(self, axes=None, plot_as_cube=False, image_axes=None, axis_ranges=None, dimension. Default=False - image_axes: `int` or iterable of one or two `int` + plot_axes: `int` or iterable of one or two `int` Default is images_axes=[-1, -2]. If sequence only has one dimension, - default is images_axes=0. + images_axes is forced to be 0. - axis_ranges: `list` of physical coordinates for image axes and sliders or `None` + axes_coordinates: `list` of physical coordinates for image axes and sliders or `None` If None coordinates derived from the WCS objects will be used for all axes. If a list, it should contain one element for each axis. Each element should be either an `astropy.units.Quantity` or a `numpy.ndarray` of coordinates for each pixel, or a `str` denoting a valid extra coordinate. - unit_x_axis: `astropy.unit.Unit` or valid unit `str` - Unit in which X-axis should be displayed. Only used if corresponding entry in - axis_ranges is a Quantity or None. - - unit_y_axis: `astropy.unit.Unit` or valid unit `str` - Unit in which Y-axis should be displayed. Only used if corresponding entry in - axis_ranges is a Quantity or None. + axes_units: length 2 `list` of `astropy.unit.Unit` or valid unit `str` or None. + Denotes unit in which X-axis and Y-axis, respectively, should be displayed. + Only used if corresponding entry in axes_coordinates is a Quantity. - unit: `astropy.unit.Unit` or valid unit `str` + data_unit: `astropy.unit.Unit` or valid unit `str` or None Unit in which data in a 2D image or animation should be displayed. Only used if visualization is a 2D image or animation, i.e. if image_axis has length 2. """ raise NotImplementedError() - def _plot_1D_sequence(self, axes=None, x_axis_range=None, unit_x_axis=None, unit_y_axis=None): + def _plot_1D_sequence(self, cubesequence, axes=None, x_axis_coordinates=None, axes_units=None, + **kwargs): """ Visualizes an NDCubeSequence of scalar NDCubes as a line plot. + A scalar NDCube is one whose NDCube.data is a scalar rather than an array. + + Parameters + ---------- + axes: `astropy.visualization.wcsaxes.core.WCSAxes` or ??? or None. + The axes to plot onto. If None the current axes will be used. + + x_axis_values: `numpy.ndarray` or `astropy.unit.Quantity` or `str` or `None` + Denotes the physical coordinates of the x-axis. + If None, coordinates are derived from the WCS objects. + If an `astropy.units.Quantity` or a `numpy.ndarray` gives the coordinates for + each pixel along the x-axis. + If a `str`, denotes the extra coordinate to be used. The extra coordinate must + correspond to the sequence axis. + + unit_x_axis: `astropy.unit.Unit` or valid unit `str` + Unit in which X-axis should be displayed. Must be compatible with the unit of + the coordinate denoted by x_axis_range. Not used if x_axis_range is a + `numpy.ndarray` or the designated extra coordinate is a `numpy.ndarray` + + unit_y_axis: `astropy.units.unit` or valid unit `str` + The units into which the y-axis should be displayed. The unit attribute of all + the sub-cubes must be compatible to set this kwarg. + """ - raise NotImplementedError() + if axes_units is None: + unit_x_axis = None + unit_y_axis = None + else: + unit_x_axis = axes_units[0] + unit_y_axis = axes_units[1] + # Check that the unit attribute is a set in all cubes and derive unit_y_axis if not set. + sequence_units, unit_y_axis = _determine_sequence_units(cubesequence.data, unit_y_axis) + # If all cubes have unit set, create a data quantity from cubes' data. + if sequence_units is not None: + ydata = u.Quantity([cube.data * sequence_units[i] + for i, cube in enumerate(cubesequence.data)], unit=unit_y_axis).value + yerror = u.Quantity([cube.uncertainty.array * sequence_units[i] + for i, cube in enumerate(cubesequence.data)], unit=unit_y_axis).value + # If not all cubes have their unit set, create a data array from cube's data. + else: + if unit_y_axis is not None: + raise ValueError(NON_COMPATIBLE_UNIT_MESSAGE) + ydata = np.array([cube.data for cube in cubesequence.data]) + yerror = np.array([cube.uncertainty for cube in cubesequence.data]) + if all(yerror == None): + yerror = None + # Define x-axis data. + if x_axis_coordinates is None: + # Since scalar NDCubes have no array/pixel indices, WCS translations don't work. + # Therefore x-axis values will be unitless sequence indices unless supplied by user + # or an extra coordinate is designated. + xdata = np.arange(int(cubesequence.dimensions[0].value)) + xname = cubesequence.world_axis_physical_types[0] + elif isinstance(x_axis_coordinates, str): + xdata = cubesequence.sequence_axis_extra_coords[x_axis_coordinates] + xname = x_axis_coordinate + else: + xdata = x_axis_coordinates + xname = cubesequence.world_axis_physical_types[0] + if isinstance(xdata, u.Quantity): + if unit_x_axis is None: + unit_x_axis = xdata.unit + else: + xdata = xdata.to(unit_x_axis) + else: + unit_x_axis = None + default_xlabel = "{0} [{1}]".format(xname, unit_x_axis) + fig, ax = _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs) + return ax - def _plot_2D_sequence_as_1Dline(self, axes=None, x_axis_range=None, unit_x_axis=None, unit_y_axis=None, - **kwargs)): + def _plot_2D_sequence_as_1Dline(self, cubesequence, axes=None, x_axis_coordinates=None, + axes_units=None, **kwargs): """ Visualizes an NDCubeSequence of 1D NDCubes with a common axis as a line plot. Called if plot_as_cube=True. + Parameters + ---------- + Same as _plot_1D_sequence + """ - raise NotImplementedError() + if axes_units is None: + unit_x_axis = None + unit_y_axis = None + else: + unit_x_axis = axes_units[0] + unit_y_axis = axes_units[1] + # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. + sequence_units, unit_y_axis = _determine_sequence_units(cubesequence.data, unit_y_axis) + # If all cubes have unit set, create a y data quantity from cube's data. + if sequence_units is not None: + ydata = np.concatenate([(cube.data * sequence_units[i]).to(unit_y_axis).value + for i, cube in enumerate(cubesequence.data)]) + yerror = np.concatenate( + [(cube.uncertainty.array * sequence_units[i]).to(unit_y_axis).value + for i, cube in enumerate(cubesequence.data)]) + else: + if unit_y_axis is not None: + raise ValueError(NON_COMPATIBLE_UNIT_MESSAGE) + # If not all cubes have unit set, create a y data array from cube's data. + ydata = np.concatenate([cube.data for cube in cubesequence.data]) + yerror = np.array([cube.uncertainty for cube in cubesequence.data]) + if all(yerror == None): + yerror = None + else: + if any(yerror == None): + w = np.where(yerror == None)[0] + for i in w: + yerror[i] = np.zeros(int(cubesequence[i].dimensions.value)) + yerror = np.concatenate(yerror) + # Define x-axis data. + if x_axis_coordinates is None: + if unit_x_axis is None: + unit_x_axis = np.asarray(cubesequence[0].wcs.wcs.cunit)[ + np.invert(cubesequence[0].missing_axis)][0] + xdata = u.Quantity(np.concatenate([cube.axis_world_coords().to(unit_x_axis).value + for cube in cubesequence]), unit=unit_x_axis) + xname = cubesequence.cube_like_world_axis_physical_types[0] + elif isinstance(x_axis_coordinates, str): + xdata = cubesequence.common_axis_extra_coords[x_axis_coordinates] + xname = x_axis_coordinates + else: + xdata = x_axis_coordinates + if isinstance(xdata, u.Quantity): + if unit_x_axis is None: + unit_x_axis = xdata.unit + else: + xdata = xdata.to(unit_x_axis) + else: + unit_x_axis = None + default_xlabel = "{0} [{1}]".format(xname, unit_x_axis) + # Plot data + fig, ax = _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs) + return ax - def _plot_2D_sequence(self, *args **kwargs): + def _plot_2D_sequence(self, cubesequence, axes=None, plot_axes=None, axes_coordinates=None, + axes_units=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of 1D NDCubes as a 2D image. + **kwargs are fed into matplotlib.image.NonUniformImage. + + Parameters + ---------- + Same as self.plot() + """ - raise NotImplementedError() + # Set default values of kwargs if not set. + if axes_coordinates is None: + axes_coordinates = [None, None] + if axes_units is None: + axes_units = [None, None] + if plot_axes is None: + plot_axes = [-1, -2] + # Convert plot_axes to array for function operations. + plot_axes = np.asarray(plot_axes) + # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. + sequence_units, data_unit = _determine_sequence_units(cubesequence.data, data_unit) + # If all cubes have unit set, create a data quantity from cube's data. + if sequence_units is not None: + data = np.stack([(cube.data * sequence_units[i]).to(data_unit).value + for i, cube in enumerate(cubesequence.data)]) + else: + data = np.stack([cube.data for i, cube in enumerate(cubesequence.data)]) + # Transpose data if user-defined images_axes require it. + if plot_axes[0] < plot_axes[1]: + data = data.transpose() + # Determine index of above axes variables corresponding to cube axis. + cube_axis_index = 1 + # Determine index of above variables corresponding to sequence axis. + sequence_axis_index = 0 + # Derive the coordinates, unit, and default label of the cube axis. + cube_axis_unit = axes_units[cube_axis_index] + if axes_coordinates[cube_axis_index] is None: + if cube_axis_unit is None: + cube_axis_unit = np.array(cubesequence[0].wcs.wcs.cunit)[ + np.invert(cubesequence[0].missing_axis)][0] + cube_axis_coords = cubesequence[0].axis_world_coords().to(cube_axis_unit).value + cube_axis_name = cubesequence.world_axis_physical_types[1] + else: + if isinstance(axes_coordinates[cube_axis_index], str): + cube_axis_coords = \ + cubesequence[0].extra_coords[axes_coordinates[cube_axis_index]]["value"] + cube_axis_name = axes_coordinates[cube_axis_index] + else: + cube_axis_coords = axes_coordinates[cube_axis_index] + cube_axis_name = cubesequence.world_axis_physical_types[1] + if isinstance(cube_axis_coords, u.Quantity): + if cube_axis_unit is None: + cube_axis_unit = cube_axis_coords.unit + cube_axis_coords = cube_axis_coords.value + else: + cube_axis_coords = cube_axis_coords.to(cube_axis_unit).value + else: + cube_axis_coords = None + default_cube_axis_label = "{0} [{1}]".format(cube_axis_name, cube_axis_unit) + axes_coordinates[cube_axis_index] = cube_axis_coords + axes_units[cube_axis_index] = cube_axis_unit + # Derive the coordinates, unit, and default label of the sequence axis. + sequence_axis_unit = axes_units[sequence_axis_index] + if axes_coordinates[sequence_axis_index] is None: + sequence_axis_coords = np.arange(len(cubesequence.data)) + sequence_axis_name = cubesequence.world_axis_physical_types[0] + elif isinstance(axes_coordinates[sequence_axis_index], str): + sequence_axis_coords = \ + cubesequence.sequence_axis_extra_coords[axes_coordinates[sequence_axis_index]] + sequence_axis_name = axes_coordinates[sequence_axis_index] + else: + sequence_axis_coords = axes_coordinates[sequence_axis_index] + sequence_axis_name = cubesequence.world_axis_physical_types[0] + if isinstance(sequence_axis_coords, u.Quantity): + if sequence_axis_unit is None: + sequence_axis_unit = sequence_axis_coords.unit + sequence_axis_coords = sequence_axis_coords.value + else: + sequence_axis_coords = sequence_axis_coords.to(sequence_axis_unit).value + else: + sequence_axis_unit = None + default_sequence_axis_label = "{0} [{1}]".format(sequence_axis_name, sequence_axis_unit) + axes_coordinates[sequence_axis_index] = sequence_axis_coords + axes_units[sequence_axis_index] = sequence_axis_unit + axes_labels = [None, None] + axes_labels[cube_axis_index] = default_cube_axis_label + axes_labels[sequence_axis_index] = default_sequence_axis_label + # Plot image. + # Create figure and axes objects. + fig, ax = plt.subplots(1, 1) + # Since we can't assume the x-axis will be uniform, create NonUniformImage + # axes and add it to the axes object. + im_ax = mpl.image.NonUniformImage( + ax, extent=(axes_coordinates[plot_axes[0]][0], axes_coordinates[plot_axes[0]][-1], + axes_coordinates[plot_axes[1]][0], axes_coordinates[plot_axes[1]][-1]), + **kwargs) + im_ax.set_data(axes_coordinates[plot_axes[0]], axes_coordinates[plot_axes[1]], data) + ax.add_image(im_ax) + # Set the limits, labels, etc. of the axes. + ax.set_xlim((axes_coordinates[plot_axes[0]][0], axes_coordinates[plot_axes[0]][-1])) + ax.set_ylim((axes_coordinates[plot_axes[1]][0], axes_coordinates[plot_axes[1]][-1])) + ax.set_xlabel(axes_labels[plot_axes[0]]) + ax.set_ylabel(axes_labels[plot_axes[1]]) + + return ax - def _plot_3D_sequence_as_2Dimage(self, *args **kwargs): + def _plot_3D_sequence_as_2Dimage(self, cubesequence, axes=None, plot_axes=None, + axes_coordinates=None, axes_units=None, data_unit=None, + **kwargs): """ Visualizes an NDCubeSequence of 2D NDCubes with a common axis as a 2D image. Called if plot_as_cube=True. """ - raise NotImplementedError() + # Set default values of kwargs if not set. + if axes_coordinates is None: + axes_coordinates = [None, None] + if axes_units is None: + axes_units = [None, None] + if plot_axes is None: + plot_axes = [-1, -2] + # Convert plot_axes to array for function operations. + plot_axes = np.asarray(plot_axes) + # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. + sequence_units, data_unit = _determine_sequence_units(cubesequence.data, data_unit) + # If all cubes have unit set, create a data quantity from cube's data. + if sequence_units is not None: + data = np.concatenate([(cube.data * sequence_units[i]).to(data_unit).value + for i, cube in enumerate(cubesequence.data)], + axis=cubesequence._common_axis) + else: + data = np.concatenate([cube.data for cube in cubesequence.data], + axis=cubesequence._common_axis) + if plot_axes[0] < plot_axes[1]: + data = data.transpose() + # Determine index of common axis and other cube axis. + common_axis_index = cubesequence._common_axis + cube_axis_index = [0, 1] + cube_axis_index.pop(common_axis_index) + cube_axis_index = cube_axis_index[0] + # Derive the coordinates, unit, and default label of the cube axis. + cube_axis_unit = axes_units[cube_axis_index] + if axes_coordinates[cube_axis_index] is None: + if cube_axis_unit is None: + cube_axis_unit = np.array(cubesequence[0].wcs.wcs.cunit)[ + np.invert(cubesequence[0].missing_axis)][0] + cube_axis_coords = \ + cubesequence[0].axis_world_coords()[cube_axis_index].to(cube_axis_unit).value + cube_axis_name = cubesequence.world_axis_physical_types[1] + else: + if isinstance(axes_coordinates[cube_axis_index], str): + cube_axis_coords = \ + cubesequence[0].extra_coords[axes_coordinates[cube_axis_index]]["value"] + cube_axis_name = axes_coordinates[cube_axis_index] + else: + cube_axis_coords = axes_coordinates[cube_axis_index] + cube_axis_name = cubesequence.world_axis_physical_types[1] + if isinstance(cube_axis_coords, u.Quantity): + if cube_axis_unit is None: + cube_axis_unit = cube_axis_coords.unit + cube_axis_coords = cube_axis_coords.value + else: + cube_axis_coords = cube_axis_coords.to(cube_axis_unit).value + else: + cube_axis_coords = None + default_cube_axis_label = "{0} [{1}]".format(cube_axis_name, cube_axis_unit) + axes_coordinates[cube_axis_index] = cube_axis_coords + axes_units[cube_axis_index] = cube_axis_unit + # Derive the coordinates, unit, and default label of the common axis. + common_axis_unit = axes_units[common_axis_index] + if axes_coordinates[common_axis_index] is None: + # Concatenate values along common axis for each cube. + if common_axis_unit is None: + wcs_common_axis_index = utils.cube.data_axis_to_wcs_axis( + common_axis_index, cubesequence[0].missing_axis) + common_axis_unit = np.array(cubesequence[0].wcs.wcs.cunit)[wcs_common_axis_index] + common_axis_coords = u.Quantity(np.concatenate( + [cube.axis_world_coords()[common_axis_index].to(common_axis_unit).value + for cube in cubesequence.data]), unit=common_axis_unit) + common_axis_name = cubesequence.cube_like_world_axis_physical_types[common_axis_index] + elif isinstance(axes_coordinates[common_axis_index], str): + common_axis_coords = \ + cubesequence.common_axis_extra_coords[axes_coordinates[common_axis_index]] + sequence_axis_name = axes_coordinates[common_axis_index] + else: + common_axis_coords = axes_coordinates[common_axis_index] + common_axis_name = cubesequence.cube_like_world_axis_physical_types[common_axis_index] + if isinstance(common_axis_coords, u.Quantity): + if common_axis_unit is None: + common_axis_unit = common_axis_coords.unit + common_axis_coords = common_axis_coords.value + else: + common_axis_coords = common_axis_coords.to(common_axis_unit).value + else: + common_axis_unit = None + default_common_axis_label = "{0} [{1}]".format(common_axis_name, common_axis_unit) + axes_coordinates[common_axis_index] = common_axis_coords + axes_units[common_axis_index] = common_axis_unit + axes_labels = [None, None] + axes_labels[cube_axis_index] = default_cube_axis_label + axes_labels[common_axis_index] = default_common_axis_label + # Plot image. + # Create figure and axes objects. + fig, ax = plt.subplots(1, 1) + # Since we can't assume the x-axis will be uniform, create NonUniformImage + # axes and add it to the axes object. + im_ax = mpl.image.NonUniformImage( + ax, extent=(axes_coordinates[plot_axes[0]][0], axes_coordinates[plot_axes[0]][-1], + axes_coordinates[plot_axes[1]][0], axes_coordinates[plot_axes[1]][-1]), + **kwargs) + im_ax.set_data(axes_coordinates[plot_axes[0]], axes_coordinates[plot_axes[1]], data) + ax.add_image(im_ax) + # Set the limits, labels, etc. of the axes. + ax.set_xlim((axes_coordinates[plot_axes[0]][0], axes_coordinates[plot_axes[0]][-1])) + ax.set_ylim((axes_coordinates[plot_axes[1]][0], axes_coordinates[plot_axes[1]][-1])) + ax.set_xlabel(axes_labels[plot_axes[0]]) + ax.set_ylabel(axes_labels[plot_axes[1]]) + + return ax + - def _animate_ND_sequence(self, *args **kwargs): + def _animate_ND_sequence(self, cubesequence, *args, **kwargs): """ Visualizes an NDCubeSequence of >2D NDCubes as 2D an animation with N-2 sliders. """ - raise NotImplementedError() + return ani.ImageAnimatorNDCubeSequence(cubesequence, *args, **kwargs) - def _animate_ND_sequence_as_Nminus1Danimation(self, *args **kwargs): + def _animate_ND_sequence_as_Nminus1Danimation(self, cubesequence, *args, **kwargs): """ Visualizes a common axis NDCubeSequence of >3D NDCubes as 2D animation with N-3 sliders. Called if plot_as_cube=True. """ - raise NotImplementedError() + return ani.ImageAnimatorCommonAxisNDCubeSequence(cubesequence, *args, **kwargs) + + +def _determine_sequence_units(cubesequence_data, unit=None): + """ + Returns units of cubes in sequence and derives data unit if not set. + + If not all cubes have their unit attribute set, an error is raised. + + Parameters + ---------- + cubesequence_data: `list` of `ndcube.NDCube` + Taken from NDCubeSequence.data attribute. + + unit: `astropy.units.Unit` or `None` + If None, an appropriate unit is derived from first cube in sequence. + + Returns + ------- + sequence_units: `list` of `astropy.units.Unit` + Unit of each cube. + + unit: `astropy.units.Unit` + If input unit is not None, then the same as input. Otherwise it is + the unit of the first cube in the sequence. + + """ + # Check that the unit attribute is set of all cubes. If not, unit_y_axis + try: + sequence_units = np.array(_get_all_cube_units(cubesequence_data)) + except ValueError: + sequence_units = None + # If all cubes have unit set, create a data quantity from cube's data. + if sequence_units is not None: + if unit is None: + unit = sequence_units[0] + else: + unit = None + return sequence_units, unit + + +def _derive_1D_x_data(cubesequence, x_axis_values, unit_x_axis, sequence_is_1d=True): + # Derive x data from wcs is extra_coord not set. + if x_axis_values is None: + if sequence_is_1d: + # Since scalar NDCubes have no array/pixel indices, WCS translations don't work. + # Therefore x-axis values will be unitless sequence indices unless supplied by user + # or an extra coordinate is designated. + unit_x_axis = None + xdata = np.arange(int(cubesequence.dimensions[0].value)) + default_xlabel = "{0} [{1}]".format(cubesequence.world_axis_physical_types[0], + unit_x_axis) + else: + if unit_x_axis is None: + unit_x_axis = np.asarray(cubesequence[0].wcs.wcs.cunit)[ + np.invert(cubesequence[0].missing_axis)][0] + xdata = u.Quantity(np.concatenate([cube.axis_world_coords().to(unit_x_axis).value + for cube in cubesequence]), unit=unit_x_axis) + default_xlabel = "{0} [{1}]".format(cubesequence.cube_like_world_axis_physical_types[0], + unit_x_axis) + elif isinstance(x_axis_values, str): + # Else derive x-axis from extra coord. + if sequence_is_1d: + xdata = cubesequence.sequence_axis_extra_coords[x_axis_extra_coord] + else: + xdata = cubesequence.common_axis_extra_coords[x_axis_extra_coord] + if unit_x_axis is None and isinstance(xdata, u.Quantity): + unit_x_axis = xdata.unit + default_xlabel = "{0} [{1}]".format(x_axis_extra_coord, unit_x_axis) + + return xdata, unit_x_axis, default_xlabel + + +def _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs): + # Define plot settings if not set in kwargs. + xlabel = kwargs.pop("xlabel", default_xlabel) + ylabel = kwargs.pop("ylabel", "Data [{0}]".format(unit_y_axis)) + title = kwargs.pop("title", "") + xlim = kwargs.pop("xlim", None) + ylim = kwargs.pop("ylim", None) + # Plot data + fig, ax = plt.subplots(1, 1) + print(xdata.shape, ydata.shape) + ax.errorbar(xdata, ydata, yerror, **kwargs) + ax.set_xlabel(xlabel) + ax.set_ylabel(ylabel) + ax.set_title(title) + ax.set_xlim(xlim) + ax.set_ylim(ylim) + return fig, ax + + +def _get_all_cube_units(sequence_data): + """ + Return units of a sequence of NDCubes. + + Raises an error if any of the cube's don't have the unit attribute set. + + Parameters + ---------- + sequence_data: iterable of `ndcube.NDCube` of `astropy.nddata.NDData`. + + Returns + ------- + sequence_units: `list` of `astropy.units.Unit` + The unit of each cube in the sequence. + + """ + sequence_units = [] + for i, cube in enumerate(sequence_data): + if cube.unit is None: + raise ValueError("{0}th cube in sequence does not have unit set.".format(i)) + else: + sequence_units.append(cube.unit) + return sequence_units diff --git a/ndcube/visualization/animation.py b/ndcube/visualization/animation.py index 867363854..c508a9ad7 100644 --- a/ndcube/visualization/animation.py +++ b/ndcube/visualization/animation.py @@ -62,9 +62,6 @@ class ImageAnimatorNDCubeSequence(ImageAnimatorWCS): """ def __init__(self, seq, wcs=None, **kwargs): - if seq._common_axis is not None: - raise ValueError("Common axis can't set set to use this class. " - "Use ImageAnimatorCommonAxisNDCubeSequence.") if wcs is None: wcs = seq[0].wcs self.sequence = seq.data From 4a53405958c57c9e8d578850cee462071d1d78bf Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Thu, 22 Mar 2018 10:37:58 -0400 Subject: [PATCH 03/30] Add NDCubeSequence ImageAnimator classes to plotting mixing. --- ndcube/mixins/sequence_plotting.py | 182 ++++++++++++++++++++++++++++- 1 file changed, 178 insertions(+), 4 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 09c354d4a..5e5fd4a64 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -1,11 +1,10 @@ import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt - import astropy.units as u +from sunpy.visualization.imageanimator import ImageAnimatorWCS from ndcube import utils -from ndcube.visualization import animation as ani __all__ = ['NDCubePlotMixin'] @@ -426,7 +425,7 @@ def _animate_ND_sequence(self, cubesequence, *args, **kwargs): Visualizes an NDCubeSequence of >2D NDCubes as 2D an animation with N-2 sliders. """ - return ani.ImageAnimatorNDCubeSequence(cubesequence, *args, **kwargs) + return ImageAnimatorNDCubeSequence(cubesequence, *args, **kwargs) def _animate_ND_sequence_as_Nminus1Danimation(self, cubesequence, *args, **kwargs): """ @@ -435,7 +434,182 @@ def _animate_ND_sequence_as_Nminus1Danimation(self, cubesequence, *args, **kwarg Called if plot_as_cube=True. """ - return ani.ImageAnimatorCommonAxisNDCubeSequence(cubesequence, *args, **kwargs) + return ImageAnimatorCommonAxisNDCubeSequence(cubesequence, *args, **kwargs) + + +class ImageAnimatorNDCubeSequence(ImageAnimatorWCS): + """ + Animates N-dimensional data with the associated astropy WCS object. + + The following keyboard shortcuts are defined in the viewer: + + left': previous step on active slider + right': next step on active slider + top': change the active slider up one + bottom': change the active slider down one + 'p': play/pause active slider + + This viewer can have user defined buttons added by specifying the labels + and functions called when those buttons are clicked as keyword arguments. + + Parameters + ---------- + seq: `ndcube.datacube.CubeSequence` + The list of cubes. + + image_axes: `list` + The two axes that make the image + + fig: `matplotlib.figure.Figure` + Figure to use + + axis_ranges: list of physical coordinates for array or None + If None array indices will be used for all axes. + If a list it should contain one element for each axis of the numpy array. + For the image axes a [min, max] pair should be specified which will be + passed to :func:`matplotlib.pyplot.imshow` as extent. + For the slider axes a [min, max] pair can be specified or an array the + same length as the axis which will provide all values for that slider. + If None is specified for an axis then the array indices will be used + for that axis. + + interval: `int` + Animation interval in ms + + colorbar: `bool` + Plot colorbar + + button_labels: `list` + List of strings to label buttons + + button_func: `list` + List of functions to map to the buttons + + unit_x_axis: `astropy.units.Unit` + The unit of x axis. + + unit_y_axis: `astropy.units.Unit` + The unit of y axis. + + Extra keywords are passed to imshow. + + """ + def __init__(self, seq, wcs=None, **kwargs): + if wcs is None: + wcs = seq[0].wcs + self.sequence = seq.data + self.cumul_cube_lengths = np.cumsum(np.ones(len(self.sequence))) + data_concat = np.stack([cube.data for cube in seq.data]) + # Add dimensions of length 1 of concatenated data array + # shape for an missing axes. + if seq[0].wcs.naxis != len(seq.dimensions) - 1: + new_shape = list(data_concat.shape) + for i in np.arange(seq[0].wcs.naxis)[seq[0].missing_axis[::-1]]: + new_shape.insert(i+1, 1) + data_concat = data_concat.reshape(new_shape) + # Add dummy axis to WCS object to represent sequence axis. + new_wcs = utils.wcs.append_sequence_axis_to_wcs(wcs) + + super(ImageAnimatorNDCubeSequence, self).__init__(data_concat, wcs=new_wcs, **kwargs) + + +class ImageAnimatorCommonAxisNDCubeSequence(ImageAnimatorWCS): + """ + Animates N-dimensional data with the associated astropy WCS object. + + The following keyboard shortcuts are defined in the viewer: + + left': previous step on active slider + right': next step on active slider + top': change the active slider up one + bottom': change the active slider down one + 'p': play/pause active slider + + This viewer can have user defined buttons added by specifying the labels + and functions called when those buttons are clicked as keyword arguments. + + Parameters + ---------- + seq: `ndcube.datacube.CubeSequence` + The list of cubes. + + image_axes: `list` + The two axes that make the image + + fig: `matplotlib.figure.Figure` + Figure to use + + axis_ranges: list of physical coordinates for array or None + If None array indices will be used for all axes. + If a list it should contain one element for each axis of the numpy array. + For the image axes a [min, max] pair should be specified which will be + passed to :func:`matplotlib.pyplot.imshow` as extent. + For the slider axes a [min, max] pair can be specified or an array the + same length as the axis which will provide all values for that slider. + If None is specified for an axis then the array indices will be used + for that axis. + + interval: `int` + Animation interval in ms + + colorbar: `bool` + Plot colorbar + + button_labels: `list` + List of strings to label buttons + + button_func: `list` + List of functions to map to the buttons + + unit_x_axis: `astropy.units.Unit` + The unit of x axis. + + unit_y_axis: `astropy.units.Unit` + The unit of y axis. + + Extra keywords are passed to imshow. + + """ + def __init__(self, seq, wcs=None, **kwargs): + if seq._common_axis is None: + raise ValueError("Common axis must be set to use this class. " + "Use ImageAnimatorNDCubeSequence.") + if wcs is None: + wcs = seq[0].wcs + self.sequence = seq.data + self.cumul_cube_lengths = np.cumsum(np.array( + [c.dimensions[0].value for c in self.sequence], dtype=int)) + data_concat = np.concatenate([cube.data for cube in seq.data], axis=seq._common_axis) + # Add dimensions of length 1 of concatenated data array + # shape for an missing axes. + if seq[0].wcs.naxis != len(seq.dimensions) - 1: + new_shape = list(data_concat.shape) + for i in np.arange(seq[0].wcs.naxis)[seq[0].missing_axis[::-1]]: + new_shape.insert(i, 1) + data_concat = data_concat.reshape(new_shape) + + super(ImageAnimatorCommonAxisNDCubeSequence, self).__init__( + data_concat, wcs=wcs, **kwargs) + + def update_plot(self, val, im, slider): + val = int(val) + ax_ind = self.slider_axes[slider.slider_ind] + ind = np.argmin(np.abs(self.axis_ranges[ax_ind] - val)) + self.frame_slice[ax_ind] = ind + list_slices_wcsaxes = list(self.slices_wcsaxes) + sequence_slice = utils.sequence._convert_cube_like_index_to_sequence_slice( + val, self.cumul_cube_lengths) + sequence_index = sequence_slice.sequence_index + cube_index = sequence_slice.common_axis_item + list_slices_wcsaxes[self.wcs.naxis-ax_ind-1] = cube_index + self.slices_wcsaxes = list_slices_wcsaxes + if val != slider.cval: + self.axes.reset_wcs( + wcs=self.sequence[sequence_index].wcs, slices=self.slices_wcsaxes) + self._set_unit_in_axis(self.axes) + im.set_array(self.data[self.frame_slice]) + slider.cval = val + def _determine_sequence_units(cubesequence_data, unit=None): From 6835bcd10148d02755c35fc33bbcf534f9a94ca0 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Thu, 22 Mar 2018 14:53:54 -0400 Subject: [PATCH 04/30] Fill in plot() function of NDCubeSequencePlotMixin and some minor kwarg API changes. --- ndcube/mixins/sequence_plotting.py | 160 +++++++++++++++++++++-------- 1 file changed, 119 insertions(+), 41 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 5e5fd4a64..73bb9c61c 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -12,8 +12,9 @@ "All sequence sub-cubes' unit attribute are not compatible with unit_y_axis set by user." class NDCubeSequencePlotMixin: - def plot(self, axes=None, plot_as_cube=False, plot_axes=None, axes_coordinates=None, - axes_units=None, data_unit=None, **kwargs): + def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, + plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, + **kwargs): """ Visualizes data in the NDCubeSequence. @@ -33,7 +34,7 @@ def plot(self, axes=None, plot_as_cube=False, plot_axes=None, axes_coordinates=N dimension. Default=False - plot_axes: `int` or iterable of one or two `int` + plot_axis_indices: `int` or iterable of one or two `int` Default is images_axes=[-1, -2]. If sequence only has one dimension, images_axes is forced to be 0. @@ -52,10 +53,52 @@ def plot(self, axes=None, plot_as_cube=False, plot_axes=None, axes_coordinates=N visualization is a 2D image or animation, i.e. if image_axis has length 2. """ - raise NotImplementedError() + # Ensure length of kwargs is consistent with dimensionality of sequence + # and setting of plot_as_cube. + naxis = len(cubesequence.dimensions) + _check_kwargs_dimensions(naxis, plot_as_cube, plot_axis_indices, axes_coordinates, axes_units) + if naxis == 1: + x_axis_coordinates, unit_x_axis = _derive_1D_coordinates_and_units(axes_coordinates, + axes_units) + # Make 1D line plot. + ax = self._plot_1D_sequence( + cubesequence, axes=axes, x_axis_coordinates=x_axis_coordinates, + unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) + elif naxis == 2: + if plot_as_cube: + if cubesequence._common_axis is None: + raise TypeError("Common axis must be set to plot sequence as cube.") + x_axis_coordinates, unit_x_axis = _derive_1D_coordinates_and_units( + axes_coordinates, axes_units) + ax = self._plot_2D_sequence_as_1Dline( + cubesequence, axes=axes, x_axis_coordinates=x_axis_coordinates, + unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) + else: + ax = self._plot_2D_sequence( + cubesequence, axes=None, plot_axis_indices=plot_axis_indices, + axes_coordinates=axes_coordinates, axes_units=axes_units, data_unit=data_unit, + **kwargs) + else: + if plot_axis_indices is None: + plot_axis_indices = [-1, -2] + if not plot_as_cube: + if axes_units is None: + axes_units = [None] * naxis + ax = ImageAnimatorNDCubeSequence( + cubesequence, image_axes=plot_axis_indices, + axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], + unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) + else: + if axes_units is None: + axes_units = [None] * (naxis-1) + ax = ImageAnimatorCommonAxisNDCubeSequence( + cubesequence, axes=axes, image_axes=plot_axis_indices, + axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], + unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) + return ax - def _plot_1D_sequence(self, cubesequence, axes=None, x_axis_coordinates=None, axes_units=None, - **kwargs): + def _plot_1D_sequence(self, cubesequence, axes=None, x_axis_coordinates=None, + unit_x_axis=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of scalar NDCubes as a line plot. @@ -66,7 +109,7 @@ def _plot_1D_sequence(self, cubesequence, axes=None, x_axis_coordinates=None, ax axes: `astropy.visualization.wcsaxes.core.WCSAxes` or ??? or None. The axes to plot onto. If None the current axes will be used. - x_axis_values: `numpy.ndarray` or `astropy.unit.Quantity` or `str` or `None` + x_axis_coordinates: `numpy.ndarray` or `astropy.unit.Quantity` or `str` or `None` Denotes the physical coordinates of the x-axis. If None, coordinates are derived from the WCS objects. If an `astropy.units.Quantity` or a `numpy.ndarray` gives the coordinates for @@ -79,17 +122,12 @@ def _plot_1D_sequence(self, cubesequence, axes=None, x_axis_coordinates=None, ax the coordinate denoted by x_axis_range. Not used if x_axis_range is a `numpy.ndarray` or the designated extra coordinate is a `numpy.ndarray` - unit_y_axis: `astropy.units.unit` or valid unit `str` + data_unit: `astropy.units.unit` or valid unit `str` The units into which the y-axis should be displayed. The unit attribute of all the sub-cubes must be compatible to set this kwarg. """ - if axes_units is None: - unit_x_axis = None - unit_y_axis = None - else: - unit_x_axis = axes_units[0] - unit_y_axis = axes_units[1] + unit_y_axis = data_unit # Check that the unit attribute is a set in all cubes and derive unit_y_axis if not set. sequence_units, unit_y_axis = _determine_sequence_units(cubesequence.data, unit_y_axis) # If all cubes have unit set, create a data quantity from cubes' data. @@ -196,7 +234,7 @@ def _plot_2D_sequence_as_1Dline(self, cubesequence, axes=None, x_axis_coordinate fig, ax = _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs) return ax - def _plot_2D_sequence(self, cubesequence, axes=None, plot_axes=None, axes_coordinates=None, + def _plot_2D_sequence(self, cubesequence, axes=None, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of 1D NDCubes as a 2D image. @@ -213,10 +251,10 @@ def _plot_2D_sequence(self, cubesequence, axes=None, plot_axes=None, axes_coordi axes_coordinates = [None, None] if axes_units is None: axes_units = [None, None] - if plot_axes is None: - plot_axes = [-1, -2] - # Convert plot_axes to array for function operations. - plot_axes = np.asarray(plot_axes) + if plot_axis_indices is None: + plot_axis_indices = [-1, -2] + # Convert plot_axis_indices to array for function operations. + plot_axis_indices = np.asarray(plot_axis_indices) # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. sequence_units, data_unit = _determine_sequence_units(cubesequence.data, data_unit) # If all cubes have unit set, create a data quantity from cube's data. @@ -226,7 +264,7 @@ def _plot_2D_sequence(self, cubesequence, axes=None, plot_axes=None, axes_coordi else: data = np.stack([cube.data for i, cube in enumerate(cubesequence.data)]) # Transpose data if user-defined images_axes require it. - if plot_axes[0] < plot_axes[1]: + if plot_axis_indices[0] < plot_axis_indices[1]: data = data.transpose() # Determine index of above axes variables corresponding to cube axis. cube_axis_index = 1 @@ -291,20 +329,20 @@ def _plot_2D_sequence(self, cubesequence, axes=None, plot_axes=None, axes_coordi # Since we can't assume the x-axis will be uniform, create NonUniformImage # axes and add it to the axes object. im_ax = mpl.image.NonUniformImage( - ax, extent=(axes_coordinates[plot_axes[0]][0], axes_coordinates[plot_axes[0]][-1], - axes_coordinates[plot_axes[1]][0], axes_coordinates[plot_axes[1]][-1]), + ax, extent=(axes_coordinates[plot_axis_indices[0]][0], axes_coordinates[plot_axis_indices[0]][-1], + axes_coordinates[plot_axis_indices[1]][0], axes_coordinates[plot_axis_indices[1]][-1]), **kwargs) - im_ax.set_data(axes_coordinates[plot_axes[0]], axes_coordinates[plot_axes[1]], data) + im_ax.set_data(axes_coordinates[plot_axis_indices[0]], axes_coordinates[plot_axis_indices[1]], data) ax.add_image(im_ax) # Set the limits, labels, etc. of the axes. - ax.set_xlim((axes_coordinates[plot_axes[0]][0], axes_coordinates[plot_axes[0]][-1])) - ax.set_ylim((axes_coordinates[plot_axes[1]][0], axes_coordinates[plot_axes[1]][-1])) - ax.set_xlabel(axes_labels[plot_axes[0]]) - ax.set_ylabel(axes_labels[plot_axes[1]]) + ax.set_xlim((axes_coordinates[plot_axis_indices[0]][0], axes_coordinates[plot_axis_indices[0]][-1])) + ax.set_ylim((axes_coordinates[plot_axis_indices[1]][0], axes_coordinates[plot_axis_indices[1]][-1])) + ax.set_xlabel(axes_labels[plot_axis_indices[0]]) + ax.set_ylabel(axes_labels[plot_axis_indices[1]]) return ax - def _plot_3D_sequence_as_2Dimage(self, cubesequence, axes=None, plot_axes=None, + def _plot_3D_sequence_as_2Dimage(self, cubesequence, axes=None, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ @@ -318,10 +356,10 @@ def _plot_3D_sequence_as_2Dimage(self, cubesequence, axes=None, plot_axes=None, axes_coordinates = [None, None] if axes_units is None: axes_units = [None, None] - if plot_axes is None: - plot_axes = [-1, -2] - # Convert plot_axes to array for function operations. - plot_axes = np.asarray(plot_axes) + if plot_axis_indices is None: + plot_axis_indices = [-1, -2] + # Convert plot_axis_indices to array for function operations. + plot_axis_indices = np.asarray(plot_axis_indices) # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. sequence_units, data_unit = _determine_sequence_units(cubesequence.data, data_unit) # If all cubes have unit set, create a data quantity from cube's data. @@ -332,7 +370,7 @@ def _plot_3D_sequence_as_2Dimage(self, cubesequence, axes=None, plot_axes=None, else: data = np.concatenate([cube.data for cube in cubesequence.data], axis=cubesequence._common_axis) - if plot_axes[0] < plot_axes[1]: + if plot_axis_indices[0] < plot_axis_indices[1]: data = data.transpose() # Determine index of common axis and other cube axis. common_axis_index = cubesequence._common_axis @@ -406,16 +444,16 @@ def _plot_3D_sequence_as_2Dimage(self, cubesequence, axes=None, plot_axes=None, # Since we can't assume the x-axis will be uniform, create NonUniformImage # axes and add it to the axes object. im_ax = mpl.image.NonUniformImage( - ax, extent=(axes_coordinates[plot_axes[0]][0], axes_coordinates[plot_axes[0]][-1], - axes_coordinates[plot_axes[1]][0], axes_coordinates[plot_axes[1]][-1]), + ax, extent=(axes_coordinates[plot_axis_indices[0]][0], axes_coordinates[plot_axis_indices[0]][-1], + axes_coordinates[plot_axis_indices[1]][0], axes_coordinates[plot_axis_indices[1]][-1]), **kwargs) - im_ax.set_data(axes_coordinates[plot_axes[0]], axes_coordinates[plot_axes[1]], data) + im_ax.set_data(axes_coordinates[plot_axis_indices[0]], axes_coordinates[plot_axis_indices[1]], data) ax.add_image(im_ax) # Set the limits, labels, etc. of the axes. - ax.set_xlim((axes_coordinates[plot_axes[0]][0], axes_coordinates[plot_axes[0]][-1])) - ax.set_ylim((axes_coordinates[plot_axes[1]][0], axes_coordinates[plot_axes[1]][-1])) - ax.set_xlabel(axes_labels[plot_axes[0]]) - ax.set_ylabel(axes_labels[plot_axes[1]]) + ax.set_xlim((axes_coordinates[plot_axis_indices[0]][0], axes_coordinates[plot_axis_indices[0]][-1])) + ax.set_ylim((axes_coordinates[plot_axis_indices[1]][0], axes_coordinates[plot_axis_indices[1]][-1])) + ax.set_xlabel(axes_labels[plot_axis_indices[0]]) + ax.set_ylabel(axes_labels[plot_axis_indices[1]]) return ax @@ -691,7 +729,6 @@ def _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kw ylim = kwargs.pop("ylim", None) # Plot data fig, ax = plt.subplots(1, 1) - print(xdata.shape, ydata.shape) ax.errorbar(xdata, ydata, yerror, **kwargs) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) @@ -724,3 +761,44 @@ def _get_all_cube_units(sequence_data): else: sequence_units.append(cube.unit) return sequence_units + + +def _check_kwargs_dimensions(naxis, plot_as_cube, plot_axis_indices, axes_coordinates, axes_units): + kws = [plot_axis_indices, axes_coordinates, axes_units] + kwarg_types = [(int), (u.Quantity, np.ndarray, str), (u.Unit, str)] + kwarg_names = ["plot_axis_indices", "axes_coordinates", "axes_unit"] + if plot_as_cube is True: + naxis = naxis - 1 + if naxis == 1: + kw_lens = [naxis] * len(kws) + for i in range(len(kws)): + if (not isinstance(kws[i], list)) and (kws[i] is not None): + kws[i] = [kws[i]] + else: + kw_lens = [2] + [naxis] * (len(kws) - 1) + for i in range(len(kws)): + if kws[i] is not None: + _check_single_kwarg_dimensions(kws[i], kw_lens[i], kwarg_types[i], kwarg_names[i]) + + +def _check_single_kwarg_dimensions(kw, kw_len, kw_types, str_kw): + if len(kw) != kw_len: + raise ValueError("length of {0} must be {1}.".format(str_kw, kw_len)) + if not isinstance(kw[0], kw_types): + raise TypeError("{0} must be one of ({1}) or list of one of ({1}).".format(str_kw, kw_types)) + + +def _derive_1D_coordinates_and_units(axes_coordinates, axes_units): + if axes_coordinates is None: + x_axis_coordinates = axes_coordinates + else: + if not isinstance(axes_coordinates, list): + axes_coordinates = [axes_coordinates] + x_axis_coordinates = axes_coordinates[0] + if axes_units is None: + unit_x_axis = axes_units + else: + if not isinstance(axes_units, list): + axes_units = [axes_units] + unit_x_axis = axes_units[0] + return x_axis_coordinates, unit_x_axis From 6b5a8c52239df04833166918a087ba832d474ec5 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Thu, 22 Mar 2018 16:04:43 -0400 Subject: [PATCH 05/30] Create paths for line animation options in NDCubeSequencePlotMixin with placeholder errors. --- ndcube/mixins/sequence_plotting.py | 119 +++++++++++++++++------------ 1 file changed, 70 insertions(+), 49 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 73bb9c61c..677332325 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -34,9 +34,18 @@ def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, dimension. Default=False - plot_axis_indices: `int` or iterable of one or two `int` - Default is images_axes=[-1, -2]. If sequence only has one dimension, - images_axes is forced to be 0. + plot_axis_indices: `int` or iterable of one or two `int`. + If two axis indices are given, the sequence is visualized as an image or + 2D animation, assuming the sequence has at least 2 dimensions. + (N.B. If plot_as_cube is True, the number of sequence dimensions is effectively + reduced by 1.) The dimension indicated by the 0th index is displayed on the + x-axis while the dimension indicated by the 1st index is displayed on the y-axis. + If only one axis index is given (either as an int or a list of one int), + then a 1D line animation is produced with the indicated dimension on the x-axis + and other dimensions represented by animations sliders. + Default=[-1, -2]. If sequence only has one dimension (or effectively one if + plot_as_cube is True), plot_axis_indices is ignored and a staice 1D line plot + is produced. axes_coordinates: `list` of physical coordinates for image axes and sliders or `None` If None coordinates derived from the WCS objects will be used for all axes. @@ -53,6 +62,14 @@ def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, visualization is a 2D image or animation, i.e. if image_axis has length 2. """ + # If plot_axis_indices, axes_coordinates, axes_units are not None and not lists, + # convert to lists for consistent indexing behaviour. + if (not isinstance(plot_axis_indices, list)) and (plot_axis_indices is not None): + plot_axis_indices = [plot_axis_indices] + if (not isinstance(axes_coordinates, list)) and (axes_coordinates is not None): + axes_coordinates = [axes_coordinates] + if (not isinstance(axes_units, list)) and (axes_units is not None): + axes_units = [axes_units] # Ensure length of kwargs is consistent with dimensionality of sequence # and setting of plot_as_cube. naxis = len(cubesequence.dimensions) @@ -62,7 +79,7 @@ def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, axes_units) # Make 1D line plot. ax = self._plot_1D_sequence( - cubesequence, axes=axes, x_axis_coordinates=x_axis_coordinates, + cubesequence, x_axis_coordinates=x_axis_coordinates, unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) elif naxis == 2: if plot_as_cube: @@ -71,33 +88,41 @@ def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, x_axis_coordinates, unit_x_axis = _derive_1D_coordinates_and_units( axes_coordinates, axes_units) ax = self._plot_2D_sequence_as_1Dline( - cubesequence, axes=axes, x_axis_coordinates=x_axis_coordinates, + cubesequence, x_axis_coordinates=x_axis_coordinates, unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) else: - ax = self._plot_2D_sequence( - cubesequence, axes=None, plot_axis_indices=plot_axis_indices, - axes_coordinates=axes_coordinates, axes_units=axes_units, data_unit=data_unit, - **kwargs) + if plot_axis_indices is None: + plot_axis_indices = [-1, -2] + if len(plot_axis_indices) == 2: + ax = self._plot_2D_sequence( + cubesequence, plot_axis_indices=plot_axis_indices, + axes_coordinates=axes_coordinates, axes_units=axes_units, + data_unit=data_unit, **kwargs) + elif len(plot_axis_indices) == 1: + raise NotImplementedError("Will depend on LineAnimator class in SunPy 0.9") else: if plot_axis_indices is None: plot_axis_indices = [-1, -2] - if not plot_as_cube: - if axes_units is None: - axes_units = [None] * naxis - ax = ImageAnimatorNDCubeSequence( - cubesequence, image_axes=plot_axis_indices, - axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], - unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) + if len(plot_axis_indices) == 2: + if not plot_as_cube: + if axes_units is None: + axes_units = [None] * naxis + ax = ImageAnimatorNDCubeSequence( + cubesequence, image_axes=plot_axis_indices, + axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], + unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) + else: + if axes_units is None: + axes_units = [None] * (naxis-1) + ax = ImageAnimatorCommonAxisNDCubeSequence( + cubesequence, image_axes=plot_axis_indices, + axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], + unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) else: - if axes_units is None: - axes_units = [None] * (naxis-1) - ax = ImageAnimatorCommonAxisNDCubeSequence( - cubesequence, axes=axes, image_axes=plot_axis_indices, - axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], - unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) + raise NotImplementedError("Will depend on LineAnimator class in SunPy 0.9") return ax - def _plot_1D_sequence(self, cubesequence, axes=None, x_axis_coordinates=None, + def _plot_1D_sequence(self, cubesequence, x_axis_coordinates=None, unit_x_axis=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of scalar NDCubes as a line plot. @@ -106,9 +131,6 @@ def _plot_1D_sequence(self, cubesequence, axes=None, x_axis_coordinates=None, Parameters ---------- - axes: `astropy.visualization.wcsaxes.core.WCSAxes` or ??? or None. - The axes to plot onto. If None the current axes will be used. - x_axis_coordinates: `numpy.ndarray` or `astropy.unit.Quantity` or `str` or `None` Denotes the physical coordinates of the x-axis. If None, coordinates are derived from the WCS objects. @@ -168,7 +190,7 @@ def _plot_1D_sequence(self, cubesequence, axes=None, x_axis_coordinates=None, fig, ax = _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs) return ax - def _plot_2D_sequence_as_1Dline(self, cubesequence, axes=None, x_axis_coordinates=None, + def _plot_2D_sequence_as_1Dline(self, cubesequence, x_axis_coordinates=None, axes_units=None, **kwargs): """ Visualizes an NDCubeSequence of 1D NDCubes with a common axis as a line plot. @@ -234,7 +256,7 @@ def _plot_2D_sequence_as_1Dline(self, cubesequence, axes=None, x_axis_coordinate fig, ax = _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs) return ax - def _plot_2D_sequence(self, cubesequence, axes=None, plot_axis_indices=None, axes_coordinates=None, + def _plot_2D_sequence(self, cubesequence, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of 1D NDCubes as a 2D image. @@ -342,7 +364,7 @@ def _plot_2D_sequence(self, cubesequence, axes=None, plot_axis_indices=None, axe return ax - def _plot_3D_sequence_as_2Dimage(self, cubesequence, axes=None, plot_axis_indices=None, + def _plot_3D_sequence_as_2Dimage(self, cubesequence, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ @@ -764,28 +786,27 @@ def _get_all_cube_units(sequence_data): def _check_kwargs_dimensions(naxis, plot_as_cube, plot_axis_indices, axes_coordinates, axes_units): - kws = [plot_axis_indices, axes_coordinates, axes_units] - kwarg_types = [(int), (u.Quantity, np.ndarray, str), (u.Unit, str)] - kwarg_names = ["plot_axis_indices", "axes_coordinates", "axes_unit"] if plot_as_cube is True: naxis = naxis - 1 - if naxis == 1: - kw_lens = [naxis] * len(kws) - for i in range(len(kws)): - if (not isinstance(kws[i], list)) and (kws[i] is not None): - kws[i] = [kws[i]] - else: - kw_lens = [2] + [naxis] * (len(kws) - 1) - for i in range(len(kws)): - if kws[i] is not None: - _check_single_kwarg_dimensions(kws[i], kw_lens[i], kwarg_types[i], kwarg_names[i]) - - -def _check_single_kwarg_dimensions(kw, kw_len, kw_types, str_kw): - if len(kw) != kw_len: - raise ValueError("length of {0} must be {1}.".format(str_kw, kw_len)) - if not isinstance(kw[0], kw_types): - raise TypeError("{0} must be one of ({1}) or list of one of ({1}).".format(str_kw, kw_types)) + if (plot_axis_indices is not None) and (naxis > 1): + if len(plot_axis_indices) not in [1, 2]: + raise ValueError("plot_axis_indices can have at most length 2.") + if axes_coordinates is not None: + if len(axes_coordinates) != naxis: + raise ValueError("length of axes_coordinates must be {0}.".format(naxis)) + ax_coord_types = (u.Quantity, np.ndarray, str) + for axis_coordinate in axes_coordinates: + if not instance(axis_coordinate, ax_coord_types): + raise TypeError("axes_coordinates must be one of {0} or list of {0}.".format( + ax_coord_types)) + if axes_units is not None: + if len(axes_units) != naxis: + raise ValueError("length of axes_units must be {0}.".format(naxis)) + ax_unit_types = (u.Unit, str) + for axis_coordinate in axes_units: + if not instance(axis_coordinate, ax_coord_types): + raise TypeError("axes_units must be one of {0} or list of {0}.".format( + ax_coord_types)) def _derive_1D_coordinates_and_units(axes_coordinates, axes_units): From d6b2c24baa955c7b14ecf2a8c923e5ca0db08aa0 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Thu, 22 Mar 2018 17:33:04 -0400 Subject: [PATCH 06/30] Wrote LineAnimator classes for NDCubeSequence in sequence and cube-like dimension states. Also got NDCubeSequencePlotMixin.plot working with LineAnimatorNDCubeSequence class for 2D sequences. --- ndcube/mixins/sequence_plotting.py | 360 ++++++++++++++++++++++++++++- 1 file changed, 356 insertions(+), 4 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 677332325..d073afedf 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -2,7 +2,7 @@ import matplotlib as mpl import matplotlib.pyplot as plt import astropy.units as u -from sunpy.visualization.imageanimator import ImageAnimatorWCS +from sunpy.visualization.imageanimator import ImageAnimatorWCS, LineAnimator from ndcube import utils @@ -99,7 +99,21 @@ def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, axes_coordinates=axes_coordinates, axes_units=axes_units, data_unit=data_unit, **kwargs) elif len(plot_axis_indices) == 1: - raise NotImplementedError("Will depend on LineAnimator class in SunPy 0.9") + xlabel = kwargs.pop("xlabel", None) + ylabel = kwargs.pop("ylabel", None) + xlim = kwargs.pop("xlim", None) + ylim = kwargs.pop("ylim", None) + if axes_units is not None: + unit_x_axis = axes_units[plot_axis_indices[0]] + else: + unit_x_axis = None + print(plot_axis_indices[0], axes_coordinates, unit_x_axis) + ax = LineAnimatorNDCubeSequence( + cubesequence, plot_axis_index=plot_axis_indices[0], + axis_ranges=axes_coordinates, + unit_x_axis=unit_x_axis, + data_unit=data_unit)#, xlabel=xlabel, ylabel=ylabel, +# xlim=xlim, ylim=ylim) else: if plot_axis_indices is None: plot_axis_indices = [-1, -2] @@ -114,7 +128,7 @@ def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, else: if axes_units is None: axes_units = [None] * (naxis-1) - ax = ImageAnimatorCommonAxisNDCubeSequence( + ax = ImageAnimatorCubeLikeNDCubeSequence( cubesequence, image_axes=plot_axis_indices, axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) @@ -573,7 +587,7 @@ def __init__(self, seq, wcs=None, **kwargs): super(ImageAnimatorNDCubeSequence, self).__init__(data_concat, wcs=new_wcs, **kwargs) -class ImageAnimatorCommonAxisNDCubeSequence(ImageAnimatorWCS): +class ImageAnimatorCubeLikeNDCubeSequence(ImageAnimatorWCS): """ Animates N-dimensional data with the associated astropy WCS object. @@ -671,6 +685,344 @@ def update_plot(self, val, im, slider): slider.cval = val +class LineAnimatorNDCubeSequence(LineAnimator): + """ + Animates N-dimensional data with the associated astropy WCS object. + + The following keyboard shortcuts are defined in the viewer: + + left': previous step on active slider + right': next step on active slider + top': change the active slider up one + bottom': change the active slider down one + 'p': play/pause active slider + + This viewer can have user defined buttons added by specifying the labels + and functions called when those buttons are clicked as keyword arguments. + + Parameters + ---------- + seq: `ndcube.datacube.CubeSequence` + The list of cubes. + + image_axes: `list` + The two axes that make the image + + fig: `matplotlib.figure.Figure` + Figure to use + + axis_ranges: list of physical coordinates for array or None + If None array indices will be used for all axes. + If a list it should contain one element for each axis of the numpy array. + For the image axes a [min, max] pair should be specified which will be + passed to :func:`matplotlib.pyplot.imshow` as extent. + For the slider axes a [min, max] pair can be specified or an array the + same length as the axis which will provide all values for that slider. + If None is specified for an axis then the array indices will be used + for that axis. + + interval: `int` + Animation interval in ms + + colorbar: `bool` + Plot colorbar + + button_labels: `list` + List of strings to label buttons + + button_func: `list` + List of functions to map to the buttons + + unit_x_axis: `astropy.units.Unit` + The unit of x axis. + + unit_y_axis: `astropy.units.Unit` + The unit of y axis. + + Extra keywords are passed to imshow. + + """ + def __init__(self, seq, plot_axis_index=None, axis_ranges=None, unit_x_axis=None, + data_unit=None, xlabel=None, ylabel=None, xlim=None, ylim=None, **kwargs): + if plot_axis_index is None: + plot_axis_index = -1 + #try: + # sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) + #except ValueError: + # sequence_error = None + # Form single cube of data from sequence. + #if sequence_error is None: + # data_concat = np.stack([cube.data for i, cube in enumerate(seq.data)]) + #else: + # data_concat = np.stack([(cube.data * sequence_units[i]).to(data_unit).value + # for i, cube in enumerate(seq.data)]) + + # Combine data from cubes in sequence. + # If cubes have masks, make result a masked array. + cubes_with_mask = np.array([False if cube.mask is None else True for cube in seq.data]) + if not cubes_with_mask.all(): + data_concat = np.stack([cube.data for cube in seq.data]) + else: + datas = [] + masks = [] + for i, cube in enumerate(seq.data): + datas.append(cube.data) + if cubes_with_mask[i]: + masks.append(cube.mask) + else: + masks.append(np.zeros_like(cube.data, dtype=bool)) + data_concat = np.ma.masked_array(np.stack(datas), np.stack(masks)) + # Ensure plot_axis_index is represented in the positive convention. + if plot_axis_index < 0: + plot_axis_index = len(seq.dimensions) + plot_axis_index + # Calculate the x-axis values if axis_ranges not supplied. + cube_plot_axis_index = plot_axis_index - 1 + if axis_ranges is None: + axis_ranges = [None] * len(seq.dimensions) + if plot_axis_index == 0: + axis_ranges[plot_axis_index] = np.arange(len(seq.data)) + else: + # Define unit of x-axis if not supplied by user. + if unit_x_axis is None: + wcs_plot_axis_index = utils.cube.data_axis_to_wcs_axis( + cube_plot_axis_index, seq[0].missing_axis) + unit_x_axis = np.asarray(seq[0].wcs.wcs.cunit)[wcs_plot_axis_index] + # Get x-axis values from each cube and combine into a single + # array for axis_ranges kwargs. + x_axis_coords = _get_non_common_axis_x_axis_coords(seq.data, cube_plot_axis_index) + axis_ranges[plot_axis_index] = np.stack(x_axis_coords) + # Set x-axis label. + if xlabel is None: + xlabel = "{0} [{1}]".format(seq.world_axis_physical_types[plot_axis_index], + unit_x_axis) + else: + # If the axis range is being defined by an extra coordinate... + if isinstance(axis_ranges[plot_axis_index], str): + axis_extra_coord = axis_ranges[plot_axis_index] + if plot_axis_index == 0: + # If the sequence axis is the plot axis, use + # sequence_axis_extra_coords to get the extra coord values + # for whole sequence. + x_axis_coords = seq.sequence_axis_extra_coords[axis_extra_coord] + if isinstance(x_axis_coords, u.Quantity) and (unit_x_axis is not None): + x_axis_coords = x_axis_coords.to(unit_x_axis).value + else: + # Else get extra coord values from each cube and + # combine into a single array for axis_ranges kwargs. + # First, confirm extra coord is of same type and corresponds + # to same axes in each cube. + extra_coord_type = np.empty(len(seq.data), dtype=object) + extra_coord_axes = np.empty(len(seq.data), dtype=object) + x_axis_coords = [] + for i, cube in enumerate(seq.data): + cube_axis_extra_coord = cube.extra_coords[axis_extra_coord] + extra_coord_type[i] = type(cube_axis_extra_coord["value"]) + extra_coord_axes[i] = cube_axis_extra_coord["axis"] + x_axis_coords.append(cube_axis_extra_coord["value"]) + if not extra_coord_type.all() == extra_coord_type[0]: + raise TypeError("Extra coord {0} must be of same type for all NDCubes to " + "use it to define a plot axis.".format(axis_extra_coord)) + else: + extra_coord_type = extra_coord_type[0] + if not extra_coord_axes.all() == extra_coord_axes[0]: + raise ValueError("Extra coord {0} must correspond to same axes in each " + "NDCube to use it to define a plot axis.".format( + axis_extra_coord)) + else: + if isinstance(extra_coord_axes[0], (int, np.int64)): + extra_coord_axes = [int(extra_coord_axes[0])] + else: + extra_coord_axes = list(extra_coord_axes[0]).sort() + # If the extra coord is a quantity, convert to the correct unit. + if extra_coord_type is u.Quantity: + if unit_x_axis is None and extra_coord_type is u.Quantity: + unit_x_axis = seq[0].extra_coords[axis_extra_coord]["value"].unit + x_axis_coords = [x_axis_value.to(unit_x_axis).value + for x_axis_value in x_axis_coords] + # If extra coord is same for each cube, storing + # values as single 1D axis range will suffice. + if ((np.array(x_axis_coords) == x_axis_coords[0]).all() and + (len(extra_coord_axes) == 1)): + x_axis_coords = x_axis_coords[0] + else: + if len(extra_coord_axes) != data_concat.ndim: + independent_axes = list(range(seq[0].data.ndim)) + for i in list(extra_coord_axes)[::-1]: + independent_axes.pop(i) + x_axis_coords_copy = copy.deepcopy(x_axis_coords) + x_axis_coords = [] + for i, x_axis_cube_coords in enumerate(x_axis_coords_copy): + tile_shape = tuple(list( + np.array(seq[i].data.shape)[independent_axes]) + \ + [1]*len(x_axis_cube_coords)) + x_axis_cube_coords = np.tile(x_axis_cube_coords, tile_shape) + # Since np.tile puts original array's dimensions as last, + # reshape x_axis_cube_coords to cube's shape. + x_axis_cube_coords = x_axis_cube_coords.reshape(seq[i].data.shape) + x_axis_coords.append(x_axis_cube_coords) + x_axis_coords = np.stack(x_axis_coords) + # Set x-axis label. + if xlabel is None: + xlabel = "{0} [{1}]".format(axis_extra_coord, unit_x_axis) + # Re-enter x-axis values into axis_ranges + axis_ranges[plot_axis_index] = x_axis_coords + # Make label for y-axis. + if ylabel is None: + ylabel = "Data [{0}]".format(data_unit) + + super(LineAnimatorNDCubeSequence, self).__init__( + data_concat, plot_axis_index=plot_axis_index, axis_ranges=axis_ranges, + xlabel=xlabel, ylabel=ylabel, xlim=xlim, ylim=ylim, **kwargs) + + +class LineAnimatorCubeLikeNDCubeSequence(LineAnimator): + """ + Animates N-dimensional data with the associated astropy WCS object. + + The following keyboard shortcuts are defined in the viewer: + + left': previous step on active slider + right': next step on active slider + top': change the active slider up one + bottom': change the active slider down one + 'p': play/pause active slider + + This viewer can have user defined buttons added by specifying the labels + and functions called when those buttons are clicked as keyword arguments. + + Parameters + ---------- + seq: `ndcube.datacube.CubeSequence` + The list of cubes. + + image_axes: `list` + The two axes that make the image + + fig: `matplotlib.figure.Figure` + Figure to use + + axis_ranges: list of physical coordinates for array or None + If None array indices will be used for all axes. + If a list it should contain one element for each axis of the numpy array. + For the image axes a [min, max] pair should be specified which will be + passed to :func:`matplotlib.pyplot.imshow` as extent. + For the slider axes a [min, max] pair can be specified or an array the + same length as the axis which will provide all values for that slider. + If None is specified for an axis then the array indices will be used + for that axis. + + interval: `int` + Animation interval in ms + + colorbar: `bool` + Plot colorbar + + button_labels: `list` + List of strings to label buttons + + button_func: `list` + List of functions to map to the buttons + + unit_x_axis: `astropy.units.Unit` + The unit of x axis. + + unit_y_axis: `astropy.units.Unit` + The unit of y axis. + + Extra keywords are passed to imshow. + + """ + def __init__(self, seq, plot_axis_index=None, axis_ranges=None, unit_x_axis=None, + data_unit=None, xlabel=None, ylabel=None, xlim=None, ylim=None, **kwargs): + if plot_axis_index is None: + plot_axis_index = -1 + #try: + # sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) + #except ValueError: + # sequence_error = None + # Form single cube of data from sequence. + #if sequence_error is None: + # data_concat = np.concatenate([cube.data for i, cube in enumerate(seq.data)], + # axis=seq._common_axis)) + #else: + # data_concat = np.concatenate([(cube.data * sequence_units[i]).to(data_unit).value + # for i, cube in enumerate(seq.data)], + # axis=seq._common_axis) + data_concat = np.concatenate([cube.data for i, cube in enumerate(seq.data)], + axis=seq._common_axis) + # Ensure plot_axis_index is represented in the positive convention. + if plot_axis_index < 0: + plot_axis_index = len(seq.cube_like_dimensions) + plot_axis_index + # Calculate the x-axis values if axis_ranges not supplied. + if axis_ranges is None: + axis_ranges = [None] * len(seq.cube_like_dimensions) + # Define unit of x-axis if not supplied by user. + if unit_x_axis is None: + wcs_plot_axis_index = utils.cube.data_axis_to_wcs_axis( + plot_axis_index, seq[0].missing_axis) + unit_x_axis = np.asarray( + seq[0].wcs.wcs.cunit)[np.invert(seq[0].missing_axis)][wcs_plot_axis_index] + if plot_axis_index == seq._common_axis: + # Determine whether common axis is dependent. + x_axis_coords = np.concatenate( + [cube.axis_world_coords(plot_axis_index).to(unit_x_axis).value + for cube in seq.data], axis=plot_axis_index) + dependent_axes = utils.wcs.get_dependent_data_axes( + seq[0].wcs, plot_axis_index, seq[0].missing_axis) + if len(dependent_axes) > 1: + independent_axes = list(range(data_concat.ndim)) + for i in list(dependent_axes)[::-1]: + independent_axes.pop(i) + # Expand dimensionality of x_axis_cube_coords using np.tile + tile_shape = tuple(list(np.array( + data_concat.shape)[independent_axes]) + [1]*len(dependent_axes)) + x_axis_coords = np.tile(x_axis_cube_coords, tile_shape) + # Since np.tile puts original array's dimensions as last, + # reshape x_axis_cube_coords to cube's shape. + x_axis_coords = x_axis_coords.reshape(seq.cube_like_dimensions.value) + else: + # Get x-axis values from each cube and combine into a single + # array for axis_ranges kwargs. + x_axis_coords = _get_non_common_axis_x_axis_coords(seq.data, plot_axis_index) + axis_ranges[plot_axis_index] = np.concatenate(x_axis_coords, axis=seq._common_axis) + # Set axis labels and limits, etc. + if xlabel is None: + xlabel = "{0} [{1}]".format( + seq.cube_like_world_axis_physical_types[plot_axis_index], unit_x_axis) + if ylabel is None: + ylabel = "Data [{0}]".format(data_unit) + + super(LineAnimatorCubeLikeNDCubeSequence, self).__init__( + data_concat, plot_axis_index=plot_axis_index, axis_ranges=axis_ranges, + xlabel=xlabel, ylabel=ylabel, xlim=xlim, ylim=ylim, **kwargs) + + +def _get_non_common_axis_x_axis_coords(seq_data, plot_axis_index): + """Get coords of an axis from NDCubes and combine into single array.""" + x_axis_coords = [] + for i, cube in enumerate(seq_data): + # Get the x-axis coordinates for each cube. + #x_axis_cube_coords = cube.axis_world_coords(plot_axis_index).to(unit_x_axis).value + x_axis_cube_coords = cube.axis_world_coords(plot_axis_index).value + # If the returned x-values have fewer dimensions than the cube, + # repeat the x-values through the higher dimensions. + if x_axis_cube_coords.shape != cube.data.shape: + # Get sequence axes dependent and independent of plot_axis_index. + dependent_axes = utils.wcs.get_dependent_data_axes( + cube.wcs, plot_axis_index, cube.missing_axis) + independent_axes = list(range(len(cube.dimensions))) + for i in list(dependent_axes)[::-1]: + independent_axes.pop(i) + # Expand dimensionality of x_axis_cube_coords using np.tile + tile_shape = tuple(list(np.array( + cube.data.shape)[independent_axes]) + [1]*len(dependent_axes)) + x_axis_cube_coords = np.tile(x_axis_cube_coords, tile_shape) + # Since np.tile puts original array's dimensions as last, + # reshape x_axis_cube_coords to cube's shape. + x_axis_cube_coords = x_axis_cube_coords.reshape(cube.data.shape) + x_axis_coords.append(x_axis_cube_coords) + return x_axis_coords + def _determine_sequence_units(cubesequence_data, unit=None): """ From cdb6a1d701743b943052745f862e71f367978099 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Thu, 22 Mar 2018 17:42:14 -0400 Subject: [PATCH 07/30] Implement NDCubeSequence LineAnimator in NDCubeSequencePlotMixin for cases where sequence dimensions > 2. --- ndcube/mixins/sequence_plotting.py | 31 +++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index d073afedf..e8d42fabf 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -107,13 +107,12 @@ def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, unit_x_axis = axes_units[plot_axis_indices[0]] else: unit_x_axis = None - print(plot_axis_indices[0], axes_coordinates, unit_x_axis) ax = LineAnimatorNDCubeSequence( cubesequence, plot_axis_index=plot_axis_indices[0], axis_ranges=axes_coordinates, unit_x_axis=unit_x_axis, - data_unit=data_unit)#, xlabel=xlabel, ylabel=ylabel, -# xlim=xlim, ylim=ylim) + data_unit=data_unit, xlabel=xlabel, ylabel=ylabel, + xlim=xlim, ylim=ylim) else: if plot_axis_indices is None: plot_axis_indices = [-1, -2] @@ -132,8 +131,30 @@ def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, cubesequence, image_axes=plot_axis_indices, axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) - else: - raise NotImplementedError("Will depend on LineAnimator class in SunPy 0.9") + elif len(plot_axis_indices) == 1: + xlabel = kwargs.pop("xlabel", None) + ylabel = kwargs.pop("ylabel", None) + xlim = kwargs.pop("xlim", None) + ylim = kwargs.pop("ylim", None) + if axes_units is not None: + unit_x_axis = axes_units[plot_axis_indices[0]] + else: + unit_x_axis = None + if not plot_as_cube: + ax = LineAnimatorNDCubeSequence( + cubesequence, plot_axis_index=plot_axis_indices[0], + axis_ranges=axes_coordinates, + unit_x_axis=unit_x_axis, + data_unit=data_unit, xlabel=xlabel, ylabel=ylabel, + xlim=xlim, ylim=ylim) + else: + ax = LineAnimatorCubeLikeNDCubeSequence( + cubesequence, plot_axis_index=plot_axis_indices[0], + axis_ranges=axes_coordinates, + unit_x_axis=unit_x_axis, + data_unit=data_unit, xlabel=xlabel, ylabel=ylabel, + xlim=xlim, ylim=ylim) + #raise NotImplementedError("Will depend on LineAnimator class in SunPy 0.9") return ax def _plot_1D_sequence(self, cubesequence, x_axis_coordinates=None, From 31a6afaba9339d0a5c9a7fb967795e4e6bb1fd31 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Fri, 23 Mar 2018 10:59:33 -0400 Subject: [PATCH 08/30] Create place-holder for new top level NDCubeSequence plot_as_cube method. Also updaed docstrings of plot and plot_as_cube to reflect the what the API will become over future commits. --- ndcube/mixins/sequence_plotting.py | 152 +++++++++++++++++++++++------ 1 file changed, 123 insertions(+), 29 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index e8d42fabf..4b6a43e44 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -12,13 +12,12 @@ "All sequence sub-cubes' unit attribute are not compatible with unit_y_axis set by user." class NDCubeSequencePlotMixin: - def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, - plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, - **kwargs): + def plot(self, cubesequence, axes=None, plot_axis_indices=None, + axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ - Visualizes data in the NDCubeSequence. + Visualizes data in the NDCubeSequence with the sequence axis as a separate dimension. - Based on the dimensionality of the sequence and value of image_axes kwarg, + Based on the dimensionality of the sequence and value of plot_axis_indices kwarg, a Line/Image Animation/Plot is produced. Parameters @@ -26,40 +25,62 @@ def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, axes: `astropy.visualization.wcsaxes.core.WCSAxes` or ??? or None. The axes to plot onto. If None the current axes will be used. - plot_as_cube: `bool` - If the sequence has a common axis, visualize the sequence as a single - cube where the sequence sub-cubes are sequential along the common axis. - This will result in the sequence being treated as a cube with N-1 dimensions - where N is the number of dimensions of the sequence, including the sequence - dimension. - Default=False - plot_axis_indices: `int` or iterable of one or two `int`. If two axis indices are given, the sequence is visualized as an image or 2D animation, assuming the sequence has at least 2 dimensions. - (N.B. If plot_as_cube is True, the number of sequence dimensions is effectively - reduced by 1.) The dimension indicated by the 0th index is displayed on the + The dimension indicated by the 0th index is displayed on the x-axis while the dimension indicated by the 1st index is displayed on the y-axis. If only one axis index is given (either as an int or a list of one int), then a 1D line animation is produced with the indicated dimension on the x-axis and other dimensions represented by animations sliders. - Default=[-1, -2]. If sequence only has one dimension (or effectively one if - plot_as_cube is True), plot_axis_indices is ignored and a staice 1D line plot - is produced. + Default=[-1, -2]. If sequence only has one dimension, + plot_axis_indices is ignored and a static 1D line plot is produced. - axes_coordinates: `list` of physical coordinates for image axes and sliders or `None` + axes_coordinates: `None` or `list` of `None` `astropy.units.Quantity` `numpy.ndarray` `str` + Denotes physical coordinates for plot and slider axes. If None coordinates derived from the WCS objects will be used for all axes. - If a list, it should contain one element for each axis. Each element should - be either an `astropy.units.Quantity` or a `numpy.ndarray` of coordinates for - each pixel, or a `str` denoting a valid extra coordinate. - - axes_units: length 2 `list` of `astropy.unit.Unit` or valid unit `str` or None. - Denotes unit in which X-axis and Y-axis, respectively, should be displayed. - Only used if corresponding entry in axes_coordinates is a Quantity. + If a list, its length should equal either the number sequence dimensions or + the length of plot_axis_indices. + If the length equals the number of sequence dimensions, each element describes + the coordinates of the corresponding sequence dimension. + If the length equals the length of plot_axis_indices, + the 0th entry describes the coordinates of the x-axis + while (if length is 2) the 1st entry describes the coordinates of the y-axis. + Slider axes are implicitly set to None. + If the number of sequence dimensions equals the length of plot_axis_indices, + the latter convention takes precedence. + The value of each entry should be either + `None` (implies derive the coordinates from the WCS objects), + an `astropy.units.Quantity` or a `numpy.ndarray` of coordinates for each pixel, + or a `str` denoting a valid extra coordinate. + + axes_units: `None or `list` of `None`, `astropy.units.Unit` and/or `str` + If None units derived from the WCS objects will be used for all axes. + If a list, its length should equal either the number sequence dimensions or + the length of plot_axis_indices. + If the length equals the number of sequence dimensions, each element gives the + unit in which the coordinates along the corresponding sequence dimension should + displayed whether they be a plot axes or a slider axes. + If the length equals the length of plot_axis_indices, + the 0th entry describes the unit in which the x-axis coordinates should be displayed + while (if length is 2) the 1st entry describes the unit in which the y-axis should + be displayed. Slider axes are implicitly set to None. + If the number of sequence dimensions equals the length of plot_axis_indices, + the latter convention takes precedence. + The value of each entry should be either + `None` (implies derive the unit from the WCS object of the 0th sub-cube), + `astropy.units.Unit` or a valid unit `str`. data_unit: `astropy.unit.Unit` or valid unit `str` or None - Unit in which data in a 2D image or animation should be displayed. Only used if - visualization is a 2D image or animation, i.e. if image_axis has length 2. + Unit in which data be displayed. If the length of plot_axis_indices is 2, + a 2D image/animation is produced and data_unit determines the unit represented by + the color table. If the length of plot_axis_indices is 1, + a 1D plot/animation is produced and data_unit determines the unit in which the + y-axis is displayed. + + Returns + ------- + ax: ??? """ # If plot_axis_indices, axes_coordinates, axes_units are not None and not lists, @@ -154,9 +175,82 @@ def plot(self, cubesequence, axes=None, plot_as_cube=False, plot_as_line=False, unit_x_axis=unit_x_axis, data_unit=data_unit, xlabel=xlabel, ylabel=ylabel, xlim=xlim, ylim=ylim) - #raise NotImplementedError("Will depend on LineAnimator class in SunPy 0.9") return ax + def plot_as_cube(self, cubesequence, axes=None, plot_axis_indices=None, + axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): + """ + Visualizes data in the NDCubeSequence with the sequence axis folded into the common axis. + + Based on the cube-like dimensionality of the sequence and value of plot_axis_indices + kwarg, a Line/Image Plot/Animation is produced. + + Parameters + ---------- + axes: `astropy.visualization.wcsaxes.core.WCSAxes` or ??? or None. + The axes to plot onto. If None the current axes will be used. + + plot_axis_indices: `int` or iterable of one or two `int`. + If two axis indices are given, the sequence is visualized as an image or + 2D animation, assuming the sequence has at least 2 cube-like dimensions. + The cube-like dimension indicated by the 0th index is displayed on the + x-axis while the cube-like dimension indicated by the 1st index is + displayed on the y-axis. If only one axis index is given (either as an int + or a list of one int), then a 1D line animation is produced with the indicated + cube-like dimension on the x-axis and other cube-like dimensions represented + by animations sliders. + Default=[-1, -2]. If sequence only has one cube-like dimension, + plot_axis_indices is ignored and a static 1D line plot is produced. + + axes_coordinates: `None` or `list` of `None` `astropy.units.Quantity` `numpy.ndarray` `str` + Denotes physical coordinates for plot and slider axes. + If None coordinates derived from the WCS objects will be used for all axes. + If a list, its length should equal either the number cube-like dimensions or + the length of plot_axis_indices. + If the length equals the number of cube-like dimensions, each element describes + the coordinates of the corresponding cube-like dimension. + If the length equals the length of plot_axis_indices, + the 0th entry describes the coordinates of the x-axis + while (if length is 2) the 1st entry describes the coordinates of the y-axis. + Slider axes are implicitly set to None. + If the number of cube-like dimensions equals the length of plot_axis_indices, + the latter convention takes precedence. + The value of each entry should be either + `None` (implies derive the coordinates from the WCS objects), + an `astropy.units.Quantity` or a `numpy.ndarray` of coordinates for each pixel, + or a `str` denoting a valid extra coordinate. + + axes_units: `None or `list` of `None`, `astropy.units.Unit` and/or `str` + If None units derived from the WCS objects will be used for all axes. + If a list, its length should equal either the number cube-like dimensions or + the length of plot_axis_indices. + If the length equals the number of cube-like dimensions, each element gives the + unit in which the coordinates along the corresponding cube-like dimension should + displayed whether they be a plot axes or a slider axes. + If the length equals the length of plot_axis_indices, + the 0th entry describes the unit in which the x-axis coordinates should be displayed + while (if length is 2) the 1st entry describes the unit in which the y-axis should + be displayed. Slider axes are implicitly set to None. + If the number of cube-like dimensions equals the length of plot_axis_indices, + the latter convention takes precedence. + The value of each entry should be either + `None` (implies derive the unit from the WCS object of the 0th sub-cube), + `astropy.units.Unit` or a valid unit `str`. + + data_unit: `astropy.unit.Unit` or valid unit `str` or None + Unit in which data be displayed. If the length of plot_axis_indices is 2, + a 2D image/animation is produced and data_unit determines the unit represented by + the color table. If the length of plot_axis_indices is 1, + a 1D plot/animation is produced and data_unit determines the unit in which the + y-axis is displayed. + + Returns + ------- + ax: ??? + + """ + pass + def _plot_1D_sequence(self, cubesequence, x_axis_coordinates=None, unit_x_axis=None, data_unit=None, **kwargs): """ From 6c698db80ab446090dbdd49b1924f0ecd84dc59c Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Fri, 23 Mar 2018 11:29:55 -0400 Subject: [PATCH 09/30] Outlined close to (but not) working implementation for NDCubeSeqeuncePlotMixin.plot_as_cube. --- ndcube/mixins/sequence_plotting.py | 48 +++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 4b6a43e44..5a783b594 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -249,7 +249,53 @@ def plot_as_cube(self, cubesequence, axes=None, plot_axis_indices=None, ax: ??? """ - pass + # Verify common axis is set. + if cubesequence._common_axis is None: + raise TypeError("Common axis must be set.") + # If plot_axis_indices, axes_coordinates, axes_units are not None and not lists, + # convert to lists for consistent indexing behaviour. + if (not isinstance(plot_axis_indices, list)) and (plot_axis_indices is not None): + plot_axis_indices = [plot_axis_indices] + if (not isinstance(axes_coordinates, list)) and (axes_coordinates is not None): + axes_coordinates = [axes_coordinates] + if (not isinstance(axes_units, list)) and (axes_units is not None): + axes_units = [axes_units] + # Ensure length of kwargs is consistent with dimensionality of sequence + # and setting of plot_as_cube. + naxis = len(cubesequence.cube_like_dimensions) + _check_kwargs_dimensions(naxis, plot_as_cube=True, + plot_axis_indices, axes_coordinates, axes_units) + # Set default values of kwargs is not det by user. + if plot_axis_indices is None: + plot_axis_indices = [-1, -2] + # Produce plot/image/animation based on cube-like dimensions of sequence. + if naxis == 1: + x_axis_coordinates, unit_x_axis = _derive_1D_coordinates_and_units( + axes_coordinates, axes_units) + ax = self._plot_2D_sequence_as_1Dline( + cubesequence, x_axis_coordinates=x_axis_coordinates, + unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) + else: + if len(plot_axis_indices) == 1: + ax = LineAnimatorCubeLikeNDCubeSequence( + cubesequence, plot_axis_index=plot_axis_indices[0], + axis_ranges=axes_coordinates, + unit_x_axis=unit_x_axis, + data_unit=data_unit, xlabel=xlabel, ylabel=ylabel, + xlim=xlim, ylim=ylim) + elif len(plot_axis_indices) == 2: + if naxis == 2: + ax = self._plot_3D_sequence_as_2Dimage( + axes=axes, plot_axis_indices=plot_axis_indices, + axes_coordinates=axes_coordinates, axes_units=axes_units, + data_unit=data_unit, **kwargs) + else: + ax = ImageAnimatorCubeLikeNDCubeSequence( + cubesequence, image_axes=plot_axis_indices, + axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], + unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) + + return ax def _plot_1D_sequence(self, cubesequence, x_axis_coordinates=None, unit_x_axis=None, data_unit=None, **kwargs): From 5910439e08b16dbf2f206cb837fe19be0f9caa02 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Fri, 23 Mar 2018 11:51:13 -0400 Subject: [PATCH 10/30] Simpify implementation of NDCubeSequencePlotMixin.plot. --- ndcube/mixins/sequence_plotting.py | 88 +++++++----------------------- 1 file changed, 21 insertions(+), 67 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 5a783b594..11bc3e98c 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -94,7 +94,10 @@ def plot(self, cubesequence, axes=None, plot_axis_indices=None, # Ensure length of kwargs is consistent with dimensionality of sequence # and setting of plot_as_cube. naxis = len(cubesequence.dimensions) - _check_kwargs_dimensions(naxis, plot_as_cube, plot_axis_indices, axes_coordinates, axes_units) + _check_kwargs_dimensions(naxis, plot_axis_indices, axes_coordinates, axes_units) + # Set default values of kwargs is not det by user. + if plot_axis_indices is None: + plot_axis_indices = [-1, -2] if naxis == 1: x_axis_coordinates, unit_x_axis = _derive_1D_coordinates_and_units(axes_coordinates, axes_units) @@ -102,79 +105,33 @@ def plot(self, cubesequence, axes=None, plot_axis_indices=None, ax = self._plot_1D_sequence( cubesequence, x_axis_coordinates=x_axis_coordinates, unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) - elif naxis == 2: - if plot_as_cube: - if cubesequence._common_axis is None: - raise TypeError("Common axis must be set to plot sequence as cube.") - x_axis_coordinates, unit_x_axis = _derive_1D_coordinates_and_units( - axes_coordinates, axes_units) - ax = self._plot_2D_sequence_as_1Dline( - cubesequence, x_axis_coordinates=x_axis_coordinates, - unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) - else: + else: + if len(plot_axis_indices) == 1: + if axes_units is not None: + unit_x_axis = axes_units[plot_axis_indices[0]] + else: + unit_x_axis = None + ax = LineAnimatorNDCubeSequence( + cubesequence, plot_axis_index=plot_axis_indices[0], + axis_ranges=axes_coordinates, + unit_x_axis=unit_x_axis, + data_unit=data_unit, **kwargs) + elif len(plot_axis_indices) == 2: if plot_axis_indices is None: plot_axis_indices = [-1, -2] - if len(plot_axis_indices) == 2: + if naxis == 2: ax = self._plot_2D_sequence( cubesequence, plot_axis_indices=plot_axis_indices, axes_coordinates=axes_coordinates, axes_units=axes_units, data_unit=data_unit, **kwargs) - elif len(plot_axis_indices) == 1: - xlabel = kwargs.pop("xlabel", None) - ylabel = kwargs.pop("ylabel", None) - xlim = kwargs.pop("xlim", None) - ylim = kwargs.pop("ylim", None) - if axes_units is not None: - unit_x_axis = axes_units[plot_axis_indices[0]] - else: - unit_x_axis = None - ax = LineAnimatorNDCubeSequence( - cubesequence, plot_axis_index=plot_axis_indices[0], - axis_ranges=axes_coordinates, - unit_x_axis=unit_x_axis, - data_unit=data_unit, xlabel=xlabel, ylabel=ylabel, - xlim=xlim, ylim=ylim) - else: - if plot_axis_indices is None: - plot_axis_indices = [-1, -2] - if len(plot_axis_indices) == 2: - if not plot_as_cube: + else: if axes_units is None: axes_units = [None] * naxis ax = ImageAnimatorNDCubeSequence( cubesequence, image_axes=plot_axis_indices, axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) - else: - if axes_units is None: - axes_units = [None] * (naxis-1) - ax = ImageAnimatorCubeLikeNDCubeSequence( - cubesequence, image_axes=plot_axis_indices, - axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], - unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) - elif len(plot_axis_indices) == 1: - xlabel = kwargs.pop("xlabel", None) - ylabel = kwargs.pop("ylabel", None) - xlim = kwargs.pop("xlim", None) - ylim = kwargs.pop("ylim", None) - if axes_units is not None: - unit_x_axis = axes_units[plot_axis_indices[0]] - else: - unit_x_axis = None - if not plot_as_cube: - ax = LineAnimatorNDCubeSequence( - cubesequence, plot_axis_index=plot_axis_indices[0], - axis_ranges=axes_coordinates, - unit_x_axis=unit_x_axis, - data_unit=data_unit, xlabel=xlabel, ylabel=ylabel, - xlim=xlim, ylim=ylim) - else: - ax = LineAnimatorCubeLikeNDCubeSequence( - cubesequence, plot_axis_index=plot_axis_indices[0], - axis_ranges=axes_coordinates, - unit_x_axis=unit_x_axis, - data_unit=data_unit, xlabel=xlabel, ylabel=ylabel, - xlim=xlim, ylim=ylim) + return ax def plot_as_cube(self, cubesequence, axes=None, plot_axis_indices=None, @@ -263,8 +220,7 @@ def plot_as_cube(self, cubesequence, axes=None, plot_axis_indices=None, # Ensure length of kwargs is consistent with dimensionality of sequence # and setting of plot_as_cube. naxis = len(cubesequence.cube_like_dimensions) - _check_kwargs_dimensions(naxis, plot_as_cube=True, - plot_axis_indices, axes_coordinates, axes_units) + _check_kwargs_dimensions(naxis, plot_axis_indices, axes_coordinates, axes_units) # Set default values of kwargs is not det by user. if plot_axis_indices is None: plot_axis_indices = [-1, -2] @@ -1298,9 +1254,7 @@ def _get_all_cube_units(sequence_data): return sequence_units -def _check_kwargs_dimensions(naxis, plot_as_cube, plot_axis_indices, axes_coordinates, axes_units): - if plot_as_cube is True: - naxis = naxis - 1 +def _check_kwargs_dimensions(naxis, plot_axis_indices, axes_coordinates, axes_units): if (plot_axis_indices is not None) and (naxis > 1): if len(plot_axis_indices) not in [1, 2]: raise ValueError("plot_axis_indices can have at most length 2.") From cae5a0ed8227bd7f0053235ef932ed9081d85296 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Fri, 23 Mar 2018 15:28:47 -0400 Subject: [PATCH 11/30] Move checking and prepping of some kwargs in NDCubeSequencePlotMixin plot and and plot_as_cube methods to external function. Also added more comments. --- ndcube/mixins/sequence_plotting.py | 207 +++++++++++++++++------------ 1 file changed, 124 insertions(+), 83 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 11bc3e98c..9489ddba6 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -83,48 +83,33 @@ def plot(self, cubesequence, axes=None, plot_axis_indices=None, ax: ??? """ - # If plot_axis_indices, axes_coordinates, axes_units are not None and not lists, - # convert to lists for consistent indexing behaviour. - if (not isinstance(plot_axis_indices, list)) and (plot_axis_indices is not None): - plot_axis_indices = [plot_axis_indices] - if (not isinstance(axes_coordinates, list)) and (axes_coordinates is not None): - axes_coordinates = [axes_coordinates] - if (not isinstance(axes_units, list)) and (axes_units is not None): - axes_units = [axes_units] - # Ensure length of kwargs is consistent with dimensionality of sequence - # and setting of plot_as_cube. + # Check kwargs are in consistent formats and set default values if not done so by user. naxis = len(cubesequence.dimensions) - _check_kwargs_dimensions(naxis, plot_axis_indices, axes_coordinates, axes_units) - # Set default values of kwargs is not det by user. - if plot_axis_indices is None: - plot_axis_indices = [-1, -2] + plot_axis_indices, axes_coordinates, axes_units = _prep_axes_kwargs( + naxis, plot_axis_indices, axes_coordinates, axes_units) if naxis == 1: - x_axis_coordinates, unit_x_axis = _derive_1D_coordinates_and_units(axes_coordinates, - axes_units) # Make 1D line plot. - ax = self._plot_1D_sequence( - cubesequence, x_axis_coordinates=x_axis_coordinates, - unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) + ax = self._plot_1D_sequence(cubesequence, x_axis_coordinates, + unit_x_axis, data_unit, **kwargs) else: if len(plot_axis_indices) == 1: + # Since sequence has more than 1 dimension and number of plot axes is 1, + # produce a 1D line animation. if axes_units is not None: unit_x_axis = axes_units[plot_axis_indices[0]] else: unit_x_axis = None - ax = LineAnimatorNDCubeSequence( - cubesequence, plot_axis_index=plot_axis_indices[0], - axis_ranges=axes_coordinates, - unit_x_axis=unit_x_axis, - data_unit=data_unit, **kwargs) + ax = LineAnimatorNDCubeSequence(cubesequence, plot_axis_indices[0], axes_coordinates, + unit_x_axis, data_unit, **kwargs) elif len(plot_axis_indices) == 2: - if plot_axis_indices is None: - plot_axis_indices = [-1, -2] if naxis == 2: - ax = self._plot_2D_sequence( - cubesequence, plot_axis_indices=plot_axis_indices, - axes_coordinates=axes_coordinates, axes_units=axes_units, - data_unit=data_unit, **kwargs) + # Since sequence has 2 dimensions and number of plot axes is 2, + # produce a 2D image. + ax = self._plot_2D_sequence(cubesequence, plot_axis_indices, axes_coordinates, + axes_units, data_unit, **kwargs) else: + # Since sequence has more than 2 dimensions and number of plot axes is 2, + # produce a 2D animation. if axes_units is None: axes_units = [None] * naxis ax = ImageAnimatorNDCubeSequence( @@ -209,52 +194,46 @@ def plot_as_cube(self, cubesequence, axes=None, plot_axis_indices=None, # Verify common axis is set. if cubesequence._common_axis is None: raise TypeError("Common axis must be set.") - # If plot_axis_indices, axes_coordinates, axes_units are not None and not lists, - # convert to lists for consistent indexing behaviour. - if (not isinstance(plot_axis_indices, list)) and (plot_axis_indices is not None): - plot_axis_indices = [plot_axis_indices] - if (not isinstance(axes_coordinates, list)) and (axes_coordinates is not None): - axes_coordinates = [axes_coordinates] - if (not isinstance(axes_units, list)) and (axes_units is not None): - axes_units = [axes_units] - # Ensure length of kwargs is consistent with dimensionality of sequence - # and setting of plot_as_cube. + # Check kwargs are in consistent formats and set default values if not done so by user. naxis = len(cubesequence.cube_like_dimensions) - _check_kwargs_dimensions(naxis, plot_axis_indices, axes_coordinates, axes_units) - # Set default values of kwargs is not det by user. - if plot_axis_indices is None: - plot_axis_indices = [-1, -2] + plot_axis_indices, axes_coordinates, axes_units = _prep_axes_kwargs( + naxis, plot_axis_indices, axes_coordinates, axes_units) # Produce plot/image/animation based on cube-like dimensions of sequence. if naxis == 1: - x_axis_coordinates, unit_x_axis = _derive_1D_coordinates_and_units( - axes_coordinates, axes_units) + # Since sequence has 1 cube-like dimension, produce a 1D line plot. ax = self._plot_2D_sequence_as_1Dline( cubesequence, x_axis_coordinates=x_axis_coordinates, unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) else: if len(plot_axis_indices) == 1: - ax = LineAnimatorCubeLikeNDCubeSequence( - cubesequence, plot_axis_index=plot_axis_indices[0], - axis_ranges=axes_coordinates, - unit_x_axis=unit_x_axis, - data_unit=data_unit, xlabel=xlabel, ylabel=ylabel, - xlim=xlim, ylim=ylim) + # Since sequence has more than 1 cube-like dimension and + # number of plot axes is 1, produce a 1D line animation. + if axes_units is not None: + unit_x_axis = axes_units[plot_axis_indices[0]] + else: + unit_x_axis = None + ax = LineAnimatorCubeLikeNDCubeSequence(cubesequence, plot_axis_indices[0], + axes_coordinates, unit_x_axis, + data_unit=data_unit, **kwargs) elif len(plot_axis_indices) == 2: if naxis == 2: - ax = self._plot_3D_sequence_as_2Dimage( - axes=axes, plot_axis_indices=plot_axis_indices, - axes_coordinates=axes_coordinates, axes_units=axes_units, - data_unit=data_unit, **kwargs) + # Since sequence has 2 cube-like dimensions and + # number of plot axes is 2, produce a 2D image. + ax = self._plot_3D_sequence_as_2Dimage(axes, plot_axis_indices, + axes_coordinates, axes_units, + data_unit, **kwargs) else: - ax = ImageAnimatorCubeLikeNDCubeSequence( - cubesequence, image_axes=plot_axis_indices, - axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], - unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) - + # Since sequence has more than 2 cube-like dimensions and + # number of plot axes is 2, produce a 2D animation. + ax = ImageAnimatorCubeLikeNDCubeSequence(cubesequence, plot_axis_indices, + axes_coordinates, + axes_units[plot_axis_indices[0]], + axes_units[plot_axis_indices[1]], + **kwargs) return ax - def _plot_1D_sequence(self, cubesequence, x_axis_coordinates=None, - unit_x_axis=None, data_unit=None, **kwargs): + def _plot_1D_sequence(self, cubesequence, axes_coordinates=None, + axes_units=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of scalar NDCubes as a line plot. @@ -262,15 +241,16 @@ def _plot_1D_sequence(self, cubesequence, x_axis_coordinates=None, Parameters ---------- - x_axis_coordinates: `numpy.ndarray` or `astropy.unit.Quantity` or `str` or `None` + axes_coordinates: `numpy.ndarray` `astropy.unit.Quantity` `str` `None` or length 1 `list` Denotes the physical coordinates of the x-axis. + If list, must be of length 1 containing one object of one of the other allowed types. If None, coordinates are derived from the WCS objects. If an `astropy.units.Quantity` or a `numpy.ndarray` gives the coordinates for each pixel along the x-axis. If a `str`, denotes the extra coordinate to be used. The extra coordinate must correspond to the sequence axis. - unit_x_axis: `astropy.unit.Unit` or valid unit `str` + axes_units: `astropy.unit.Unit` or valid unit `str` or length 1 `list` of those types. Unit in which X-axis should be displayed. Must be compatible with the unit of the coordinate denoted by x_axis_range. Not used if x_axis_range is a `numpy.ndarray` or the designated extra coordinate is a `numpy.ndarray` @@ -280,8 +260,11 @@ def _plot_1D_sequence(self, cubesequence, x_axis_coordinates=None, the sub-cubes must be compatible to set this kwarg. """ - unit_y_axis = data_unit + # Derive x-axis coordinates and unit from inputs. + x_axis_coordinates, unit_x_axis = _derive_1D_coordinates_and_units(axes_coordinates, + axes_units) # Check that the unit attribute is a set in all cubes and derive unit_y_axis if not set. + unit_y_axis = data_unit sequence_units, unit_y_axis = _determine_sequence_units(cubesequence.data, unit_y_axis) # If all cubes have unit set, create a data quantity from cubes' data. if sequence_units is not None: @@ -322,7 +305,7 @@ def _plot_1D_sequence(self, cubesequence, x_axis_coordinates=None, return ax def _plot_2D_sequence_as_1Dline(self, cubesequence, x_axis_coordinates=None, - axes_units=None, **kwargs): + axes_units=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of 1D NDCubes with a common axis as a line plot. @@ -333,13 +316,11 @@ def _plot_2D_sequence_as_1Dline(self, cubesequence, x_axis_coordinates=None, Same as _plot_1D_sequence """ - if axes_units is None: - unit_x_axis = None - unit_y_axis = None - else: - unit_x_axis = axes_units[0] - unit_y_axis = axes_units[1] + # Derive x-axis coordinates and unit from inputs. + x_axis_coordinates, unit_x_axis = _derive_1D_coordinates_and_units(axes_coordinates, + axes_units) # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. + unit_y_axis = data_unit sequence_units, unit_y_axis = _determine_sequence_units(cubesequence.data, unit_y_axis) # If all cubes have unit set, create a y data quantity from cube's data. if sequence_units is not None: @@ -387,6 +368,7 @@ def _plot_2D_sequence_as_1Dline(self, cubesequence, x_axis_coordinates=None, fig, ax = _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs) return ax + def _plot_2D_sequence(self, cubesequence, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ @@ -1254,26 +1236,85 @@ def _get_all_cube_units(sequence_data): return sequence_units -def _check_kwargs_dimensions(naxis, plot_axis_indices, axes_coordinates, axes_units): - if (plot_axis_indices is not None) and (naxis > 1): - if len(plot_axis_indices) not in [1, 2]: - raise ValueError("plot_axis_indices can have at most length 2.") +def _prep_axes_kwargs(naxis, plot_axis_indices, axes_coordinates, axes_units): + """ + Checks input values are correct based on number of sequence dimensions and sets defaults. + + Parameters + ---------- + plot_axis_indices: As for NDCubeSequencePlotMixin.plot or NDCubeSequencePlotMixin.plot_as_cube + + axes_coordinates: As for NDCubeSequencePlotMixin.plot or NDCubeSequencePlotMixin.plot_as_cube + + axes_units: As for NDCubeSequencePlotMixin.plot or NDCubeSequencePlotMixin.plot_as_cube + + Returns + ------- + plot_axis_indices: None or `list` of `int` of length 1 or 2. + + axes_coordinates: `None` or `list` of `None` `astropy.units.Quantity` `numpy.ndarray` `str` + Length of list equals number of sequence axes. + + axes_units: None or `list` of `None` `astropy.units.Unit` or `str` + Length of list equals number of sequence axes. + + """ + # If plot_axis_indices, axes_coordinates, axes_units are not None and not lists, + # convert to lists for consistent indexing behaviour. + if (not isinstance(plot_axis_indices, list)) and (plot_axis_indices is not None): + plot_axis_indices = [plot_axis_indices] + if (not isinstance(axes_coordinates, list)) and (axes_coordinates is not None): + axes_coordinates = [axes_coordinates] + if (not isinstance(axes_units, list)) and (axes_units is not None): + axes_units = [axes_units] + # Set default value of plot_axis_indices if not set by user. + if plot_axis_indices is None: + plot_axis_indices = [-1, -2] + # If number of sequence dimensions is greater than 1, + # ensure length of plot_axis_indices is 1 or 2. + # No need to check case where number of sequence dimensions is 1 + # as plot_axis_indices is ignored in that case. + if naxis > 1: + len(plot_axis_indices) not in [1, 2]: + raise ValueError("plot_axis_indices can have at most length 2.") + # If convention of axes_coordinates and axes_units being length of + # plot_axis_index is being used, convert to convention where their + # length equals sequence dimensions. Only do this if number of dimensions if + # greater than 1 as the conventions are equivalent if there is only one dimension. + if axes_coordinates is not None: + if len(axes_coordinates) == len(plot_axis_indices): + none_axes_coordinates = np.array([None] * naxis) + none_axes_coordinates[plot_axis_indices] = plot_axis_indices + axes_coordinates = list(none_axes_coordinates) + if axes_units is not None: + if len(axes_units) == len(plot_axis_indices): + none_axes_units = np.array([None] * naxis) + none_axes_units[plot_axis_indices] = plot_axis_indices + axes_units = list(none_axes_units) if axes_coordinates is not None: - if len(axes_coordinates) != naxis: + # Now axes_coordinates have been converted to a consistent convention, + # ensure their length equals the number of sequence dimensions. + len(axes_coordinates) != naxis: raise ValueError("length of axes_coordinates must be {0}.".format(naxis)) + # Ensure all elements in axes_coordinates are of correct types. ax_coord_types = (u.Quantity, np.ndarray, str) for axis_coordinate in axes_coordinates: - if not instance(axis_coordinate, ax_coord_types): + if axis_coordinate is not None and not instance(axis_coordinate, ax_coord_types): raise TypeError("axes_coordinates must be one of {0} or list of {0}.".format( - ax_coord_types)) + [None] + list(ax_coord_types))) if axes_units is not None: + # Now axes_units have been converted to a consistent convention, + # ensure their length equals the number of sequence dimensions. if len(axes_units) != naxis: raise ValueError("length of axes_units must be {0}.".format(naxis)) + # Ensure all elements in axes_units are of correct types. ax_unit_types = (u.Unit, str) - for axis_coordinate in axes_units: - if not instance(axis_coordinate, ax_coord_types): + for axis_unit in axes_units: + if axis_unit is not None and not instance(axis_unit, ax_unit_types): raise TypeError("axes_units must be one of {0} or list of {0}.".format( - ax_coord_types)) + ax_unit_types)) + + return plot_axis_indices, axes_coordinates, axes_units def _derive_1D_coordinates_and_units(axes_coordinates, axes_units): From 4fa0da3bb6a6457977ac4b663aef67564660a6f4 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Fri, 23 Mar 2018 15:48:52 -0400 Subject: [PATCH 12/30] Remove unused functions. --- ndcube/mixins/sequence_plotting.py | 112 +++++++---------------------- 1 file changed, 24 insertions(+), 88 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 9489ddba6..d3cdd276a 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -9,7 +9,7 @@ __all__ = ['NDCubePlotMixin'] NON_COMPATIBLE_UNIT_MESSAGE = \ - "All sequence sub-cubes' unit attribute are not compatible with unit_y_axis set by user." + "All sequence sub-cubes' unit attribute are not compatible with data_unit set by user." class NDCubeSequencePlotMixin: def plot(self, cubesequence, axes=None, plot_axis_indices=None, @@ -89,8 +89,8 @@ def plot(self, cubesequence, axes=None, plot_axis_indices=None, naxis, plot_axis_indices, axes_coordinates, axes_units) if naxis == 1: # Make 1D line plot. - ax = self._plot_1D_sequence(cubesequence, x_axis_coordinates, - unit_x_axis, data_unit, **kwargs) + ax = self._plot_1D_sequence(cubesequence, axes_coordinates, + axes_units, data_unit, **kwargs) else: if len(plot_axis_indices) == 1: # Since sequence has more than 1 dimension and number of plot axes is 1, @@ -463,15 +463,20 @@ def _plot_2D_sequence(self, cubesequence, plot_axis_indices=None, axes_coordinat fig, ax = plt.subplots(1, 1) # Since we can't assume the x-axis will be uniform, create NonUniformImage # axes and add it to the axes object. - im_ax = mpl.image.NonUniformImage( - ax, extent=(axes_coordinates[plot_axis_indices[0]][0], axes_coordinates[plot_axis_indices[0]][-1], - axes_coordinates[plot_axis_indices[1]][0], axes_coordinates[plot_axis_indices[1]][-1]), - **kwargs) - im_ax.set_data(axes_coordinates[plot_axis_indices[0]], axes_coordinates[plot_axis_indices[1]], data) + im_ax = mpl.image.NonUniformImage(ax, + extent=(axes_coordinates[plot_axis_indices[0]][0], + axes_coordinates[plot_axis_indices[0]][-1], + axes_coordinates[plot_axis_indices[1]][0], + axes_coordinates[plot_axis_indices[1]][-1]), + **kwargs) + im_ax.set_data(axes_coordinates[plot_axis_indices[0]], + axes_coordinates[plot_axis_indices[1]], data) ax.add_image(im_ax) # Set the limits, labels, etc. of the axes. - ax.set_xlim((axes_coordinates[plot_axis_indices[0]][0], axes_coordinates[plot_axis_indices[0]][-1])) - ax.set_ylim((axes_coordinates[plot_axis_indices[1]][0], axes_coordinates[plot_axis_indices[1]][-1])) + ax.set_xlim((axes_coordinates[plot_axis_indices[0]][0], + axes_coordinates[plot_axis_indices[0]][-1])) + ax.set_ylim((axes_coordinates[plot_axis_indices[1]][0], + axes_coordinates[plot_axis_indices[1]][-1])) ax.set_xlabel(axes_labels[plot_axis_indices[0]]) ax.set_ylabel(axes_labels[plot_axis_indices[1]]) @@ -593,23 +598,6 @@ def _plot_3D_sequence_as_2Dimage(self, cubesequence, plot_axis_indices=None, return ax - def _animate_ND_sequence(self, cubesequence, *args, **kwargs): - """ - Visualizes an NDCubeSequence of >2D NDCubes as 2D an animation with N-2 sliders. - - """ - return ImageAnimatorNDCubeSequence(cubesequence, *args, **kwargs) - - def _animate_ND_sequence_as_Nminus1Danimation(self, cubesequence, *args, **kwargs): - """ - Visualizes a common axis NDCubeSequence of >3D NDCubes as 2D animation with N-3 sliders. - - Called if plot_as_cube=True. - - """ - return ImageAnimatorCommonAxisNDCubeSequence(cubesequence, *args, **kwargs) - - class ImageAnimatorNDCubeSequence(ImageAnimatorWCS): """ Animates N-dimensional data with the associated astropy WCS object. @@ -1148,9 +1136,14 @@ def _determine_sequence_units(cubesequence_data, unit=None): """ # Check that the unit attribute is set of all cubes. If not, unit_y_axis + sequence_units = [] try: - sequence_units = np.array(_get_all_cube_units(cubesequence_data)) - except ValueError: + for i, cube in enumerate(sequence_data): + if cube.unit is None: + break + else: + sequence_units.append(cube.unit) + if len(sequence_units) != len(cubesequence_data): sequence_units = None # If all cubes have unit set, create a data quantity from cube's data. if sequence_units is not None: @@ -1161,38 +1154,6 @@ def _determine_sequence_units(cubesequence_data, unit=None): return sequence_units, unit -def _derive_1D_x_data(cubesequence, x_axis_values, unit_x_axis, sequence_is_1d=True): - # Derive x data from wcs is extra_coord not set. - if x_axis_values is None: - if sequence_is_1d: - # Since scalar NDCubes have no array/pixel indices, WCS translations don't work. - # Therefore x-axis values will be unitless sequence indices unless supplied by user - # or an extra coordinate is designated. - unit_x_axis = None - xdata = np.arange(int(cubesequence.dimensions[0].value)) - default_xlabel = "{0} [{1}]".format(cubesequence.world_axis_physical_types[0], - unit_x_axis) - else: - if unit_x_axis is None: - unit_x_axis = np.asarray(cubesequence[0].wcs.wcs.cunit)[ - np.invert(cubesequence[0].missing_axis)][0] - xdata = u.Quantity(np.concatenate([cube.axis_world_coords().to(unit_x_axis).value - for cube in cubesequence]), unit=unit_x_axis) - default_xlabel = "{0} [{1}]".format(cubesequence.cube_like_world_axis_physical_types[0], - unit_x_axis) - elif isinstance(x_axis_values, str): - # Else derive x-axis from extra coord. - if sequence_is_1d: - xdata = cubesequence.sequence_axis_extra_coords[x_axis_extra_coord] - else: - xdata = cubesequence.common_axis_extra_coords[x_axis_extra_coord] - if unit_x_axis is None and isinstance(xdata, u.Quantity): - unit_x_axis = xdata.unit - default_xlabel = "{0} [{1}]".format(x_axis_extra_coord, unit_x_axis) - - return xdata, unit_x_axis, default_xlabel - - def _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs): # Define plot settings if not set in kwargs. xlabel = kwargs.pop("xlabel", default_xlabel) @@ -1211,31 +1172,6 @@ def _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kw return fig, ax -def _get_all_cube_units(sequence_data): - """ - Return units of a sequence of NDCubes. - - Raises an error if any of the cube's don't have the unit attribute set. - - Parameters - ---------- - sequence_data: iterable of `ndcube.NDCube` of `astropy.nddata.NDData`. - - Returns - ------- - sequence_units: `list` of `astropy.units.Unit` - The unit of each cube in the sequence. - - """ - sequence_units = [] - for i, cube in enumerate(sequence_data): - if cube.unit is None: - raise ValueError("{0}th cube in sequence does not have unit set.".format(i)) - else: - sequence_units.append(cube.unit) - return sequence_units - - def _prep_axes_kwargs(naxis, plot_axis_indices, axes_coordinates, axes_units): """ Checks input values are correct based on number of sequence dimensions and sets defaults. @@ -1275,7 +1211,7 @@ def _prep_axes_kwargs(naxis, plot_axis_indices, axes_coordinates, axes_units): # No need to check case where number of sequence dimensions is 1 # as plot_axis_indices is ignored in that case. if naxis > 1: - len(plot_axis_indices) not in [1, 2]: + if len(plot_axis_indices) not in [1, 2]: raise ValueError("plot_axis_indices can have at most length 2.") # If convention of axes_coordinates and axes_units being length of # plot_axis_index is being used, convert to convention where their @@ -1294,7 +1230,7 @@ def _prep_axes_kwargs(naxis, plot_axis_indices, axes_coordinates, axes_units): if axes_coordinates is not None: # Now axes_coordinates have been converted to a consistent convention, # ensure their length equals the number of sequence dimensions. - len(axes_coordinates) != naxis: + if len(axes_coordinates) != naxis: raise ValueError("length of axes_coordinates must be {0}.".format(naxis)) # Ensure all elements in axes_coordinates are of correct types. ax_coord_types = (u.Quantity, np.ndarray, str) From 5fc66e52eb73c631462021b140d4f9e479c69796 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Sat, 24 Mar 2018 11:10:57 -0400 Subject: [PATCH 13/30] Enabled user to set data unit in LineAnimatorNDCubeSequence. --- ndcube/mixins/sequence_plotting.py | 68 +++++++++++++++--------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index d3cdd276a..5b98c2409 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -833,32 +833,35 @@ def __init__(self, seq, plot_axis_index=None, axis_ranges=None, unit_x_axis=None data_unit=None, xlabel=None, ylabel=None, xlim=None, ylim=None, **kwargs): if plot_axis_index is None: plot_axis_index = -1 - #try: - # sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) - #except ValueError: - # sequence_error = None - # Form single cube of data from sequence. - #if sequence_error is None: - # data_concat = np.stack([cube.data for i, cube in enumerate(seq.data)]) - #else: - # data_concat = np.stack([(cube.data * sequence_units[i]).to(data_unit).value - # for i, cube in enumerate(seq.data)]) + # Combine data from cubes in sequence. If all cubes have a unit, + # put data into data_unit. + sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) + if sequence_units is None: + if data_unit is None: + data_concat = np.stack([cube.data for i, cube in enumerate(seq.data)]) + else: + raise TypeError(NON_COMPATIBLE_UNIT_MESSAGE) + else: + data_concat = np.stack([(cube.data * sequence_units[i]).to(data_unit).value + for i, cube in enumerate(seq.data)]) - # Combine data from cubes in sequence. - # If cubes have masks, make result a masked array. + # If some cubes have a mask set, convert data to masked array. + # If other cubes do not have a mask set, set all mask to False. + # If no cubes have a mask, keep data as a simple array. cubes_with_mask = np.array([False if cube.mask is None else True for cube in seq.data]) - if not cubes_with_mask.all(): - data_concat = np.stack([cube.data for cube in seq.data]) - else: - datas = [] - masks = [] - for i, cube in enumerate(seq.data): - datas.append(cube.data) - if cubes_with_mask[i]: - masks.append(cube.mask) - else: - masks.append(np.zeros_like(cube.data, dtype=bool)) - data_concat = np.ma.masked_array(np.stack(datas), np.stack(masks)) + if cubes_with_mask.any(): + if cubes_with_mask.all(): + mask_concat = np.stack([cube.mask for cube in seq.data]) + else: + masks = [] + for i, cube in enumerate(seq.data): + if cubes_with_mask[i]: + masks.append(cube.mask) + else: + masks.append(np.zeros_like(cube.data, dtype=bool)) + mask_concat = np.stack(masks) + data_concat = np.ma.masked_array(data_concat, mask_concat) + # Ensure plot_axis_index is represented in the positive convention. if plot_axis_index < 0: plot_axis_index = len(seq.dimensions) + plot_axis_index @@ -1137,20 +1140,19 @@ def _determine_sequence_units(cubesequence_data, unit=None): """ # Check that the unit attribute is set of all cubes. If not, unit_y_axis sequence_units = [] - try: - for i, cube in enumerate(sequence_data): - if cube.unit is None: - break - else: - sequence_units.append(cube.unit) + for i, cube in enumerate(cubesequence_data): + if cube.unit is None: + break + else: + sequence_units.append(cube.unit) if len(sequence_units) != len(cubesequence_data): sequence_units = None # If all cubes have unit set, create a data quantity from cube's data. - if sequence_units is not None: + if sequence_units is None: + unit = None + else: if unit is None: unit = sequence_units[0] - else: - unit = None return sequence_units, unit From ccccaa962e9999e3cdb66917a50b1a9fb7ff2818 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Sat, 24 Mar 2018 18:42:25 -0400 Subject: [PATCH 14/30] Enable user to set data unit in LineAnimatorCubeLikeNDCubeSequence. --- ndcube/mixins/sequence_plotting.py | 45 ++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 5b98c2409..832b86989 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -1026,20 +1026,37 @@ def __init__(self, seq, plot_axis_index=None, axis_ranges=None, unit_x_axis=None data_unit=None, xlabel=None, ylabel=None, xlim=None, ylim=None, **kwargs): if plot_axis_index is None: plot_axis_index = -1 - #try: - # sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) - #except ValueError: - # sequence_error = None - # Form single cube of data from sequence. - #if sequence_error is None: - # data_concat = np.concatenate([cube.data for i, cube in enumerate(seq.data)], - # axis=seq._common_axis)) - #else: - # data_concat = np.concatenate([(cube.data * sequence_units[i]).to(data_unit).value - # for i, cube in enumerate(seq.data)], - # axis=seq._common_axis) - data_concat = np.concatenate([cube.data for i, cube in enumerate(seq.data)], - axis=seq._common_axis) + # Combine data from cubes in sequence. If all cubes have a unit, + # put data into data_unit. + sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) + if sequence_units is None: + if data_unit is None: + data_concat = np.concatenate([cube.data for i, cube in enumerate(seq.data)], + axis=seq._common_axis) + else: + raise TypeError(NON_COMPATIBLE_UNIT_MESSAGE) + else: + data_concat = np.concatenate([(cube.data * sequence_units[i]).to(data_unit).value + for i, cube in enumerate(seq.data)], + axis=seq._common_axis) + + # If some cubes have a mask set, convert data to masked array. + # If other cubes do not have a mask set, set all mask to False. + # If no cubes have a mask, keep data as a simple array. + cubes_with_mask = np.array([False if cube.mask is None else True for cube in seq.data]) + if cubes_with_mask.any(): + if cubes_with_mask.all(): + mask_concat = np.concatenate([cube.mask for cube in seq.data], axis=seq._common_axis) + else: + masks = [] + for i, cube in enumerate(seq.data): + if cubes_with_mask[i]: + masks.append(cube.mask) + else: + masks.append(np.zeros_like(cube.data, dtype=bool)) + mask_concat = np.concatenate(masks, axis=seq._common_axis) + data_concat = np.ma.masked_array(data_concat, mask_concat) + # Ensure plot_axis_index is represented in the positive convention. if plot_axis_index < 0: plot_axis_index = len(seq.cube_like_dimensions) + plot_axis_index From f359ab51e80aae2bfa3c53ee0c587503de631fcc Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Sat, 24 Mar 2018 23:21:13 -0400 Subject: [PATCH 15/30] Rewrote tiling of x-axis coords in LineAnimatorNDCubeSequence that will stop bugs when dimensions are of same length. Also made NDCubeSequencePlotMixin a proper class by replacing the cubesequence input arg to self. --- ndcube/mixins/sequence_plotting.py | 190 ++++++++++++++++------------- 1 file changed, 104 insertions(+), 86 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 832b86989..c09d2d4d1 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -12,7 +12,7 @@ "All sequence sub-cubes' unit attribute are not compatible with data_unit set by user." class NDCubeSequencePlotMixin: - def plot(self, cubesequence, axes=None, plot_axis_indices=None, + def plot(self, axes=None, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ Visualizes data in the NDCubeSequence with the sequence axis as a separate dimension. @@ -84,12 +84,12 @@ def plot(self, cubesequence, axes=None, plot_axis_indices=None, """ # Check kwargs are in consistent formats and set default values if not done so by user. - naxis = len(cubesequence.dimensions) + naxis = len(self.dimensions) plot_axis_indices, axes_coordinates, axes_units = _prep_axes_kwargs( naxis, plot_axis_indices, axes_coordinates, axes_units) if naxis == 1: # Make 1D line plot. - ax = self._plot_1D_sequence(cubesequence, axes_coordinates, + ax = self._plot_1D_sequence(self, axes_coordinates, axes_units, data_unit, **kwargs) else: if len(plot_axis_indices) == 1: @@ -99,13 +99,13 @@ def plot(self, cubesequence, axes=None, plot_axis_indices=None, unit_x_axis = axes_units[plot_axis_indices[0]] else: unit_x_axis = None - ax = LineAnimatorNDCubeSequence(cubesequence, plot_axis_indices[0], axes_coordinates, + ax = LineAnimatorNDCubeSequence(self, plot_axis_indices[0], axes_coordinates, unit_x_axis, data_unit, **kwargs) elif len(plot_axis_indices) == 2: if naxis == 2: # Since sequence has 2 dimensions and number of plot axes is 2, # produce a 2D image. - ax = self._plot_2D_sequence(cubesequence, plot_axis_indices, axes_coordinates, + ax = self._plot_2D_sequence(self, plot_axis_indices, axes_coordinates, axes_units, data_unit, **kwargs) else: # Since sequence has more than 2 dimensions and number of plot axes is 2, @@ -113,13 +113,13 @@ def plot(self, cubesequence, axes=None, plot_axis_indices=None, if axes_units is None: axes_units = [None] * naxis ax = ImageAnimatorNDCubeSequence( - cubesequence, image_axes=plot_axis_indices, + self, image_axes=plot_axis_indices, axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) return ax - def plot_as_cube(self, cubesequence, axes=None, plot_axis_indices=None, + def plot_as_cube(self, axes=None, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ Visualizes data in the NDCubeSequence with the sequence axis folded into the common axis. @@ -192,17 +192,17 @@ def plot_as_cube(self, cubesequence, axes=None, plot_axis_indices=None, """ # Verify common axis is set. - if cubesequence._common_axis is None: + if self._common_axis is None: raise TypeError("Common axis must be set.") # Check kwargs are in consistent formats and set default values if not done so by user. - naxis = len(cubesequence.cube_like_dimensions) + naxis = len(self.cube_like_dimensions) plot_axis_indices, axes_coordinates, axes_units = _prep_axes_kwargs( naxis, plot_axis_indices, axes_coordinates, axes_units) # Produce plot/image/animation based on cube-like dimensions of sequence. if naxis == 1: # Since sequence has 1 cube-like dimension, produce a 1D line plot. ax = self._plot_2D_sequence_as_1Dline( - cubesequence, x_axis_coordinates=x_axis_coordinates, + self, x_axis_coordinates=x_axis_coordinates, unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) else: if len(plot_axis_indices) == 1: @@ -212,7 +212,7 @@ def plot_as_cube(self, cubesequence, axes=None, plot_axis_indices=None, unit_x_axis = axes_units[plot_axis_indices[0]] else: unit_x_axis = None - ax = LineAnimatorCubeLikeNDCubeSequence(cubesequence, plot_axis_indices[0], + ax = LineAnimatorCubeLikeNDCubeSequence(self, plot_axis_indices[0], axes_coordinates, unit_x_axis, data_unit=data_unit, **kwargs) elif len(plot_axis_indices) == 2: @@ -225,14 +225,14 @@ def plot_as_cube(self, cubesequence, axes=None, plot_axis_indices=None, else: # Since sequence has more than 2 cube-like dimensions and # number of plot axes is 2, produce a 2D animation. - ax = ImageAnimatorCubeLikeNDCubeSequence(cubesequence, plot_axis_indices, + ax = ImageAnimatorCubeLikeNDCubeSequence(self, plot_axis_indices, axes_coordinates, axes_units[plot_axis_indices[0]], axes_units[plot_axis_indices[1]], **kwargs) return ax - def _plot_1D_sequence(self, cubesequence, axes_coordinates=None, + def _plot_1D_sequence(self, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of scalar NDCubes as a line plot. @@ -265,19 +265,19 @@ def _plot_1D_sequence(self, cubesequence, axes_coordinates=None, axes_units) # Check that the unit attribute is a set in all cubes and derive unit_y_axis if not set. unit_y_axis = data_unit - sequence_units, unit_y_axis = _determine_sequence_units(cubesequence.data, unit_y_axis) + sequence_units, unit_y_axis = _determine_sequence_units(self.data, unit_y_axis) # If all cubes have unit set, create a data quantity from cubes' data. if sequence_units is not None: ydata = u.Quantity([cube.data * sequence_units[i] - for i, cube in enumerate(cubesequence.data)], unit=unit_y_axis).value + for i, cube in enumerate(self.data)], unit=unit_y_axis).value yerror = u.Quantity([cube.uncertainty.array * sequence_units[i] - for i, cube in enumerate(cubesequence.data)], unit=unit_y_axis).value + for i, cube in enumerate(self.data)], unit=unit_y_axis).value # If not all cubes have their unit set, create a data array from cube's data. else: if unit_y_axis is not None: raise ValueError(NON_COMPATIBLE_UNIT_MESSAGE) - ydata = np.array([cube.data for cube in cubesequence.data]) - yerror = np.array([cube.uncertainty for cube in cubesequence.data]) + ydata = np.array([cube.data for cube in self.data]) + yerror = np.array([cube.uncertainty for cube in self.data]) if all(yerror == None): yerror = None # Define x-axis data. @@ -285,14 +285,14 @@ def _plot_1D_sequence(self, cubesequence, axes_coordinates=None, # Since scalar NDCubes have no array/pixel indices, WCS translations don't work. # Therefore x-axis values will be unitless sequence indices unless supplied by user # or an extra coordinate is designated. - xdata = np.arange(int(cubesequence.dimensions[0].value)) - xname = cubesequence.world_axis_physical_types[0] + xdata = np.arange(int(self.dimensions[0].value)) + xname = self.world_axis_physical_types[0] elif isinstance(x_axis_coordinates, str): - xdata = cubesequence.sequence_axis_extra_coords[x_axis_coordinates] + xdata = self.sequence_axis_extra_coords[x_axis_coordinates] xname = x_axis_coordinate else: xdata = x_axis_coordinates - xname = cubesequence.world_axis_physical_types[0] + xname = self.world_axis_physical_types[0] if isinstance(xdata, u.Quantity): if unit_x_axis is None: unit_x_axis = xdata.unit @@ -304,7 +304,7 @@ def _plot_1D_sequence(self, cubesequence, axes_coordinates=None, fig, ax = _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs) return ax - def _plot_2D_sequence_as_1Dline(self, cubesequence, x_axis_coordinates=None, + def _plot_2D_sequence_as_1Dline(self, x_axis_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of 1D NDCubes with a common axis as a line plot. @@ -321,38 +321,38 @@ def _plot_2D_sequence_as_1Dline(self, cubesequence, x_axis_coordinates=None, axes_units) # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. unit_y_axis = data_unit - sequence_units, unit_y_axis = _determine_sequence_units(cubesequence.data, unit_y_axis) + sequence_units, unit_y_axis = _determine_sequence_units(self.data, unit_y_axis) # If all cubes have unit set, create a y data quantity from cube's data. if sequence_units is not None: ydata = np.concatenate([(cube.data * sequence_units[i]).to(unit_y_axis).value - for i, cube in enumerate(cubesequence.data)]) + for i, cube in enumerate(self.data)]) yerror = np.concatenate( [(cube.uncertainty.array * sequence_units[i]).to(unit_y_axis).value - for i, cube in enumerate(cubesequence.data)]) + for i, cube in enumerate(self.data)]) else: if unit_y_axis is not None: raise ValueError(NON_COMPATIBLE_UNIT_MESSAGE) # If not all cubes have unit set, create a y data array from cube's data. - ydata = np.concatenate([cube.data for cube in cubesequence.data]) - yerror = np.array([cube.uncertainty for cube in cubesequence.data]) + ydata = np.concatenate([cube.data for cube in self.data]) + yerror = np.array([cube.uncertainty for cube in self.data]) if all(yerror == None): yerror = None else: if any(yerror == None): w = np.where(yerror == None)[0] for i in w: - yerror[i] = np.zeros(int(cubesequence[i].dimensions.value)) + yerror[i] = np.zeros(int(self[i].dimensions.value)) yerror = np.concatenate(yerror) # Define x-axis data. if x_axis_coordinates is None: if unit_x_axis is None: - unit_x_axis = np.asarray(cubesequence[0].wcs.wcs.cunit)[ - np.invert(cubesequence[0].missing_axis)][0] + unit_x_axis = np.asarray(self[0].wcs.wcs.cunit)[ + np.invert(self[0].missing_axis)][0] xdata = u.Quantity(np.concatenate([cube.axis_world_coords().to(unit_x_axis).value - for cube in cubesequence]), unit=unit_x_axis) - xname = cubesequence.cube_like_world_axis_physical_types[0] + for cube in self]), unit=unit_x_axis) + xname = self.cube_like_world_axis_physical_types[0] elif isinstance(x_axis_coordinates, str): - xdata = cubesequence.common_axis_extra_coords[x_axis_coordinates] + xdata = self.common_axis_extra_coords[x_axis_coordinates] xname = x_axis_coordinates else: xdata = x_axis_coordinates @@ -369,7 +369,7 @@ def _plot_2D_sequence_as_1Dline(self, cubesequence, x_axis_coordinates=None, return ax - def _plot_2D_sequence(self, cubesequence, plot_axis_indices=None, axes_coordinates=None, + def _plot_2D_sequence(self, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of 1D NDCubes as a 2D image. @@ -391,13 +391,13 @@ def _plot_2D_sequence(self, cubesequence, plot_axis_indices=None, axes_coordinat # Convert plot_axis_indices to array for function operations. plot_axis_indices = np.asarray(plot_axis_indices) # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. - sequence_units, data_unit = _determine_sequence_units(cubesequence.data, data_unit) + sequence_units, data_unit = _determine_sequence_units(self.data, data_unit) # If all cubes have unit set, create a data quantity from cube's data. if sequence_units is not None: data = np.stack([(cube.data * sequence_units[i]).to(data_unit).value - for i, cube in enumerate(cubesequence.data)]) + for i, cube in enumerate(self.data)]) else: - data = np.stack([cube.data for i, cube in enumerate(cubesequence.data)]) + data = np.stack([cube.data for i, cube in enumerate(self.data)]) # Transpose data if user-defined images_axes require it. if plot_axis_indices[0] < plot_axis_indices[1]: data = data.transpose() @@ -409,18 +409,18 @@ def _plot_2D_sequence(self, cubesequence, plot_axis_indices=None, axes_coordinat cube_axis_unit = axes_units[cube_axis_index] if axes_coordinates[cube_axis_index] is None: if cube_axis_unit is None: - cube_axis_unit = np.array(cubesequence[0].wcs.wcs.cunit)[ - np.invert(cubesequence[0].missing_axis)][0] - cube_axis_coords = cubesequence[0].axis_world_coords().to(cube_axis_unit).value - cube_axis_name = cubesequence.world_axis_physical_types[1] + cube_axis_unit = np.array(self[0].wcs.wcs.cunit)[ + np.invert(self[0].missing_axis)][0] + cube_axis_coords = self[0].axis_world_coords().to(cube_axis_unit).value + cube_axis_name = self.world_axis_physical_types[1] else: if isinstance(axes_coordinates[cube_axis_index], str): cube_axis_coords = \ - cubesequence[0].extra_coords[axes_coordinates[cube_axis_index]]["value"] + self[0].extra_coords[axes_coordinates[cube_axis_index]]["value"] cube_axis_name = axes_coordinates[cube_axis_index] else: cube_axis_coords = axes_coordinates[cube_axis_index] - cube_axis_name = cubesequence.world_axis_physical_types[1] + cube_axis_name = self.world_axis_physical_types[1] if isinstance(cube_axis_coords, u.Quantity): if cube_axis_unit is None: cube_axis_unit = cube_axis_coords.unit @@ -435,15 +435,15 @@ def _plot_2D_sequence(self, cubesequence, plot_axis_indices=None, axes_coordinat # Derive the coordinates, unit, and default label of the sequence axis. sequence_axis_unit = axes_units[sequence_axis_index] if axes_coordinates[sequence_axis_index] is None: - sequence_axis_coords = np.arange(len(cubesequence.data)) - sequence_axis_name = cubesequence.world_axis_physical_types[0] + sequence_axis_coords = np.arange(len(self.data)) + sequence_axis_name = self.world_axis_physical_types[0] elif isinstance(axes_coordinates[sequence_axis_index], str): sequence_axis_coords = \ - cubesequence.sequence_axis_extra_coords[axes_coordinates[sequence_axis_index]] + self.sequence_axis_extra_coords[axes_coordinates[sequence_axis_index]] sequence_axis_name = axes_coordinates[sequence_axis_index] else: sequence_axis_coords = axes_coordinates[sequence_axis_index] - sequence_axis_name = cubesequence.world_axis_physical_types[0] + sequence_axis_name = self.world_axis_physical_types[0] if isinstance(sequence_axis_coords, u.Quantity): if sequence_axis_unit is None: sequence_axis_unit = sequence_axis_coords.unit @@ -482,7 +482,7 @@ def _plot_2D_sequence(self, cubesequence, plot_axis_indices=None, axes_coordinat return ax - def _plot_3D_sequence_as_2Dimage(self, cubesequence, plot_axis_indices=None, + def _plot_3D_sequence_as_2Dimage(self, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ @@ -501,19 +501,19 @@ def _plot_3D_sequence_as_2Dimage(self, cubesequence, plot_axis_indices=None, # Convert plot_axis_indices to array for function operations. plot_axis_indices = np.asarray(plot_axis_indices) # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. - sequence_units, data_unit = _determine_sequence_units(cubesequence.data, data_unit) + sequence_units, data_unit = _determine_sequence_units(self.data, data_unit) # If all cubes have unit set, create a data quantity from cube's data. if sequence_units is not None: data = np.concatenate([(cube.data * sequence_units[i]).to(data_unit).value - for i, cube in enumerate(cubesequence.data)], - axis=cubesequence._common_axis) + for i, cube in enumerate(self.data)], + axis=self._common_axis) else: - data = np.concatenate([cube.data for cube in cubesequence.data], - axis=cubesequence._common_axis) + data = np.concatenate([cube.data for cube in self.data], + axis=self._common_axis) if plot_axis_indices[0] < plot_axis_indices[1]: data = data.transpose() # Determine index of common axis and other cube axis. - common_axis_index = cubesequence._common_axis + common_axis_index = self._common_axis cube_axis_index = [0, 1] cube_axis_index.pop(common_axis_index) cube_axis_index = cube_axis_index[0] @@ -521,19 +521,19 @@ def _plot_3D_sequence_as_2Dimage(self, cubesequence, plot_axis_indices=None, cube_axis_unit = axes_units[cube_axis_index] if axes_coordinates[cube_axis_index] is None: if cube_axis_unit is None: - cube_axis_unit = np.array(cubesequence[0].wcs.wcs.cunit)[ - np.invert(cubesequence[0].missing_axis)][0] + cube_axis_unit = np.array(self[0].wcs.wcs.cunit)[ + np.invert(self[0].missing_axis)][0] cube_axis_coords = \ - cubesequence[0].axis_world_coords()[cube_axis_index].to(cube_axis_unit).value - cube_axis_name = cubesequence.world_axis_physical_types[1] + self[0].axis_world_coords()[cube_axis_index].to(cube_axis_unit).value + cube_axis_name = self.world_axis_physical_types[1] else: if isinstance(axes_coordinates[cube_axis_index], str): cube_axis_coords = \ - cubesequence[0].extra_coords[axes_coordinates[cube_axis_index]]["value"] + self[0].extra_coords[axes_coordinates[cube_axis_index]]["value"] cube_axis_name = axes_coordinates[cube_axis_index] else: cube_axis_coords = axes_coordinates[cube_axis_index] - cube_axis_name = cubesequence.world_axis_physical_types[1] + cube_axis_name = self.world_axis_physical_types[1] if isinstance(cube_axis_coords, u.Quantity): if cube_axis_unit is None: cube_axis_unit = cube_axis_coords.unit @@ -551,19 +551,19 @@ def _plot_3D_sequence_as_2Dimage(self, cubesequence, plot_axis_indices=None, # Concatenate values along common axis for each cube. if common_axis_unit is None: wcs_common_axis_index = utils.cube.data_axis_to_wcs_axis( - common_axis_index, cubesequence[0].missing_axis) - common_axis_unit = np.array(cubesequence[0].wcs.wcs.cunit)[wcs_common_axis_index] + common_axis_index, self[0].missing_axis) + common_axis_unit = np.array(self[0].wcs.wcs.cunit)[wcs_common_axis_index] common_axis_coords = u.Quantity(np.concatenate( [cube.axis_world_coords()[common_axis_index].to(common_axis_unit).value - for cube in cubesequence.data]), unit=common_axis_unit) - common_axis_name = cubesequence.cube_like_world_axis_physical_types[common_axis_index] + for cube in self.data]), unit=common_axis_unit) + common_axis_name = self.cube_like_world_axis_physical_types[common_axis_index] elif isinstance(axes_coordinates[common_axis_index], str): common_axis_coords = \ - cubesequence.common_axis_extra_coords[axes_coordinates[common_axis_index]] + self.common_axis_extra_coords[axes_coordinates[common_axis_index]] sequence_axis_name = axes_coordinates[common_axis_index] else: common_axis_coords = axes_coordinates[common_axis_index] - common_axis_name = cubesequence.cube_like_world_axis_physical_types[common_axis_index] + common_axis_name = self.cube_like_world_axis_physical_types[common_axis_index] if isinstance(common_axis_coords, u.Quantity): if common_axis_unit is None: common_axis_unit = common_axis_coords.unit @@ -866,12 +866,12 @@ def __init__(self, seq, plot_axis_index=None, axis_ranges=None, unit_x_axis=None if plot_axis_index < 0: plot_axis_index = len(seq.dimensions) + plot_axis_index # Calculate the x-axis values if axis_ranges not supplied. - cube_plot_axis_index = plot_axis_index - 1 if axis_ranges is None: axis_ranges = [None] * len(seq.dimensions) if plot_axis_index == 0: axis_ranges[plot_axis_index] = np.arange(len(seq.data)) else: + cube_plot_axis_index = plot_axis_index - 1 # Define unit of x-axis if not supplied by user. if unit_x_axis is None: wcs_plot_axis_index = utils.cube.data_axis_to_wcs_axis( @@ -909,23 +909,23 @@ def __init__(self, seq, plot_axis_index=None, axis_ranges=None, unit_x_axis=None extra_coord_type[i] = type(cube_axis_extra_coord["value"]) extra_coord_axes[i] = cube_axis_extra_coord["axis"] x_axis_coords.append(cube_axis_extra_coord["value"]) - if not extra_coord_type.all() == extra_coord_type[0]: - raise TypeError("Extra coord {0} must be of same type for all NDCubes to " - "use it to define a plot axis.".format(axis_extra_coord)) - else: + if extra_coord_type.all() == extra_coord_type[0]: extra_coord_type = extra_coord_type[0] - if not extra_coord_axes.all() == extra_coord_axes[0]: - raise ValueError("Extra coord {0} must correspond to same axes in each " - "NDCube to use it to define a plot axis.".format( - axis_extra_coord)) else: + raise TypeError("Extra coord {0} must be of same type for all NDCubes to " + "use it to define a plot axis.".format(axis_extra_coord)) + if extra_coord_axes.all() == extra_coord_axes[0]: if isinstance(extra_coord_axes[0], (int, np.int64)): extra_coord_axes = [int(extra_coord_axes[0])] else: extra_coord_axes = list(extra_coord_axes[0]).sort() + else: + raise ValueError("Extra coord {0} must correspond to same axes in each " + "NDCube to use it to define a plot axis.".format( + axis_extra_coord)) # If the extra coord is a quantity, convert to the correct unit. if extra_coord_type is u.Quantity: - if unit_x_axis is None and extra_coord_type is u.Quantity: + if unit_x_axis is None: unit_x_axis = seq[0].extra_coords[axis_extra_coord]["value"].unit x_axis_coords = [x_axis_value.to(unit_x_axis).value for x_axis_value in x_axis_coords] @@ -935,21 +935,39 @@ def __init__(self, seq, plot_axis_index=None, axis_ranges=None, unit_x_axis=None (len(extra_coord_axes) == 1)): x_axis_coords = x_axis_coords[0] else: + # Else if all axes are not dependent, create an array of x-axis + # coords for each cube that are the same shape as the data in the + # respective cubes where the x coords are replicated in the extra + # dimensions. Then stack them together along the sequence axis so + # the final x-axis coord array is the same shape as the data array. + # This will be used in determining the correct x-axis coords for + # each frame of the animation. if len(extra_coord_axes) != data_concat.ndim: - independent_axes = list(range(seq[0].data.ndim)) - for i in list(extra_coord_axes)[::-1]: - independent_axes.pop(i) x_axis_coords_copy = copy.deepcopy(x_axis_coords) x_axis_coords = [] for i, x_axis_cube_coords in enumerate(x_axis_coords_copy): - tile_shape = tuple(list( - np.array(seq[i].data.shape)[independent_axes]) + \ - [1]*len(x_axis_cube_coords)) + # For each cube in the sequence, use np.tile to replicate + # the x-axis coords through the higher dimensions. + # But first give extra dummy (length 1) dimensions to the + # x-axis coords array so its number of dimensions is the + # same as the cube's data array. + # First, create shape of pre-np.tiled x-coord array for the cube. + coords_reshape = np.array([1] * seq[i].data.ndim) + coords_reshape[extra_coord_axes] = x_axis_cube_coords.shape + # Then reshape x-axis array to give it the dummy dimensions. + x_axis_cube_coords = x_axis_cube_coords.reshape( + tuple(coords_reshape)) + # Now the correct dummy dimensions are in place so the + # number of dimensions in the x-axis coord array equals + # the number of dimensions of the cube's data array, + # replicating the coords through the higher dimensions + # is simple using np.tile. x_axis_cube_coords = np.tile(x_axis_cube_coords, tile_shape) - # Since np.tile puts original array's dimensions as last, - # reshape x_axis_cube_coords to cube's shape. - x_axis_cube_coords = x_axis_cube_coords.reshape(seq[i].data.shape) + # Append new dimension-ed x-axis coords array for this cube + # sequence x-axis coords list. x_axis_coords.append(x_axis_cube_coords) + # Stack the x-axis coords along a new axis for the sequence axis so + # its the same shape as the data array. x_axis_coords = np.stack(x_axis_coords) # Set x-axis label. if xlabel is None: From 7ece2398699e34bf5d96dad8968a6e81feb9f19f Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Sun, 25 Mar 2018 15:47:50 -0400 Subject: [PATCH 16/30] Tidy up implementation of _prep_axes_kwargs() and remove eroneous self occurences. --- ndcube/mixins/sequence_plotting.py | 50 +++++++++++++++--------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index c09d2d4d1..a9367ab6c 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -89,7 +89,7 @@ def plot(self, axes=None, plot_axis_indices=None, naxis, plot_axis_indices, axes_coordinates, axes_units) if naxis == 1: # Make 1D line plot. - ax = self._plot_1D_sequence(self, axes_coordinates, + ax = self._plot_1D_sequence(axes_coordinates, axes_units, data_unit, **kwargs) else: if len(plot_axis_indices) == 1: @@ -105,7 +105,7 @@ def plot(self, axes=None, plot_axis_indices=None, if naxis == 2: # Since sequence has 2 dimensions and number of plot axes is 2, # produce a 2D image. - ax = self._plot_2D_sequence(self, plot_axis_indices, axes_coordinates, + ax = self._plot_2D_sequence(plot_axis_indices, axes_coordinates, axes_units, data_unit, **kwargs) else: # Since sequence has more than 2 dimensions and number of plot axes is 2, @@ -202,7 +202,7 @@ def plot_as_cube(self, axes=None, plot_axis_indices=None, if naxis == 1: # Since sequence has 1 cube-like dimension, produce a 1D line plot. ax = self._plot_2D_sequence_as_1Dline( - self, x_axis_coordinates=x_axis_coordinates, + x_axis_coordinates=x_axis_coordinates, unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) else: if len(plot_axis_indices) == 1: @@ -1243,28 +1243,23 @@ def _prep_axes_kwargs(naxis, plot_axis_indices, axes_coordinates, axes_units): # Set default value of plot_axis_indices if not set by user. if plot_axis_indices is None: plot_axis_indices = [-1, -2] - # If number of sequence dimensions is greater than 1, - # ensure length of plot_axis_indices is 1 or 2. - # No need to check case where number of sequence dimensions is 1 - # as plot_axis_indices is ignored in that case. - if naxis > 1: - if len(plot_axis_indices) not in [1, 2]: + else: + # If number of sequence dimensions is greater than 1, + # ensure length of plot_axis_indices is 1 or 2. + # No need to check case where number of sequence dimensions is 1 + # as plot_axis_indices is ignored in that case. + if naxis > 1 and len(plot_axis_indices) not in [1, 2]: raise ValueError("plot_axis_indices can have at most length 2.") - # If convention of axes_coordinates and axes_units being length of - # plot_axis_index is being used, convert to convention where their - # length equals sequence dimensions. Only do this if number of dimensions if - # greater than 1 as the conventions are equivalent if there is only one dimension. - if axes_coordinates is not None: + if axes_coordinates is not None: + if naxis > 1: + # If convention of axes_coordinates and axes_units being length of + # plot_axis_index is being used, convert to convention where their + # length equals sequence dimensions. Only do this if number of dimensions if + # greater than 1 as the conventions are equivalent if there is only one dimension. if len(axes_coordinates) == len(plot_axis_indices): none_axes_coordinates = np.array([None] * naxis) - none_axes_coordinates[plot_axis_indices] = plot_axis_indices + none_axes_coordinates[plot_axis_indices] = axes_coordinates axes_coordinates = list(none_axes_coordinates) - if axes_units is not None: - if len(axes_units) == len(plot_axis_indices): - none_axes_units = np.array([None] * naxis) - none_axes_units[plot_axis_indices] = plot_axis_indices - axes_units = list(none_axes_units) - if axes_coordinates is not None: # Now axes_coordinates have been converted to a consistent convention, # ensure their length equals the number of sequence dimensions. if len(axes_coordinates) != naxis: @@ -1272,18 +1267,23 @@ def _prep_axes_kwargs(naxis, plot_axis_indices, axes_coordinates, axes_units): # Ensure all elements in axes_coordinates are of correct types. ax_coord_types = (u.Quantity, np.ndarray, str) for axis_coordinate in axes_coordinates: - if axis_coordinate is not None and not instance(axis_coordinate, ax_coord_types): - raise TypeError("axes_coordinates must be one of {0} or list of {0}.".format( + if axis_coordinate is not None and not isinstance(axis_coordinate, ax_coord_types): + raise TypeError("axes_coordinates must be one of {0} or list of those.".format( [None] + list(ax_coord_types))) if axes_units is not None: + if naxis > 1: + if len(axes_units) == len(plot_axis_indices): + none_axes_units = np.array([None] * naxis) + none_axes_units[plot_axis_indices] = axes_units + axes_units = list(none_axes_units) # Now axes_units have been converted to a consistent convention, # ensure their length equals the number of sequence dimensions. if len(axes_units) != naxis: raise ValueError("length of axes_units must be {0}.".format(naxis)) # Ensure all elements in axes_units are of correct types. - ax_unit_types = (u.Unit, str) + ax_unit_types = (u.UnitBase, str) for axis_unit in axes_units: - if axis_unit is not None and not instance(axis_unit, ax_unit_types): + if axis_unit is not None and not isinstance(axis_unit, ax_unit_types): raise TypeError("axes_units must be one of {0} or list of {0}.".format( ax_unit_types)) From 623466e5b5151ec8b5c88c6a34f8c86dc0691095 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Sun, 25 Mar 2018 15:49:31 -0400 Subject: [PATCH 17/30] Create testing module for NDCubeSequencePlotMixin module and add tests for _prep_axes_kwargs() and _determine_sequence_units() and first cut at testing 1D NDCubeSequence plotting. --- ndcube/tests/test_sequence_plotting.py | 254 +++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 ndcube/tests/test_sequence_plotting.py diff --git a/ndcube/tests/test_sequence_plotting.py b/ndcube/tests/test_sequence_plotting.py new file mode 100644 index 000000000..79b9093a1 --- /dev/null +++ b/ndcube/tests/test_sequence_plotting.py @@ -0,0 +1,254 @@ +# -*- coding: utf-8 -*- +import pytest +import datetime + +import numpy as np +import astropy.units as u +import matplotlib + +from ndcube import NDCube, NDCubeSequence +from ndcube.utils.wcs import WCS +import ndcube.mixins.sequence_plotting + +# sample data for tests +# TODO: use a fixture reading from a test file. file TBD. +data = np.array([[[1, 2, 3, 4], [2, 4, 5, 3], [0, -1, 2, 3]], + [[2, 4, 5, 1], [10, 5, 2, 2], [10, 3, 3, 0]]]) + +data2 = np.array([[[11, 22, 33, 44], [22, 44, 55, 33], [0, -1, 22, 33]], + [[22, 44, 55, 11], [10, 55, 22, 22], [10, 33, 33, 0]]]) + +ht = {'CTYPE3': 'HPLT-TAN', 'CUNIT3': 'deg', 'CDELT3': 0.5, 'CRPIX3': 0, 'CRVAL3': 0, 'NAXIS3': 2, + 'CTYPE2': 'WAVE ', 'CUNIT2': 'Angstrom', 'CDELT2': 0.2, 'CRPIX2': 0, 'CRVAL2': 0, + 'NAXIS2': 3, + 'CTYPE1': 'TIME ', 'CUNIT1': 'min', 'CDELT1': 0.4, 'CRPIX1': 0, 'CRVAL1': 0, 'NAXIS1': 4} + +wt = WCS(header=ht, naxis=3) + +cube1 = NDCube( + data, wt, missing_axis=[False, False, False, True], + extra_coords=[ + ('pix', 0, u.Quantity(range(data.shape[0]), unit=u.pix)), + ('distance', None, u.Quantity(0, unit=u.cm)), + ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) + +cube1_with_unit = NDCube( + data, wt, missing_axis=[False, False, False, True], + unit=u.km, + extra_coords=[ + ('pix', 0, u.Quantity(range(data.shape[0]), unit=u.pix)), + ('distance', None, u.Quantity(0, unit=u.cm)), + ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) + +cube1_with_mask = NDCube( + data, wt, missing_axis=[False, False, False, True], + mask=np.zeros_like(data, dtype=bool), + extra_coords=[ + ('pix', 0, u.Quantity(range(data.shape[0]), unit=u.pix)), + ('distance', None, u.Quantity(0, unit=u.cm)), + ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) + +cube3 = NDCube( + data2, wt, missing_axis=[False, False, False, True], + extra_coords=[ + ('pix', 0, u.Quantity(np.arange(1, data2.shape[0]+1), unit=u.pix) + + cube1.extra_coords['pix']['value'][-1]), + ('distance', None, u.Quantity(2, unit=u.cm)), + ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) + +cube3_with_unit = NDCube( + data2, wt, missing_axis=[False, False, False, True], + unit=u.m, + extra_coords=[ + ('pix', 0, u.Quantity(np.arange(1, data2.shape[0]+1), unit=u.pix) + + cube1.extra_coords['pix']['value'][-1]), + ('distance', None, u.Quantity(2, unit=u.cm)), + ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) + +cube3_with_mask = NDCube( + data2, wt, missing_axis=[False, False, False, True], + mask=np.zeros_like(data2, dtype=bool), + extra_coords=[ + ('pix', 0, u.Quantity(np.arange(1, data2.shape[0]+1), unit=u.pix) + + cube1.extra_coords['pix']['value'][-1]), + ('distance', None, u.Quantity(2, unit=u.cm)), + ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) + +# Define some test NDCubeSequences. +common_axis = 0 +seq = NDCubeSequence(data_list=[cube1, cube3, cube1, cube3], common_axis=common_axis) + +seq_with_units = NDCubeSequence( + data_list=[cube1_with_unit, cube3_with_unit, cube1_with_unit, cube3_with_unit], + common_axis=common_axis) + +seq_with_masks = NDCubeSequence( + data_list=[cube1_with_mask, cube3_with_mask, cube1_with_mask, cube3_with_mask], + common_axis=common_axis) + +seq_with_unit0 = NDCubeSequence(data_list=[cube1_with_unit, cube3, + cube1_with_unit, cube3], common_axis=common_axis) + +seq_with_mask0 = NDCubeSequence(data_list=[cube1_with_mask, cube3, + cube1_with_mask, cube3], common_axis=common_axis) + +# Derive some expected data arrays in plot objects. +seq_data_stack = np.stack([cube.data for cube in seq_with_masks.data]) +seq_mask_stack = np.stack([cube.mask for cube in seq_with_masks.data]) + +seq_stack = np.ma.masked_array(seq_data_stack, seq_mask_stack) +seq_stack_km = np.ma.masked_array( + np.stack([(cube.data * cube.unit).to(u.km).value for cube in seq_with_units.data]), + seq_mask_stack) + +seq_data_concat = np.concatenate([cube.data for cube in seq_with_masks.data], axis=common_axis) +seq_mask_concat = np.concatenate([cube.mask for cube in seq_with_masks.data], axis=common_axis) +seq_concat = np.ma.masked_array(seq_data_concat, seq_mask_concat) + +# Derive expected axis_ranges +x_axis_coords = np.array([0.4, 0.8, 1.2, 1.6]).reshape((1, 1, 4)) +new_x_axis_coords_shape = u.Quantity(seq.dimensions, unit=u.pix).value.astype(int) +new_x_axis_coords_shape[-1] = 1 +none_axis_ranges_axis3 = [np.arange(len(seq.data)), np.array([0., 2.]), np.array([0., 1.5, 3.]), + np.tile(np.array(x_axis_coords), new_x_axis_coords_shape)] + + +@pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ + (seq[:, 0, 0, 0], {}, + (np.arange(len(seq.data)), np.array([ 1, 11, 1, 11]), + "meta.obs.sequence [None]", "Data [None]", + (0, len(seq[:, 0, 0, 0].data)-1), + (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), + max([cube.data.min() for cube in seq[:, 0, 0, 0].data])))), + (seq_with_units[:, 0, 0, 0], {}, + (np.arange(len(seq.data)), np.array([ 1, 11, 1, 11]), + "meta.obs.sequence [None]", "Data [None]", + (0, len(seq[:, 0, 0, 0].data)-1), + (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), + max([cube.data.min() for cube in seq[:, 0, 0, 0].data])))) + ]) +def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): + # Unpack expected values + expected_x_data, expected_y_data, expected_x_label, expected_y_label, \ + expected_xlim, expected_ylim = expected_values + # Run plot method + output = test_input.plot(**test_kwargs) + # Check values are correct + #assert isinstance(output, matplotlib.axes._subplots.AxesSubplot) + np.testing.assert_array_equal(output.lines[0].get_xdata(), expected_x_data) + np.testing.assert_array_equal(output.lines[0].get_ydata(), expected_y_data) + assert output.axes.get_xlabel() == expected_x_label + assert output.axes.get_ylabel() == expected_y_label + output_xlim = output.axes.get_xlim() + assert output_xlim[0] <= expected_xlim[0] + assert output_xlim[1] >= expected_xlim[1] + output_ylim = output.axes.get_ylim() + assert output_ylim[0] <= expected_ylim[0] + assert output_ylim[1] >= expected_ylim[1] + + +def test_sequence_plot_as_cube_1D_plot(): + pass + +""" +@pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ + (seq, {"plot_axis_indices": 3}, + (ndcube.mixins.sequence_plotting.LineAnimatorNDCubeSequence, seq_stack.data, + none_axis_ranges_axis3, "time [min]", "Data [None]", + (none_axis_ranges_axis3[-1].min(), none_axis_ranges_axis3[-1].max()), + (seq_stack.data.min(), seq_stack.data.max()))), + (seq, {"plot_axis_indices": -1, "data_unit": u.km}, + (ndcube.mixins.sequence_plotting.LineAnimatorNDCubeSequence, seq_stack_km.data, + none_axis_ranges_axis3, "time [min]", "Data [None]", + (none_axis_ranges_axis3[-1].min(), none_axis_ranges_axis3[-1].max()), + (seq_stack.data.min(), seq_stack.data.max()))), + (seq, {"plot_axis_indices": -1}, + (ndcube.mixins.sequence_plotting.LineAnimatorNDCubeSequence, seq_stack, + none_axis_ranges_axis3, "time [min]", "Data [None]", + (none_axis_ranges_axis3[-1].min(), none_axis_ranges_axis3[-1].max()), + (seq_stack.data.min(), seq_stack.data.max())))]) +def test_sequence_plot_LineAnimator(test_input, test_kwargs, expected_values): + # Unpack expected values + expected_type, expected_data, expected_axis_ranges, expected_xlabel, \ + expected_ylabel, expected_xlim, expected_ylim = expected_values + # Run plot method. + output = seq.plot(**test_kwargs) + # Check right type of plot object is produced. + assert type(output) is expected_type + # Check data being plotted is correct + np.testing.assert_array_equal(output.data, expected_data) + if type(expected_data) is np.ma.core.MaskedArray: + np.testing.assert_array_equal(output.data.mask, expected_data.mask) + # Check values of axes and sliders is correct. + for i in range(len(output.axis_ranges)): + print(i) + np.testing.assert_array_equal(output.axis_ranges[i], expected_axis_ranges[i]) + # Check plot axis labels and limits are correct + assert output.xlabel == expected_xlabel + assert output.ylabel == expected_ylabel + assert output.xlim == expected_xlim + assert output.ylim == expected_ylim +""" + +def test_sequence_plot_as_cube_LineAnimator(): + pass + + +def test_sequence_plot_2D_image(): + #p.images[0].get_extent() # xlim and ylim + #p.images[0].get_array() # data + #p.xaxis.get_label_text() + #p.yaxis.get_label_text() + pass + + +def test_sequence_as_cube_plot_2D_image(): + #p.images[0].get_extent() # xlim and ylim + #p.images[0].get_array() # data + #p.xaxis.get_label_text() + #p.yaxis.get_label_text() + pass + + +def test_sequence_plot_ImageAnimator(): + #p.data + #p.axis_ranges + #p.axes.get_xaxis().get_label_text() + #p.axes.get_yaxis().get_label_text() + pass + + +def test_sequence_plot_as_cube_ImageAnimator(): + pass + + +@pytest.mark.parametrize("test_input, expected", [ + ((seq_with_unit0.data, None), (None, None)), + ((seq_with_unit0.data, u.km), (None, None)), + ((seq_with_units.data, None), ([u.km, u.m, u.km, u.m], u.km)), + ((seq_with_units.data, u.cm), ([u.km, u.m, u.km, u.m], u.cm))]) +def test_determine_sequence_units(test_input, expected): + output_seq_unit, output_unit = ndcube.mixins.sequence_plotting._determine_sequence_units( + test_input[0], unit=test_input[1]) + assert output_seq_unit == expected[0] + assert output_unit == expected[1] + + +@pytest.mark.parametrize("test_input, expected", [ + ((3, 1, "time", u.s), ([1], [None, 'time', None], [None, u.s, None])), + ((3, None, None, None), ([-1, -2], None, None))]) +def test_prep_axes_kwargs(test_input, expected): + output = ndcube.mixins.sequence_plotting._prep_axes_kwargs(*test_input) + for i in range(3): + assert output[i] == expected[i] + + +@pytest.mark.parametrize("test_input, expected_error", [ + ((3, [0, 1, 2], ["time", "pix"], u.s), ValueError), + ((3, 0, ["time", "pix"], u.s), ValueError), + ((3, 0, "time", [u.s, u.pix]), ValueError), + ((3, 0, 0, u.s), TypeError), + ((3, 0, "time", 0), TypeError)]) +def test_prep_axes_kwargs_errors(test_input, expected_error): + with pytest.raises(expected_error): + output = ndcube.mixins.sequence_plotting._prep_axes_kwargs(*test_input) From f6e08f7c7dcb8b2e099aea7aaf2df62748cf8ce4 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Sun, 25 Mar 2018 22:09:47 -0400 Subject: [PATCH 18/30] Write tests for NDCubeSequencePlotMixin.plot and plot_as_cube 1D line plot options. Also implement mixin in ndcubesequence module creating a non-plotting Base class and an NDCubeSequence class using plot mixin. --- ndcube/mixins/sequence_plotting.py | 107 ++++++---- ndcube/ndcube_sequence.py | 15 +- ndcube/tests/test_sequence_plotting.py | 272 ++++++++++++++++++++++++- 3 files changed, 340 insertions(+), 54 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index a9367ab6c..6ec281495 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -201,9 +201,8 @@ def plot_as_cube(self, axes=None, plot_axis_indices=None, # Produce plot/image/animation based on cube-like dimensions of sequence. if naxis == 1: # Since sequence has 1 cube-like dimension, produce a 1D line plot. - ax = self._plot_2D_sequence_as_1Dline( - x_axis_coordinates=x_axis_coordinates, - unit_x_axis=unit_x_axis, data_unit=data_unit, **kwargs) + ax = self._plot_2D_sequence_as_1Dline(axes_coordinates, axes_units, data_unit, + **kwargs) else: if len(plot_axis_indices) == 1: # Since sequence has more than 1 cube-like dimension and @@ -266,20 +265,36 @@ def _plot_1D_sequence(self, axes_coordinates=None, # Check that the unit attribute is a set in all cubes and derive unit_y_axis if not set. unit_y_axis = data_unit sequence_units, unit_y_axis = _determine_sequence_units(self.data, unit_y_axis) - # If all cubes have unit set, create a data quantity from cubes' data. - if sequence_units is not None: - ydata = u.Quantity([cube.data * sequence_units[i] - for i, cube in enumerate(self.data)], unit=unit_y_axis).value - yerror = u.Quantity([cube.uncertainty.array * sequence_units[i] - for i, cube in enumerate(self.data)], unit=unit_y_axis).value # If not all cubes have their unit set, create a data array from cube's data. - else: - if unit_y_axis is not None: - raise ValueError(NON_COMPATIBLE_UNIT_MESSAGE) + if sequence_units is None: ydata = np.array([cube.data for cube in self.data]) - yerror = np.array([cube.uncertainty for cube in self.data]) - if all(yerror == None): + else: + # If all cubes have unit set, create a data quantity from cubes' data. + ydata = u.Quantity([cube.data * sequence_units[i] + for i, cube in enumerate(self.data)], unit=unit_y_axis).value + # Determine uncertainties. + sequence_uncertainty_nones = [] + for i, cube in enumerate(self.data): + if cube.uncertainty is None: + sequence_uncertainty_nones.append(i) + if sequence_uncertainty_nones == list(range(len(self.data))): + # If all cube uncertainties are None, make yerror also None. yerror = None + else: + # Else determine uncertainties, giving 0 uncertainty for + # cubes with uncertainty of None. + if sequence_units is None: + yerror = np.array([cube.uncertainty.array for cube in self.data]) + yerror[sequence_uncertainty_nones] = 0. + else: + # If all cubes have compatible units, ensure uncertainties are in the same unit. + yerror = [] + for i, cube in enumerate(self.data): + if i in sequence_uncertainty_nones: + yerror.append(0. * sequence_units[i]) + else: + yerror.append(cube.uncertainty.array * sequence_units[i]) + yerror = u.Quantity(yerror, unit=unit_y_axis).value # Define x-axis data. if x_axis_coordinates is None: # Since scalar NDCubes have no array/pixel indices, WCS translations don't work. @@ -289,7 +304,7 @@ def _plot_1D_sequence(self, axes_coordinates=None, xname = self.world_axis_physical_types[0] elif isinstance(x_axis_coordinates, str): xdata = self.sequence_axis_extra_coords[x_axis_coordinates] - xname = x_axis_coordinate + xname = x_axis_coordinates else: xdata = x_axis_coordinates xname = self.world_axis_physical_types[0] @@ -304,7 +319,7 @@ def _plot_1D_sequence(self, axes_coordinates=None, fig, ax = _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs) return ax - def _plot_2D_sequence_as_1Dline(self, x_axis_coordinates=None, + def _plot_2D_sequence_as_1Dline(self, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ Visualizes an NDCubeSequence of 1D NDCubes with a common axis as a line plot. @@ -323,39 +338,58 @@ def _plot_2D_sequence_as_1Dline(self, x_axis_coordinates=None, unit_y_axis = data_unit sequence_units, unit_y_axis = _determine_sequence_units(self.data, unit_y_axis) # If all cubes have unit set, create a y data quantity from cube's data. - if sequence_units is not None: + if sequence_units is None: + ydata = np.concatenate([cube.data for cube in self.data]) + else: + # If all cubes have unit set, create a data quantity from cubes' data. ydata = np.concatenate([(cube.data * sequence_units[i]).to(unit_y_axis).value for i, cube in enumerate(self.data)]) - yerror = np.concatenate( - [(cube.uncertainty.array * sequence_units[i]).to(unit_y_axis).value - for i, cube in enumerate(self.data)]) + # Determine uncertainties. + # Check which cubes don't have uncertainties. + sequence_uncertainty_nones = [] + for i, cube in enumerate(self.data): + if cube.uncertainty is None: + sequence_uncertainty_nones.append(i) + if sequence_uncertainty_nones == list(range(len(self.data))): + # If no sub-cubes have uncertainty, set overall yerror to None. + yerror = None else: - if unit_y_axis is not None: - raise ValueError(NON_COMPATIBLE_UNIT_MESSAGE) - # If not all cubes have unit set, create a y data array from cube's data. - ydata = np.concatenate([cube.data for cube in self.data]) - yerror = np.array([cube.uncertainty for cube in self.data]) - if all(yerror == None): - yerror = None + # Else determine uncertainties, giving 0 uncertainty for + # cubes with uncertainty of None. + yerror = [] + if sequence_units is None: + for i, cube in enumerate(self.data): + if i in sequence_uncertainty_nones: + yerror.append(np.zeros(cube.data.shape)) + else: + yerror.append(cube.uncertainty.array) else: - if any(yerror == None): - w = np.where(yerror == None)[0] - for i in w: - yerror[i] = np.zeros(int(self[i].dimensions.value)) - yerror = np.concatenate(yerror) + for i, cube in enumerate(self.data): + if i in sequence_uncertainty_nones: + yerror.append((np.zeros(cube.data.shape) * sequence_units[i]).to( + unit_y_axis).value) + else: + yerror.append((cube.uncertainty.array * sequence_units[i]).to( + unit_y_axis).value) + yerror = np.concatenate(yerror) # Define x-axis data. if x_axis_coordinates is None: + print('a', unit_x_axis) if unit_x_axis is None: + print('b', unit_x_axis) unit_x_axis = np.asarray(self[0].wcs.wcs.cunit)[ np.invert(self[0].missing_axis)][0] + print('c', unit_x_axis) xdata = u.Quantity(np.concatenate([cube.axis_world_coords().to(unit_x_axis).value - for cube in self]), unit=unit_x_axis) + for cube in self.data]), unit=unit_x_axis) xname = self.cube_like_world_axis_physical_types[0] + print('d', unit_x_axis) elif isinstance(x_axis_coordinates, str): xdata = self.common_axis_extra_coords[x_axis_coordinates] xname = x_axis_coordinates else: xdata = x_axis_coordinates + xname = "" if isinstance(xdata, u.Quantity): if unit_x_axis is None: unit_x_axis = xdata.unit @@ -364,6 +398,10 @@ def _plot_2D_sequence_as_1Dline(self, x_axis_coordinates=None, else: unit_x_axis = None default_xlabel = "{0} [{1}]".format(xname, unit_x_axis) + # For consistency, make xdata an array if a Quantity. Wait until now + # because if xdata is a Quantity, its unit is needed until now. + if isinstance(xdata, u.Quantity): + xdata = xdata.value # Plot data fig, ax = _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs) return ax @@ -1184,7 +1222,8 @@ def _determine_sequence_units(cubesequence_data, unit=None): sequence_units = None # If all cubes have unit set, create a data quantity from cube's data. if sequence_units is None: - unit = None + if unit is not None: + raise ValueError(NON_COMPATIBLE_UNIT_MESSAGE) else: if unit is None: unit = sequence_units[0] diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 305e33628..50deadb66 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -5,12 +5,12 @@ from sunpy.map import MapCube from ndcube import utils -from ndcube.visualization import animation as ani +from ndcube.mixins.sequence_plotting import NDCubeSequencePlotMixin __all__ = ['NDCubeSequence'] -class NDCubeSequence: +class NDCubeSequenceBase: """ Class representing list of cubes. @@ -171,13 +171,6 @@ def sequence_axis_extra_coords(self): sequence_extra_coords = None return sequence_extra_coords - def plot(self, *args, **kwargs): - if self._common_axis is None: - i = ani.ImageAnimatorNDCubeSequence(self, *args, **kwargs) - else: - i = ani.ImageAnimatorCommonAxisNDCubeSequence(self, *args, **kwargs) - return i - def explode_along_axis(self, axis): """ Separates slices of NDCubes in sequence along a given cube axis into (N-1)DCubes. @@ -228,6 +221,10 @@ def _new_instance(cls, data_list, meta=None, common_axis=None): return cls(data_list, meta=meta, common_axis=common_axis) +class NDCubeSequence(NDCubeSequenceBase, NDCubeSequencePlotMixin): + pass + + """ Cube Sequence Helpers """ diff --git a/ndcube/tests/test_sequence_plotting.py b/ndcube/tests/test_sequence_plotting.py index 79b9093a1..b09f42ace 100644 --- a/ndcube/tests/test_sequence_plotting.py +++ b/ndcube/tests/test_sequence_plotting.py @@ -48,6 +48,22 @@ ('distance', None, u.Quantity(0, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) +cube1_with_uncertainty = NDCube( + data, wt, missing_axis=[False, False, False, True], + uncertainty=np.sqrt(data), + extra_coords=[ + ('pix', 0, u.Quantity(range(data.shape[0]), unit=u.pix)), + ('distance', None, u.Quantity(0, unit=u.cm)), + ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) + +cube1_with_unit_and_uncertainty = NDCube( + data, wt, missing_axis=[False, False, False, True], + unit=u.km, uncertainty=np.sqrt(data), + extra_coords=[ + ('pix', 0, u.Quantity(range(data.shape[0]), unit=u.pix)), + ('distance', None, u.Quantity(0, unit=u.cm)), + ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) + cube3 = NDCube( data2, wt, missing_axis=[False, False, False, True], extra_coords=[ @@ -74,10 +90,30 @@ ('distance', None, u.Quantity(2, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) +cube3_with_uncertainty = NDCube( + data2, wt, missing_axis=[False, False, False, True], + uncertainty=np.sqrt(data2), + extra_coords=[ + ('pix', 0, u.Quantity(np.arange(1, data2.shape[0]+1), unit=u.pix) + + cube1.extra_coords['pix']['value'][-1]), + ('distance', None, u.Quantity(2, unit=u.cm)), + ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) + +cube3_with_unit_and_uncertainty = NDCube( + data2, wt, missing_axis=[False, False, False, True], + unit=u.m, uncertainty=np.sqrt(data2), + extra_coords=[ + ('pix', 0, u.Quantity(np.arange(1, data2.shape[0]+1), unit=u.pix) + + cube1.extra_coords['pix']['value'][-1]), + ('distance', None, u.Quantity(2, unit=u.cm)), + ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) + # Define some test NDCubeSequences. common_axis = 0 seq = NDCubeSequence(data_list=[cube1, cube3, cube1, cube3], common_axis=common_axis) +seq_no_common_axis = NDCubeSequence(data_list=[cube1, cube3, cube1, cube3]) + seq_with_units = NDCubeSequence( data_list=[cube1_with_unit, cube3_with_unit, cube1_with_unit, cube3_with_unit], common_axis=common_axis) @@ -92,6 +128,24 @@ seq_with_mask0 = NDCubeSequence(data_list=[cube1_with_mask, cube3, cube1_with_mask, cube3], common_axis=common_axis) +seq_with_uncertainty = NDCubeSequence(data_list=[cube1_with_uncertainty, cube3_with_uncertainty, + cube1_with_uncertainty, cube3_with_uncertainty], + common_axis=common_axis) + +seq_with_some_uncertainty = NDCubeSequence( + data_list=[cube1_with_uncertainty, cube3, cube1, cube3_with_uncertainty], + common_axis=common_axis) + +seq_with_units_and_uncertainty = NDCubeSequence( + data_list=[cube1_with_unit_and_uncertainty, cube3_with_unit_and_uncertainty, + cube1_with_unit_and_uncertainty, cube3_with_unit_and_uncertainty], + common_axis=common_axis) + +seq_with_units_and_some_uncertainty = NDCubeSequence( + data_list=[cube1_with_unit_and_uncertainty, cube3_with_unit, + cube1_with_unit, cube3_with_unit_and_uncertainty], + common_axis=common_axis) + # Derive some expected data arrays in plot objects. seq_data_stack = np.stack([cube.data for cube in seq_with_masks.data]) seq_mask_stack = np.stack([cube.mask for cube in seq_with_masks.data]) @@ -116,16 +170,58 @@ @pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ (seq[:, 0, 0, 0], {}, (np.arange(len(seq.data)), np.array([ 1, 11, 1, 11]), - "meta.obs.sequence [None]", "Data [None]", - (0, len(seq[:, 0, 0, 0].data)-1), + "meta.obs.sequence [None]", "Data [None]", (0, len(seq[:, 0, 0, 0].data)-1), (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), - max([cube.data.min() for cube in seq[:, 0, 0, 0].data])))), + max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))), + (seq_with_units[:, 0, 0, 0], {}, - (np.arange(len(seq.data)), np.array([ 1, 11, 1, 11]), - "meta.obs.sequence [None]", "Data [None]", - (0, len(seq[:, 0, 0, 0].data)-1), - (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), - max([cube.data.min() for cube in seq[:, 0, 0, 0].data])))) + (np.arange(len(seq_with_units.data)), np.array([ 1, 0.011, 1, 0.011]), + "meta.obs.sequence [None]", "Data [km]", (0, len(seq_with_units[:, 0, 0, 0].data)-1), + (min([(cube.data * cube.unit).to(seq_with_units[:, 0, 0, 0].data[0].unit).value + for cube in seq_with_units[:, 0, 0, 0].data]), + max([(cube.data * cube.unit).to(seq_with_units[:, 0, 0, 0].data[0].unit).value + for cube in seq_with_units[:, 0, 0, 0].data])))), + + (seq_with_uncertainty[:, 0, 0, 0], {}, + (np.arange(len(seq_with_uncertainty.data)), np.array([ 1, 11, 1, 11]), + "meta.obs.sequence [None]", "Data [None]", (0, len(seq_with_uncertainty[:, 0, 0, 0].data)-1), + (min([cube.data for cube in seq_with_uncertainty[:, 0, 0, 0].data]), + max([cube.data for cube in seq_with_uncertainty[:, 0, 0, 0].data])))), + + (seq_with_units_and_uncertainty[:, 0, 0, 0], {}, + (np.arange(len(seq_with_units_and_uncertainty.data)), np.array([ 1, 0.011, 1, 0.011]), + "meta.obs.sequence [None]", "Data [km]", + (0, len(seq_with_units_and_uncertainty[:, 0, 0, 0].data)-1), + (min([(cube.data*cube.unit).to(seq_with_units_and_uncertainty[:, 0, 0, 0].data[0].unit).value + for cube in seq_with_units_and_uncertainty[:, 0, 0, 0].data]), + max([(cube.data*cube.unit).to(seq_with_units_and_uncertainty[:, 0, 0, 0].data[0].unit).value + for cube in seq_with_units_and_uncertainty[:, 0, 0, 0].data])))), + + (seq_with_units_and_some_uncertainty[:, 0, 0, 0], {}, + (np.arange(len(seq_with_units_and_some_uncertainty.data)), np.array([ 1, 0.011, 1, 0.011]), + "meta.obs.sequence [None]", "Data [km]", + (0, len(seq_with_units_and_some_uncertainty[:, 0, 0, 0].data)-1), + (min([(cube.data*cube.unit).to(seq_with_units_and_some_uncertainty[:, 0, 0, 0].data[0].unit).value + for cube in seq_with_units_and_some_uncertainty[:, 0, 0, 0].data]), + max([(cube.data*cube.unit).to(seq_with_units_and_some_uncertainty[:, 0, 0, 0].data[0].unit).value + for cube in seq_with_units_and_some_uncertainty[:, 0, 0, 0].data])))), + + (seq[:, 0, 0, 0], {"axes_coordinates": "distance"}, + ((seq.sequence_axis_extra_coords["distance"]), np.array([ 1, 11, 1, 11]), + "distance [{0}]".format(seq.sequence_axis_extra_coords["distance"].unit), "Data [None]", + (min(seq.sequence_axis_extra_coords["distance"].value), + max(seq.sequence_axis_extra_coords["distance"].value)), + (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), + max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))), + + (seq[:, 0, 0, 0], {"axes_coordinates": u.Quantity(np.arange(len(seq.data)), unit=u.cm), + "axes_units": u.km}, + (u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km), np.array([ 1, 11, 1, 11]), + "meta.obs.sequence [km]", "Data [None]", + (min((u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km).value)), + max((u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km).value))), + (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), + max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))) ]) def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): # Unpack expected values @@ -145,10 +241,159 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): output_ylim = output.axes.get_ylim() assert output_ylim[0] <= expected_ylim[0] assert output_ylim[1] >= expected_ylim[1] - -def test_sequence_plot_as_cube_1D_plot(): - pass + +""" + (seq[:, 0, 0, 0], {"axes_coordinates": "distance"}, + ((seq.sequence_axis_extra_coords["distance"]), np.array([ 1, 11, 1, 11]), + "distance [{0}]".format(seq.sequence_axis_extra_coords["distance"].unit), "Data [None]", + (min(seq.sequence_axis_extra_coords["distance"].value), + max(seq.sequence_axis_extra_coords["distance"].value)), + (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), + max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))), + + (seq[:, 0, 0, 0], {"axes_coordinates": u.Quantity(np.arange(len(seq.data)), unit=u.cm), + "axes_units": u.km}, + (u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km), np.array([ 1, 11, 1, 11]), + "meta.obs.sequence [km]", "Data [None]", + (min((u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km).value)), + max((u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km).value))), + (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), + max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))) +""" +@pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ + (seq[:, :, 0, 0], {}, + (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848]), + np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), + "{0} [{1}]".format(seq[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), + "Data [None]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848]), + max([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + (min([cube.data.min() for cube in seq[:, :, 0, 0].data]), + max([cube.data.max() for cube in seq[:, :, 0, 0].data])))), + + (seq_with_units[:, :, 0, 0], {}, + (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848]), + np.array([ 1, 2, 0.011, 0.022, 1, 2, 0.011, 0.022]), + "{0} [{1}]".format(seq[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), + "Data [km]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.4998731, 0.99989848]), + max([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + (min([min((cube.data * cube.unit).to(u.km).value) + for cube in seq_with_units[:, :, 0, 0].data]), + max([max((cube.data * cube.unit).to(u.km).value) + for cube in seq_with_units[:, :, 0, 0].data])))), + + (seq_with_uncertainty[:, :, 0, 0], {}, + (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848]), + np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), + "{0} [{1}]".format( + seq_with_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], + "deg"), + "Data [None]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848]), + max([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + (min([cube.data.min() for cube in seq_with_uncertainty[:, :, 0, 0].data]), + max([cube.data.max() for cube in seq_with_uncertainty[:, :, 0, 0].data])))), + + (seq_with_some_uncertainty[:, :, 0, 0], {}, + (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848]), + np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), + "{0} [{1}]".format( + seq_with_some_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], + "deg"), + "Data [None]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848]), + max([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + (min([cube.data.min() for cube in seq_with_some_uncertainty[:, :, 0, 0].data]), + max([cube.data.max() for cube in seq_with_some_uncertainty[:, :, 0, 0].data])))), + + (seq_with_units_and_uncertainty[:, :, 0, 0], {}, + (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848]), + np.array([ 1, 2, 0.011, 0.022, 1, 2, 0.011, 0.022]), + "{0} [{1}]".format( + seq_with_units_and_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], + "deg"), + "Data [km]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.4998731, 0.99989848]), + max([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + (min([min((cube.data * cube.unit).to(u.km).value) + for cube in seq_with_units[:, :, 0, 0].data]), + max([max((cube.data * cube.unit).to(u.km).value) + for cube in seq_with_units[:, :, 0, 0].data])))), + + (seq_with_units_and_some_uncertainty[:, :, 0, 0], {}, + (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848]), + np.array([ 1, 2, 0.011, 0.022, 1, 2, 0.011, 0.022]), + "{0} [{1}]".format( + seq_with_units_and_some_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], + "deg"), + "Data [km]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.4998731, 0.99989848]), + max([0.49998731, 0.99989848, 0.49998731, 0.99989848, + 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + (min([min((cube.data * cube.unit).to(u.km).value) + for cube in seq_with_units[:, :, 0, 0].data]), + max([max((cube.data * cube.unit).to(u.km).value) + for cube in seq_with_units[:, :, 0, 0].data])))), + + (seq[:, :, 0, 0], {"axes_coordinates": "pix"}, + (seq[:, :, 0, 0].common_axis_extra_coords["pix"].value, + np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), + "pix [pix]", "Data [None]", + (min(seq[:, :, 0, 0].common_axis_extra_coords["pix"].value), + max(seq[:, :, 0, 0].common_axis_extra_coords["pix"].value)), + (min([cube.data.min() for cube in seq[:, :, 0, 0].data]), + max([cube.data.max() for cube in seq[:, :, 0, 0].data])))), + + (seq[:, :, 0, 0], + {"axes_coordinates": np.arange(10, 10+seq[:, :, 0, 0].cube_like_dimensions[0].value)}, + (np.arange(10, 10+seq[:, :, 0, 0].cube_like_dimensions[0].value), + np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), + "{0} [{1}]".format("", None), + "Data [None]", (10, 10 + seq[:, :, 0, 0].cube_like_dimensions[0].value - 1), + (min([cube.data.min() for cube in seq[:, :, 0, 0].data]), + max([cube.data.max() for cube in seq[:, :, 0, 0].data])))) + ]) +def test_sequence_plot_as_cube_1D_plot(test_input, test_kwargs, expected_values): + # Unpack expected values + expected_x_data, expected_y_data, expected_x_label, expected_y_label, \ + expected_xlim, expected_ylim = expected_values + # Run plot method + output = test_input.plot_as_cube(**test_kwargs) + # Check values are correct + # Check type of ouput plot object + #assert isinstance(output, matplotlib.axes._subplots.AxesSubplot) + # Check x and y data are correct. + assert np.allclose(output.lines[0].get_xdata(), expected_x_data) + assert np.allclose(output.lines[0].get_ydata(), expected_y_data) + # Check x and y axis labels are correct. + assert output.axes.get_xlabel() == expected_x_label + assert output.axes.get_ylabel() == expected_y_label + # Check all data is contained within x and y axes limits. + output_xlim = output.axes.get_xlim() + assert output_xlim[0] <= expected_xlim[0] + assert output_xlim[1] >= expected_xlim[1] + output_ylim = output.axes.get_ylim() + assert output_ylim[0] <= expected_ylim[0] + assert output_ylim[1] >= expected_ylim[1] + + +def test_sequence_plot_as_cube_error(): + with pytest.raises(TypeError): + seq_no_common_axis.plot_as_cube() + """ @pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ @@ -234,6 +479,11 @@ def test_determine_sequence_units(test_input, expected): assert output_unit == expected[1] +def test_determine_sequence_units(): + with pytest.raises(ValueError): + output_seq_unit, output_unit = ndcube.mixins.sequence_plotting._determine_sequence_units( + seq.data, u.m) + @pytest.mark.parametrize("test_input, expected", [ ((3, 1, "time", u.s), ([1], [None, 'time', None], [None, u.s, None])), ((3, None, None, None), ([-1, -2], None, None))]) From 8bf5036e53056c7163442e1e6c4ec75812491d20 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Mon, 26 Mar 2018 10:34:31 -0400 Subject: [PATCH 19/30] Fix bus in NDCubeSequencePlotMixin._plot_sequence_2D whereby axis values were being reassigned instead of the axis unit. Also added some error checks and removed a few lines of extraneous code. --- ndcube/mixins/sequence_plotting.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 6ec281495..09013a0f9 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -424,8 +424,6 @@ def _plot_2D_sequence(self, plot_axis_indices=None, axes_coordinates=None, axes_coordinates = [None, None] if axes_units is None: axes_units = [None, None] - if plot_axis_indices is None: - plot_axis_indices = [-1, -2] # Convert plot_axis_indices to array for function operations. plot_axis_indices = np.asarray(plot_axis_indices) # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. @@ -436,13 +434,15 @@ def _plot_2D_sequence(self, plot_axis_indices=None, axes_coordinates=None, for i, cube in enumerate(self.data)]) else: data = np.stack([cube.data for i, cube in enumerate(self.data)]) - # Transpose data if user-defined images_axes require it. if plot_axis_indices[0] < plot_axis_indices[1]: + # Transpose data if user-defined images_axes require it. data = data.transpose() - # Determine index of above axes variables corresponding to cube axis. - cube_axis_index = 1 - # Determine index of above variables corresponding to sequence axis. + # Determine index of above axes variables corresponding to sequence and cube axes. + # Since the axes variables have been re-oriented before this function was called + # so the 0th element corresponds to the sequence axis, and the 1st to the cube axis, + # determining this is trivial. sequence_axis_index = 0 + cube_axis_index = 1 # Derive the coordinates, unit, and default label of the cube axis. cube_axis_unit = axes_units[cube_axis_index] if axes_coordinates[cube_axis_index] is None: @@ -458,7 +458,7 @@ def _plot_2D_sequence(self, plot_axis_indices=None, axes_coordinates=None, cube_axis_name = axes_coordinates[cube_axis_index] else: cube_axis_coords = axes_coordinates[cube_axis_index] - cube_axis_name = self.world_axis_physical_types[1] + cube_axis_name = "" if isinstance(cube_axis_coords, u.Quantity): if cube_axis_unit is None: cube_axis_unit = cube_axis_coords.unit @@ -466,7 +466,9 @@ def _plot_2D_sequence(self, plot_axis_indices=None, axes_coordinates=None, else: cube_axis_coords = cube_axis_coords.to(cube_axis_unit).value else: - cube_axis_coords = None + if cube_axis_unit is not None: + raise ValueError("axes_unit element must be None unless corresponding " + "axes_coordinate is None or a Quantity.") default_cube_axis_label = "{0} [{1}]".format(cube_axis_name, cube_axis_unit) axes_coordinates[cube_axis_index] = cube_axis_coords axes_units[cube_axis_index] = cube_axis_unit @@ -489,7 +491,9 @@ def _plot_2D_sequence(self, plot_axis_indices=None, axes_coordinates=None, else: sequence_axis_coords = sequence_axis_coords.to(sequence_axis_unit).value else: - sequence_axis_unit = None + if sequence_axis_unit is not None: + raise ValueError("axes_unit element must be None unless corresponding " + "axes_coordinate is None or a Quantity.") default_sequence_axis_label = "{0} [{1}]".format(sequence_axis_name, sequence_axis_unit) axes_coordinates[sequence_axis_index] = sequence_axis_coords axes_units[sequence_axis_index] = sequence_axis_unit From d02118a21f4ad9cb71b89ce89eadd8abe2e90a9a Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Mon, 26 Mar 2018 11:20:05 -0400 Subject: [PATCH 20/30] Add tests for NDCubeSequencePlotMixin._plot_sequence_2D. --- ndcube/tests/test_sequence_plotting.py | 134 +++++++++++++++---------- 1 file changed, 80 insertions(+), 54 deletions(-) diff --git a/ndcube/tests/test_sequence_plotting.py b/ndcube/tests/test_sequence_plotting.py index b09f42ace..ec1291201 100644 --- a/ndcube/tests/test_sequence_plotting.py +++ b/ndcube/tests/test_sequence_plotting.py @@ -166,6 +166,10 @@ none_axis_ranges_axis3 = [np.arange(len(seq.data)), np.array([0., 2.]), np.array([0., 1.5, 3.]), np.tile(np.array(x_axis_coords), new_x_axis_coords_shape)] +# Derive expected extents +seq_axis1_lim_deg = [0.49998731, 0.99989848] +seq_axis1_lim_arcsec = [(axis1_xlim*u.deg).to(u.arcsec).value for axis1_xlim in seq_axis1_lim_deg] + @pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ (seq[:, 0, 0, 0], {}, @@ -225,7 +229,7 @@ ]) def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): # Unpack expected values - expected_x_data, expected_y_data, expected_x_label, expected_y_label, \ + expected_x_data, expected_y_data, expected_xlabel, expected_ylabel, \ expected_xlim, expected_ylim = expected_values # Run plot method output = test_input.plot(**test_kwargs) @@ -233,8 +237,8 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): #assert isinstance(output, matplotlib.axes._subplots.AxesSubplot) np.testing.assert_array_equal(output.lines[0].get_xdata(), expected_x_data) np.testing.assert_array_equal(output.lines[0].get_ydata(), expected_y_data) - assert output.axes.get_xlabel() == expected_x_label - assert output.axes.get_ylabel() == expected_y_label + assert output.axes.get_xlabel() == expected_xlabel + assert output.axes.get_ylabel() == expected_ylabel output_xlim = output.axes.get_xlim() assert output_xlim[0] <= expected_xlim[0] assert output_xlim[1] >= expected_xlim[1] @@ -243,34 +247,13 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): assert output_ylim[1] >= expected_ylim[1] -""" - (seq[:, 0, 0, 0], {"axes_coordinates": "distance"}, - ((seq.sequence_axis_extra_coords["distance"]), np.array([ 1, 11, 1, 11]), - "distance [{0}]".format(seq.sequence_axis_extra_coords["distance"].unit), "Data [None]", - (min(seq.sequence_axis_extra_coords["distance"].value), - max(seq.sequence_axis_extra_coords["distance"].value)), - (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), - max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))), - - (seq[:, 0, 0, 0], {"axes_coordinates": u.Quantity(np.arange(len(seq.data)), unit=u.cm), - "axes_units": u.km}, - (u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km), np.array([ 1, 11, 1, 11]), - "meta.obs.sequence [km]", "Data [None]", - (min((u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km).value)), - max((u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km).value))), - (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), - max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))) -""" @pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ (seq[:, :, 0, 0], {}, (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848]), np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), "{0} [{1}]".format(seq[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), - "Data [None]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.49998731, 0.99989848]), - max([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + "Data [None]", tuple(seq_axis1_lim_deg), (min([cube.data.min() for cube in seq[:, :, 0, 0].data]), max([cube.data.max() for cube in seq[:, :, 0, 0].data])))), @@ -279,10 +262,7 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): 0.49998731, 0.99989848, 0.49998731, 0.99989848]), np.array([ 1, 2, 0.011, 0.022, 1, 2, 0.011, 0.022]), "{0} [{1}]".format(seq[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), - "Data [km]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.4998731, 0.99989848]), - max([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + "Data [km]", tuple(seq_axis1_lim_deg), (min([min((cube.data * cube.unit).to(u.km).value) for cube in seq_with_units[:, :, 0, 0].data]), max([max((cube.data * cube.unit).to(u.km).value) @@ -295,10 +275,7 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): "{0} [{1}]".format( seq_with_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), - "Data [None]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.49998731, 0.99989848]), - max([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + "Data [None]", tuple(seq_axis1_lim_deg), (min([cube.data.min() for cube in seq_with_uncertainty[:, :, 0, 0].data]), max([cube.data.max() for cube in seq_with_uncertainty[:, :, 0, 0].data])))), @@ -309,10 +286,7 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): "{0} [{1}]".format( seq_with_some_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), - "Data [None]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.49998731, 0.99989848]), - max([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + "Data [None]", tuple(seq_axis1_lim_deg), (min([cube.data.min() for cube in seq_with_some_uncertainty[:, :, 0, 0].data]), max([cube.data.max() for cube in seq_with_some_uncertainty[:, :, 0, 0].data])))), @@ -323,10 +297,7 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): "{0} [{1}]".format( seq_with_units_and_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), - "Data [km]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.4998731, 0.99989848]), - max([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + "Data [km]", tuple(seq_axis1_lim_deg), (min([min((cube.data * cube.unit).to(u.km).value) for cube in seq_with_units[:, :, 0, 0].data]), max([max((cube.data * cube.unit).to(u.km).value) @@ -339,10 +310,7 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): "{0} [{1}]".format( seq_with_units_and_some_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), - "Data [km]", (min([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.4998731, 0.99989848]), - max([0.49998731, 0.99989848, 0.49998731, 0.99989848, - 0.49998731, 0.99989848, 0.49998731, 0.99989848])), + "Data [km]", tuple(seq_axis1_lim_deg), (min([min((cube.data * cube.unit).to(u.km).value) for cube in seq_with_units[:, :, 0, 0].data]), max([max((cube.data * cube.unit).to(u.km).value) @@ -368,7 +336,7 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): ]) def test_sequence_plot_as_cube_1D_plot(test_input, test_kwargs, expected_values): # Unpack expected values - expected_x_data, expected_y_data, expected_x_label, expected_y_label, \ + expected_x_data, expected_y_data, expected_xlabel, expected_ylabel, \ expected_xlim, expected_ylim = expected_values # Run plot method output = test_input.plot_as_cube(**test_kwargs) @@ -379,8 +347,8 @@ def test_sequence_plot_as_cube_1D_plot(test_input, test_kwargs, expected_values) assert np.allclose(output.lines[0].get_xdata(), expected_x_data) assert np.allclose(output.lines[0].get_ydata(), expected_y_data) # Check x and y axis labels are correct. - assert output.axes.get_xlabel() == expected_x_label - assert output.axes.get_ylabel() == expected_y_label + assert output.axes.get_xlabel() == expected_xlabel + assert output.axes.get_ylabel() == expected_ylabel # Check all data is contained within x and y axes limits. output_xlim = output.axes.get_xlim() assert output_xlim[0] <= expected_xlim[0] @@ -438,13 +406,71 @@ def test_sequence_plot_LineAnimator(test_input, test_kwargs, expected_values): def test_sequence_plot_as_cube_LineAnimator(): pass +@pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ + (seq[:, :, 0, 0], {}, + (seq_stack[:, :, 0, 0], + "custom:pos.helioprojective.lat [deg]", "meta.obs.sequence [None]", + tuple(seq_axis1_lim_deg + [0, len(seq.data)-1]))), -def test_sequence_plot_2D_image(): - #p.images[0].get_extent() # xlim and ylim - #p.images[0].get_array() # data - #p.xaxis.get_label_text() - #p.yaxis.get_label_text() - pass + (seq_with_units[:, :, 0, 0], {}, + (seq_stack_km[:, :, 0, 0], + "custom:pos.helioprojective.lat [deg]", "meta.obs.sequence [None]", + tuple(seq_axis1_lim_deg + [0, len(seq.data)-1]))), + + (seq[:, :, 0, 0], {"plot_axis_indices": [0, 1]}, + (seq_stack[:, :, 0, 0].transpose(), + "meta.obs.sequence [None]", "custom:pos.helioprojective.lat [deg]", + tuple([0, len(seq.data)-1] + seq_axis1_lim_deg))), + + (seq[:, :, 0, 0], {"axes_coordinates": ["pix", "distance"]}, + (seq_stack[:, :, 0, 0], + "pix [pix]", "distance [cm]", + (min(seq[0, :, 0, 0].extra_coords["pix"]["value"].value), + max(seq[0, :, 0, 0].extra_coords["pix"]["value"].value), + min(seq[:, :, 0, 0].sequence_axis_extra_coords["distance"].value), + max(seq[:, :, 0, 0].sequence_axis_extra_coords["distance"].value)))), ## This shows weakness of current extra coord axis values on 2D plotting! + + (seq[:, :, 0, 0], {"axes_coordinates": [np.arange( + 10, 10+seq[:, :, 0, 0].dimensions[-1].value), "distance"], "axes_units": [None, u.m]}, + (seq_stack[:, :, 0, 0], + " [None]", "distance [m]", + (10, 10+seq[:, :, 0, 0].dimensions[-1].value-1, + min(seq[:, :, 0, 0].sequence_axis_extra_coords["distance"].to(u.m).value), + max(seq[:, :, 0, 0].sequence_axis_extra_coords["distance"].to(u.m).value)))), + + (seq[:, :, 0, 0], {"axes_coordinates": [np.arange( + 10, 10+seq[:, :, 0, 0].dimensions[-1].value)*u.deg, None], "axes_units": [u.arcsec, None]}, + (seq_stack[:, :, 0, 0], + " [arcsec]", "meta.obs.sequence [None]", + tuple( + list((np.arange(10, 10+seq[:, :, 0, 0].dimensions[-1].value)*u.deg).to(u.arcsec).value) + \ + [0, len(seq.data)-1]))) + ]) +def test_sequence_plot_2D_image(test_input, test_kwargs, expected_values): + # Unpack expected values + expected_data, expected_xlabel, expected_ylabel, expected_extent = expected_values + # Run plot method + output = test_input.plot(**test_kwargs) + # Check values are correct + np.testing.assert_array_equal(output.images[0].get_array(), expected_data) + assert output.xaxis.get_label_text() == expected_xlabel + assert output.yaxis.get_label_text() == expected_ylabel + assert np.allclose(output.images[0].get_extent(), expected_extent, rtol=1e-3) + # Also check x and y values????? + + +@pytest.mark.parametrize("test_input, test_kwargs, expected_error", [ + (seq[:, :, 0, 0], {"axes_coordinates": [ + np.arange(10, 10+seq[:, :, 0, 0].dimensions[-1].value), None], + "axes_units": [u.m, None]}, ValueError), + + (seq[:, :, 0, 0], {"axes_coordinates": [ + None, np.arange(10, 10+seq[:, :, 0, 0].dimensions[0].value)], + "axes_units": [None, u.m]}, ValueError) + ]) +def test_sequence_plot_2D_image_errors(test_input, test_kwargs, expected_error): + with pytest.raises(expected_error): + output = test_input.plot(**test_kwargs) def test_sequence_as_cube_plot_2D_image(): From 1226447e7422f376408922e05a1dea7341dfdcc8 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Mon, 26 Mar 2018 12:32:18 -0400 Subject: [PATCH 21/30] Fix bug in NDCubeSequencePlotMixin._plot_3D_sequence_as_2D_image whereby axis values were being reassigned instead of the axis unit. Also added some error checks and removed a few lines of extraneous code. --- ndcube/mixins/sequence_plotting.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 09013a0f9..2ff789887 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -10,6 +10,8 @@ NON_COMPATIBLE_UNIT_MESSAGE = \ "All sequence sub-cubes' unit attribute are not compatible with data_unit set by user." +AXES_UNIT_ERRONESLY_SET_MESSAGE = \ + "axes_unit element must be None unless corresponding axes_coordinate is None or a Quantity." class NDCubeSequencePlotMixin: def plot(self, axes=None, plot_axis_indices=None, @@ -467,8 +469,7 @@ def _plot_2D_sequence(self, plot_axis_indices=None, axes_coordinates=None, cube_axis_coords = cube_axis_coords.to(cube_axis_unit).value else: if cube_axis_unit is not None: - raise ValueError("axes_unit element must be None unless corresponding " - "axes_coordinate is None or a Quantity.") + raise ValueError(AXES_UNIT_ERRONESLY_SET_MESSAGE) default_cube_axis_label = "{0} [{1}]".format(cube_axis_name, cube_axis_unit) axes_coordinates[cube_axis_index] = cube_axis_coords axes_units[cube_axis_index] = cube_axis_unit @@ -492,8 +493,7 @@ def _plot_2D_sequence(self, plot_axis_indices=None, axes_coordinates=None, sequence_axis_coords = sequence_axis_coords.to(sequence_axis_unit).value else: if sequence_axis_unit is not None: - raise ValueError("axes_unit element must be None unless corresponding " - "axes_coordinate is None or a Quantity.") + raise ValueError(AXES_UNIT_ERRONESLY_SET_MESSAGE) default_sequence_axis_label = "{0} [{1}]".format(sequence_axis_name, sequence_axis_unit) axes_coordinates[sequence_axis_index] = sequence_axis_coords axes_units[sequence_axis_index] = sequence_axis_unit @@ -524,7 +524,7 @@ def _plot_2D_sequence(self, plot_axis_indices=None, axes_coordinates=None, return ax - def _plot_3D_sequence_as_2Dimage(self, plot_axis_indices=None, + def _plot_3D_sequence_as_2Dimage(self, axes=None, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ @@ -538,8 +538,6 @@ def _plot_3D_sequence_as_2Dimage(self, plot_axis_indices=None, axes_coordinates = [None, None] if axes_units is None: axes_units = [None, None] - if plot_axis_indices is None: - plot_axis_indices = [-1, -2] # Convert plot_axis_indices to array for function operations. plot_axis_indices = np.asarray(plot_axis_indices) # Check that the unit attribute is set of all cubes and derive unit_y_axis if not set. @@ -567,7 +565,7 @@ def _plot_3D_sequence_as_2Dimage(self, plot_axis_indices=None, np.invert(self[0].missing_axis)][0] cube_axis_coords = \ self[0].axis_world_coords()[cube_axis_index].to(cube_axis_unit).value - cube_axis_name = self.world_axis_physical_types[1] + cube_axis_name = self.cube_like_world_axis_physical_types[1] else: if isinstance(axes_coordinates[cube_axis_index], str): cube_axis_coords = \ @@ -575,7 +573,7 @@ def _plot_3D_sequence_as_2Dimage(self, plot_axis_indices=None, cube_axis_name = axes_coordinates[cube_axis_index] else: cube_axis_coords = axes_coordinates[cube_axis_index] - cube_axis_name = self.world_axis_physical_types[1] + cube_axis_name = "" if isinstance(cube_axis_coords, u.Quantity): if cube_axis_unit is None: cube_axis_unit = cube_axis_coords.unit @@ -583,7 +581,8 @@ def _plot_3D_sequence_as_2Dimage(self, plot_axis_indices=None, else: cube_axis_coords = cube_axis_coords.to(cube_axis_unit).value else: - cube_axis_coords = None + if cube_axis_unit is not None: + raise ValueError(AXES_UNIT_ERRONESLY_SET_MESSAGE) default_cube_axis_label = "{0} [{1}]".format(cube_axis_name, cube_axis_unit) axes_coordinates[cube_axis_index] = cube_axis_coords axes_units[cube_axis_index] = cube_axis_unit @@ -602,10 +601,10 @@ def _plot_3D_sequence_as_2Dimage(self, plot_axis_indices=None, elif isinstance(axes_coordinates[common_axis_index], str): common_axis_coords = \ self.common_axis_extra_coords[axes_coordinates[common_axis_index]] - sequence_axis_name = axes_coordinates[common_axis_index] + common_axis_name = axes_coordinates[common_axis_index] else: common_axis_coords = axes_coordinates[common_axis_index] - common_axis_name = self.cube_like_world_axis_physical_types[common_axis_index] + common_axis_name = "" if isinstance(common_axis_coords, u.Quantity): if common_axis_unit is None: common_axis_unit = common_axis_coords.unit @@ -613,7 +612,8 @@ def _plot_3D_sequence_as_2Dimage(self, plot_axis_indices=None, else: common_axis_coords = common_axis_coords.to(common_axis_unit).value else: - common_axis_unit = None + if common_axis_unit is not None: + raise ValueError(AXES_UNIT_ERRONESLY_SET_MESSAGE) default_common_axis_label = "{0} [{1}]".format(common_axis_name, common_axis_unit) axes_coordinates[common_axis_index] = common_axis_coords axes_units[common_axis_index] = common_axis_unit From 643408c234cb5b2bd93ded736128c13aefa24f8f Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Mon, 26 Mar 2018 12:33:49 -0400 Subject: [PATCH 22/30] Add tests for NDCubeSequencePlotMixin._plot_3D_sequence_2D_image. --- ndcube/tests/test_sequence_plotting.py | 82 ++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/ndcube/tests/test_sequence_plotting.py b/ndcube/tests/test_sequence_plotting.py index ec1291201..9c17ef0ba 100644 --- a/ndcube/tests/test_sequence_plotting.py +++ b/ndcube/tests/test_sequence_plotting.py @@ -29,6 +29,7 @@ data, wt, missing_axis=[False, False, False, True], extra_coords=[ ('pix', 0, u.Quantity(range(data.shape[0]), unit=u.pix)), + ('hi', 1, u.Quantity(range(data.shape[1]), unit=u.s)), ('distance', None, u.Quantity(0, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) @@ -37,6 +38,7 @@ unit=u.km, extra_coords=[ ('pix', 0, u.Quantity(range(data.shape[0]), unit=u.pix)), + ('hi', 1, u.Quantity(range(data.shape[1]), unit=u.s)), ('distance', None, u.Quantity(0, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) @@ -45,6 +47,7 @@ mask=np.zeros_like(data, dtype=bool), extra_coords=[ ('pix', 0, u.Quantity(range(data.shape[0]), unit=u.pix)), + ('hi', 1, u.Quantity(range(data.shape[1]), unit=u.s)), ('distance', None, u.Quantity(0, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) @@ -53,6 +56,7 @@ uncertainty=np.sqrt(data), extra_coords=[ ('pix', 0, u.Quantity(range(data.shape[0]), unit=u.pix)), + ('hi', 1, u.Quantity(range(data.shape[1]), unit=u.s)), ('distance', None, u.Quantity(0, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) @@ -61,6 +65,7 @@ unit=u.km, uncertainty=np.sqrt(data), extra_coords=[ ('pix', 0, u.Quantity(range(data.shape[0]), unit=u.pix)), + ('hi', 1, u.Quantity(range(data.shape[1]), unit=u.s)), ('distance', None, u.Quantity(0, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 0))]) @@ -69,6 +74,7 @@ extra_coords=[ ('pix', 0, u.Quantity(np.arange(1, data2.shape[0]+1), unit=u.pix) + cube1.extra_coords['pix']['value'][-1]), + ('hi', 1, u.Quantity(range(data2.shape[1]), unit=u.s)), ('distance', None, u.Quantity(2, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) @@ -78,6 +84,7 @@ extra_coords=[ ('pix', 0, u.Quantity(np.arange(1, data2.shape[0]+1), unit=u.pix) + cube1.extra_coords['pix']['value'][-1]), + ('hi', 1, u.Quantity(range(data2.shape[1]), unit=u.s)), ('distance', None, u.Quantity(2, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) @@ -87,6 +94,7 @@ extra_coords=[ ('pix', 0, u.Quantity(np.arange(1, data2.shape[0]+1), unit=u.pix) + cube1.extra_coords['pix']['value'][-1]), + ('hi', 1, u.Quantity(range(data2.shape[1]), unit=u.s)), ('distance', None, u.Quantity(2, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) @@ -96,6 +104,7 @@ extra_coords=[ ('pix', 0, u.Quantity(np.arange(1, data2.shape[0]+1), unit=u.pix) + cube1.extra_coords['pix']['value'][-1]), + ('hi', 1, u.Quantity(range(data2.shape[1]), unit=u.s)), ('distance', None, u.Quantity(2, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) @@ -105,6 +114,7 @@ extra_coords=[ ('pix', 0, u.Quantity(np.arange(1, data2.shape[0]+1), unit=u.pix) + cube1.extra_coords['pix']['value'][-1]), + ('hi', 1, u.Quantity(range(data2.shape[1]), unit=u.s)), ('distance', None, u.Quantity(2, unit=u.cm)), ('time', None, datetime.datetime(2000, 1, 1, 0, 2))]) @@ -157,7 +167,12 @@ seq_data_concat = np.concatenate([cube.data for cube in seq_with_masks.data], axis=common_axis) seq_mask_concat = np.concatenate([cube.mask for cube in seq_with_masks.data], axis=common_axis) + seq_concat = np.ma.masked_array(seq_data_concat, seq_mask_concat) +seq_concat_km = np.ma.masked_array( + np.concatenate([(cube.data * cube.unit).to(u.km).value + for cube in seq_with_units.data], axis=common_axis), + seq_mask_concat) # Derive expected axis_ranges x_axis_coords = np.array([0.4, 0.8, 1.2, 1.6]).reshape((1, 1, 4)) @@ -169,6 +184,8 @@ # Derive expected extents seq_axis1_lim_deg = [0.49998731, 0.99989848] seq_axis1_lim_arcsec = [(axis1_xlim*u.deg).to(u.arcsec).value for axis1_xlim in seq_axis1_lim_deg] +seq_axis2_lim_m = [seq[:, :, :, 0].data[0].axis_world_coords()[-1][0].value, + seq[:, :, :, 0].data[0].axis_world_coords()[-1][-1].value] @pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ @@ -473,12 +490,65 @@ def test_sequence_plot_2D_image_errors(test_input, test_kwargs, expected_error): output = test_input.plot(**test_kwargs) -def test_sequence_as_cube_plot_2D_image(): - #p.images[0].get_extent() # xlim and ylim - #p.images[0].get_array() # data - #p.xaxis.get_label_text() - #p.yaxis.get_label_text() - pass +@pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ + (seq[:, :, :, 0], {}, + (seq_concat[:, :, 0], + "em.wl [m]", "custom:pos.helioprojective.lat [deg]", + tuple(seq_axis2_lim_m + seq_axis1_lim_deg))), + + (seq_with_units[:, :, :, 0], {}, + (seq_concat_km[:, :, 0], + "em.wl [m]", "custom:pos.helioprojective.lat [deg]", + tuple(seq_axis2_lim_m + seq_axis1_lim_deg))), + + (seq[:, :, :, 0], {"plot_axis_indices": [0, 1], + "axes_coordinates": ["pix", "hi"]}, + (seq_concat[:, :, 0].transpose(), "pix [pix]", "hi [s]", + ((seq[:, :, :, 0].common_axis_extra_coords["pix"][0].value, + seq[:, :, :, 0].common_axis_extra_coords["pix"][-1].value, + seq[:, :, :, 0].data[0].extra_coords["hi"]["value"][0].value, + seq[:, :, :, 0].data[0].extra_coords["hi"]["value"][-1].value)))), + + (seq[:, :, :, 0], {"axes_coordinates": [ + np.arange(10, 10+seq[:, :, :, 0].cube_like_dimensions[-1].value) * u.m, + np.arange(10, 10+seq[:, :, :, 0].cube_like_dimensions[0].value) * u.m]}, + (seq_concat[:, :, 0], " [m]", " [m]", + (10, 10+seq[:, :, :, 0].cube_like_dimensions[-1].value-1, + 10, 10+seq[:, :, :, 0].cube_like_dimensions[0].value-1))), + + (seq[:, :, :, 0], {"axes_coordinates": [ + np.arange(10, 10+seq[:, :, :, 0].cube_like_dimensions[-1].value) * u.m, + np.arange(10, 10+seq[:, :, :, 0].cube_like_dimensions[0].value) * u.m], + "axes_units": ["cm", u.cm]}, + (seq_concat[:, :, 0], " [cm]", " [cm]", + (10*100, (10+seq[:, :, :, 0].cube_like_dimensions[-1].value-1)*100, + 10*100, (10+seq[:, :, :, 0].cube_like_dimensions[0].value-1)*100))) + ]) +def test_sequence_plot_as_cube_2D_image(test_input, test_kwargs, expected_values): + # Unpack expected values + expected_data, expected_xlabel, expected_ylabel, expected_extent = expected_values + # Run plot method + output = test_input.plot_as_cube(**test_kwargs) + # Check values are correct + np.testing.assert_array_equal(output.images[0].get_array(), expected_data) + assert output.xaxis.get_label_text() == expected_xlabel + assert output.yaxis.get_label_text() == expected_ylabel + assert np.allclose(output.images[0].get_extent(), expected_extent, rtol=1e-3) + # Also check x and y values????? + + +@pytest.mark.parametrize("test_input, test_kwargs, expected_error", [ + (seq[:, :, :, 0], {"axes_coordinates": [ + np.arange(10, 10+seq[:, :, :, 0].cube_like_dimensions[-1].value), None], + "axes_units": [u.m, None]}, ValueError), + + (seq[:, :, :, 0], {"axes_coordinates": [ + None, np.arange(10, 10+seq[:, :, :, 0].cube_like_dimensions[0].value)], + "axes_units": [None, u.m]}, ValueError) + ]) +def test_sequence_plot_as_cube_2D_image_errors(test_input, test_kwargs, expected_error): + with pytest.raises(expected_error): + output = test_input.plot_as_cube(**test_kwargs) def test_sequence_plot_ImageAnimator(): From 3b7b86aea6359510ee6806eb9e1307a4fe99f08f Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Mon, 26 Mar 2018 13:13:21 -0400 Subject: [PATCH 23/30] Fix calling of animation in NDCubeSequencePlotMixin.plot_as_cube and write test for it. --- ndcube/mixins/sequence_plotting.py | 13 +++---- ndcube/tests/test_sequence_plotting.py | 50 ++++++++++++++++++++------ 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 2ff789887..81f74307e 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -226,11 +226,12 @@ def plot_as_cube(self, axes=None, plot_axis_indices=None, else: # Since sequence has more than 2 cube-like dimensions and # number of plot axes is 2, produce a 2D animation. - ax = ImageAnimatorCubeLikeNDCubeSequence(self, plot_axis_indices, - axes_coordinates, - axes_units[plot_axis_indices[0]], - axes_units[plot_axis_indices[1]], - **kwargs) + if axes_units is None: + axes_units = [None] * naxis + ax = ImageAnimatorCubeLikeNDCubeSequence( + self, image_axes=plot_axis_indices, axis_ranges=axes_coordinates, + unit_x_axis=axes_units[plot_axis_indices[0]], + unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) return ax def _plot_1D_sequence(self, axes_coordinates=None, @@ -791,7 +792,7 @@ def __init__(self, seq, wcs=None, **kwargs): new_shape.insert(i, 1) data_concat = data_concat.reshape(new_shape) - super(ImageAnimatorCommonAxisNDCubeSequence, self).__init__( + super(ImageAnimatorCubeLikeNDCubeSequence, self).__init__( data_concat, wcs=wcs, **kwargs) def update_plot(self, val, im, slider): diff --git a/ndcube/tests/test_sequence_plotting.py b/ndcube/tests/test_sequence_plotting.py index 9c17ef0ba..34c5ea232 100644 --- a/ndcube/tests/test_sequence_plotting.py +++ b/ndcube/tests/test_sequence_plotting.py @@ -550,17 +550,47 @@ def test_sequence_plot_as_cube_2D_image_errors(test_input, test_kwargs, expected with pytest.raises(expected_error): output = test_input.plot_as_cube(**test_kwargs) +@pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ + (seq, {}, + (seq_stack.reshape(4, 1, 2, 3, 4), + [np.linspace(0, len(seq.data), len(seq.data)), np.array([0]), np.array([0, 2]), + [0, 3], [0, 4]], "", "", (-0.5, 3.5, -0.5, 2.5))) + ]) +def test_sequence_plot_ImageAnimator(test_input, test_kwargs, expected_values): + # Unpack expected values + expected_data, expected_axis_ranges, expected_xlabel, expected_ylabel, \ + expected_extent = expected_values + # Run plot method + output = test_input.plot(**test_kwargs) + # Check plot object properties are correct. + assert type(output) == ndcube.mixins.sequence_plotting.ImageAnimatorNDCubeSequence + #np.testing.assert_array_equal(output.data, expected_data) + #for i in range(len(expected_axis_ranges)): + # np.testing.assert_array_equal(output.axis_ranges[i], expected_axis_ranges[i]) + #assert output.axes.get_xaxis().get_label_text() == expected_xlabel + #assert output.axes.get_yaxis().get_label_text() == expected_ylabel + #assert np.allclose(output.im.get_extent(), expected_extent) -def test_sequence_plot_ImageAnimator(): - #p.data - #p.axis_ranges - #p.axes.get_xaxis().get_label_text() - #p.axes.get_yaxis().get_label_text() - pass - - -def test_sequence_plot_as_cube_ImageAnimator(): - pass +@pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ + (seq, {}, + (seq_concat.reshape(1, 8, 3, 4), + [np.array([0]), np.linspace(0, 8, 8), [0, 3], [0, 4]], + "", "", (-0.5, 3.5, -0.5, 2.5))) + ]) +def test_sequence_plot_as_cube_ImageAnimator(test_input, test_kwargs, expected_values): + # Unpack expected values + expected_data, expected_axis_ranges, expected_xlabel, expected_ylabel, \ + expected_extent = expected_values + # Run plot method + output = test_input.plot_as_cube(**test_kwargs) + # Check plot object properties are correct. + assert type(output) == ndcube.mixins.sequence_plotting.ImageAnimatorCubeLikeNDCubeSequence + #np.testing.assert_array_equal(output.data, expected_data) + #for i in range(len(expected_axis_ranges)): + # np.testing.assert_array_equal(output.axis_ranges[i], expected_axis_ranges[i]) + #assert output.axes.get_xaxis().get_label_text() == expected_xlabel + #assert output.axes.get_yaxis().get_label_text() == expected_ylabel + #assert np.allclose(output.im.get_extent(), expected_extent) @pytest.mark.parametrize("test_input, expected", [ From 3e4997d6b1e8972356ac88ecfd4bf266502e8cdc Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Mon, 26 Mar 2018 14:12:46 -0400 Subject: [PATCH 24/30] Rework NDCubeSeqeunce ImageAnimator classes API to be more like the current plot API. Also make those ImageAnimator classes aware of the sub-cubes data unit. --- ndcube/mixins/sequence_plotting.py | 82 ++++++++++++++++++-------- ndcube/tests/test_sequence_plotting.py | 41 ++++--------- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 81f74307e..298308c56 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -112,12 +112,9 @@ def plot(self, axes=None, plot_axis_indices=None, else: # Since sequence has more than 2 dimensions and number of plot axes is 2, # produce a 2D animation. - if axes_units is None: - axes_units = [None] * naxis ax = ImageAnimatorNDCubeSequence( - self, image_axes=plot_axis_indices, - axis_ranges=axes_coordinates, unit_x_axis=axes_units[plot_axis_indices[0]], - unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) + self, plot_axis_indices=plot_axis_indices, + axes_coordinates=axes_coordinates, axes_units=axes_units, **kwargs) return ax @@ -226,12 +223,9 @@ def plot_as_cube(self, axes=None, plot_axis_indices=None, else: # Since sequence has more than 2 cube-like dimensions and # number of plot axes is 2, produce a 2D animation. - if axes_units is None: - axes_units = [None] * naxis ax = ImageAnimatorCubeLikeNDCubeSequence( - self, image_axes=plot_axis_indices, axis_ranges=axes_coordinates, - unit_x_axis=axes_units[plot_axis_indices[0]], - unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) + self, plot_axis_indices=plot_axis_indices, + axes_coordinates=axes_coordinates, axes_units=axes_units, **kwargs) return ax def _plot_1D_sequence(self, axes_coordinates=None, @@ -658,7 +652,7 @@ class ImageAnimatorNDCubeSequence(ImageAnimatorWCS): Parameters ---------- - seq: `ndcube.datacube.CubeSequence` + seq: `ndcube.NDCubeSequence` The list of cubes. image_axes: `list` @@ -698,23 +692,42 @@ class ImageAnimatorNDCubeSequence(ImageAnimatorWCS): Extra keywords are passed to imshow. """ - def __init__(self, seq, wcs=None, **kwargs): + def __init__(self, seq, wcs=None, axes=None, plot_axis_indices=None, + axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): + self.sequence = seq.data # Required by parent class. + # Set default values of kwargs if not set. if wcs is None: wcs = seq[0].wcs - self.sequence = seq.data - self.cumul_cube_lengths = np.cumsum(np.ones(len(self.sequence))) - data_concat = np.stack([cube.data for cube in seq.data]) + if axes_coordinates is None: + axes_coordinates = [None] * len(seq.dimensions) + if axes_units is None: + axes_units = [None] * len(seq.dimensions) + # Determine units of each cube in sequence. + sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) + # If all cubes have unit set, create a data quantity from cube's data. + if sequence_units is None: + data_stack = np.stack([cube.data for i, cube in enumerate(seq.data)]) + else: + data_stack = np.stack([(cube.data * sequence_units[i]).to(data_unit).value + for i, cube in enumerate(seq.data)]) + self.cumul_cube_lengths = np.cumsum(np.ones(len(seq.data))) # Add dimensions of length 1 of concatenated data array # shape for an missing axes. if seq[0].wcs.naxis != len(seq.dimensions) - 1: - new_shape = list(data_concat.shape) + new_shape = list(data_stack.shape) for i in np.arange(seq[0].wcs.naxis)[seq[0].missing_axis[::-1]]: new_shape.insert(i+1, 1) - data_concat = data_concat.reshape(new_shape) + # Also insert dummy coordinates and units. + axes_coordinates.insert(i+1, None) + axes_units.insert(i+1, None) + data_stack = data_stack.reshape(new_shape) # Add dummy axis to WCS object to represent sequence axis. new_wcs = utils.wcs.append_sequence_axis_to_wcs(wcs) - super(ImageAnimatorNDCubeSequence, self).__init__(data_concat, wcs=new_wcs, **kwargs) + super(ImageAnimatorNDCubeSequence, self).__init__( + data_stack, wcs=new_wcs, image_axes=plot_axis_indices, axis_ranges=axes_coordinates, + unit_x_axis=axes_units[plot_axis_indices[0]], + unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) class ImageAnimatorCubeLikeNDCubeSequence(ImageAnimatorWCS): @@ -774,26 +787,45 @@ class ImageAnimatorCubeLikeNDCubeSequence(ImageAnimatorWCS): Extra keywords are passed to imshow. """ - def __init__(self, seq, wcs=None, **kwargs): + def __init__(self, seq, wcs=None, axes=None, plot_axis_indices=None, + axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): if seq._common_axis is None: - raise ValueError("Common axis must be set to use this class. " - "Use ImageAnimatorNDCubeSequence.") + raise TypeError("Common axis must be set to use this class. " + "Use ImageAnimatorNDCubeSequence.") + self.sequence = seq.data # Required by parent class. + # Set default values of kwargs if not set. if wcs is None: wcs = seq[0].wcs - self.sequence = seq.data + if axes_coordinates is None: + axes_coordinates = [None] * len(seq.cube_like_dimensions) + if axes_units is None: + axes_units = [None] * len(seq.cube_like_dimensions) + # Determine units of each cube in sequence. + sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) + # If all cubes have unit set, create a data quantity from cube's data. + if sequence_units is None: + data_concat = np.concatenate([cube.data for cube in seq.data], axis=seq._common_axis) + else: + data_concat = np.concatenate( + [(cube.data * sequence_units[i]).to(data_unit).value + for i, cube in enumerate(seq.data)], axis=seq._common_axis) self.cumul_cube_lengths = np.cumsum(np.array( - [c.dimensions[0].value for c in self.sequence], dtype=int)) - data_concat = np.concatenate([cube.data for cube in seq.data], axis=seq._common_axis) + [c.dimensions[0].value for c in seq.data], dtype=int)) # Add dimensions of length 1 of concatenated data array # shape for an missing axes. if seq[0].wcs.naxis != len(seq.dimensions) - 1: new_shape = list(data_concat.shape) for i in np.arange(seq[0].wcs.naxis)[seq[0].missing_axis[::-1]]: new_shape.insert(i, 1) + # Also insert dummy coordinates and units. + axes_coordinates.insert(i, None) + axes_units.insert(i, None) data_concat = data_concat.reshape(new_shape) super(ImageAnimatorCubeLikeNDCubeSequence, self).__init__( - data_concat, wcs=wcs, **kwargs) + data_concat, wcs=wcs, image_axes=plot_axis_indices, axis_ranges=axes_coordinates, + unit_x_axis=axes_units[plot_axis_indices[0]], + unit_y_axis=axes_units[plot_axis_indices[1]], **kwargs) def update_plot(self, val, im, slider): val = int(val) diff --git a/ndcube/tests/test_sequence_plotting.py b/ndcube/tests/test_sequence_plotting.py index 34c5ea232..eaf240b35 100644 --- a/ndcube/tests/test_sequence_plotting.py +++ b/ndcube/tests/test_sequence_plotting.py @@ -550,47 +550,28 @@ def test_sequence_plot_as_cube_2D_image_errors(test_input, test_kwargs, expected with pytest.raises(expected_error): output = test_input.plot_as_cube(**test_kwargs) -@pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ - (seq, {}, - (seq_stack.reshape(4, 1, 2, 3, 4), - [np.linspace(0, len(seq.data), len(seq.data)), np.array([0]), np.array([0, 2]), - [0, 3], [0, 4]], "", "", (-0.5, 3.5, -0.5, 2.5))) +@pytest.mark.parametrize("test_input, test_kwargs, expected_data", [ + (seq, {}, seq_stack.reshape(4, 1, 2, 3, 4)), + (seq_with_units, {}, seq_stack_km.reshape(4, 1, 2, 3, 4)) ]) -def test_sequence_plot_ImageAnimator(test_input, test_kwargs, expected_values): - # Unpack expected values - expected_data, expected_axis_ranges, expected_xlabel, expected_ylabel, \ - expected_extent = expected_values +def test_sequence_plot_ImageAnimator(test_input, test_kwargs, expected_data): # Run plot method output = test_input.plot(**test_kwargs) # Check plot object properties are correct. assert type(output) == ndcube.mixins.sequence_plotting.ImageAnimatorNDCubeSequence - #np.testing.assert_array_equal(output.data, expected_data) - #for i in range(len(expected_axis_ranges)): - # np.testing.assert_array_equal(output.axis_ranges[i], expected_axis_ranges[i]) - #assert output.axes.get_xaxis().get_label_text() == expected_xlabel - #assert output.axes.get_yaxis().get_label_text() == expected_ylabel - #assert np.allclose(output.im.get_extent(), expected_extent) + np.testing.assert_array_equal(output.data, expected_data) -@pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ - (seq, {}, - (seq_concat.reshape(1, 8, 3, 4), - [np.array([0]), np.linspace(0, 8, 8), [0, 3], [0, 4]], - "", "", (-0.5, 3.5, -0.5, 2.5))) + +@pytest.mark.parametrize("test_input, test_kwargs, expected_data", [ + (seq, {}, seq_concat.reshape(1, 8, 3, 4)), + (seq_with_units, {}, seq_concat_km.reshape(1, 8, 3, 4)) ]) -def test_sequence_plot_as_cube_ImageAnimator(test_input, test_kwargs, expected_values): - # Unpack expected values - expected_data, expected_axis_ranges, expected_xlabel, expected_ylabel, \ - expected_extent = expected_values +def test_sequence_plot_as_cube_ImageAnimator(test_input, test_kwargs, expected_data): # Run plot method output = test_input.plot_as_cube(**test_kwargs) # Check plot object properties are correct. assert type(output) == ndcube.mixins.sequence_plotting.ImageAnimatorCubeLikeNDCubeSequence - #np.testing.assert_array_equal(output.data, expected_data) - #for i in range(len(expected_axis_ranges)): - # np.testing.assert_array_equal(output.axis_ranges[i], expected_axis_ranges[i]) - #assert output.axes.get_xaxis().get_label_text() == expected_xlabel - #assert output.axes.get_yaxis().get_label_text() == expected_ylabel - #assert np.allclose(output.im.get_extent(), expected_extent) + np.testing.assert_array_equal(output.data, expected_data) @pytest.mark.parametrize("test_input, expected", [ From 3d62c7f84534cdbe6310bd94eeade1e5832f45e9 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Mon, 26 Mar 2018 14:13:14 -0400 Subject: [PATCH 25/30] Delete visualisation module as no longer used. --- ndcube/visualization/__init__.py | 3 - ndcube/visualization/animation.py | 178 ------------------------------ 2 files changed, 181 deletions(-) delete mode 100644 ndcube/visualization/__init__.py delete mode 100644 ndcube/visualization/animation.py diff --git a/ndcube/visualization/__init__.py b/ndcube/visualization/__init__.py deleted file mode 100644 index 4332d48cd..000000000 --- a/ndcube/visualization/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -'''Sunpycube Visualization''' - -from . import animation diff --git a/ndcube/visualization/animation.py b/ndcube/visualization/animation.py deleted file mode 100644 index c508a9ad7..000000000 --- a/ndcube/visualization/animation.py +++ /dev/null @@ -1,178 +0,0 @@ -import numpy as np -from sunpy.visualization.imageanimator import ImageAnimatorWCS - -from ndcube import utils - - -class ImageAnimatorNDCubeSequence(ImageAnimatorWCS): - """ - Animates N-dimensional data with the associated astropy WCS object. - - The following keyboard shortcuts are defined in the viewer: - - left': previous step on active slider - right': next step on active slider - top': change the active slider up one - bottom': change the active slider down one - 'p': play/pause active slider - - This viewer can have user defined buttons added by specifying the labels - and functions called when those buttons are clicked as keyword arguments. - - Parameters - ---------- - seq: `ndcube.datacube.CubeSequence` - The list of cubes. - - image_axes: `list` - The two axes that make the image - - fig: `matplotlib.figure.Figure` - Figure to use - - axis_ranges: list of physical coordinates for array or None - If None array indices will be used for all axes. - If a list it should contain one element for each axis of the numpy array. - For the image axes a [min, max] pair should be specified which will be - passed to :func:`matplotlib.pyplot.imshow` as extent. - For the slider axes a [min, max] pair can be specified or an array the - same length as the axis which will provide all values for that slider. - If None is specified for an axis then the array indices will be used - for that axis. - - interval: `int` - Animation interval in ms - - colorbar: `bool` - Plot colorbar - - button_labels: `list` - List of strings to label buttons - - button_func: `list` - List of functions to map to the buttons - - unit_x_axis: `astropy.units.Unit` - The unit of x axis. - - unit_y_axis: `astropy.units.Unit` - The unit of y axis. - - Extra keywords are passed to imshow. - - """ - def __init__(self, seq, wcs=None, **kwargs): - if wcs is None: - wcs = seq[0].wcs - self.sequence = seq.data - self.cumul_cube_lengths = np.cumsum(np.ones(len(self.sequence))) - data_concat = np.stack([cube.data for cube in seq.data]) - # Add dimensions of length 1 of concatenated data array - # shape for an missing axes. - if seq[0].wcs.naxis != len(seq.dimensions) - 1: - new_shape = list(data_concat.shape) - for i in np.arange(seq[0].wcs.naxis)[seq[0].missing_axis[::-1]]: - new_shape.insert(i+1, 1) - data_concat = data_concat.reshape(new_shape) - # Add dummy axis to WCS object to represent sequence axis. - new_wcs = utils.wcs.append_sequence_axis_to_wcs(wcs) - - super(ImageAnimatorNDCubeSequence, self).__init__(data_concat, wcs=new_wcs, **kwargs) - - -class ImageAnimatorCommonAxisNDCubeSequence(ImageAnimatorWCS): - """ - Animates N-dimensional data with the associated astropy WCS object. - - The following keyboard shortcuts are defined in the viewer: - - left': previous step on active slider - right': next step on active slider - top': change the active slider up one - bottom': change the active slider down one - 'p': play/pause active slider - - This viewer can have user defined buttons added by specifying the labels - and functions called when those buttons are clicked as keyword arguments. - - Parameters - ---------- - seq: `ndcube.datacube.CubeSequence` - The list of cubes. - - image_axes: `list` - The two axes that make the image - - fig: `matplotlib.figure.Figure` - Figure to use - - axis_ranges: list of physical coordinates for array or None - If None array indices will be used for all axes. - If a list it should contain one element for each axis of the numpy array. - For the image axes a [min, max] pair should be specified which will be - passed to :func:`matplotlib.pyplot.imshow` as extent. - For the slider axes a [min, max] pair can be specified or an array the - same length as the axis which will provide all values for that slider. - If None is specified for an axis then the array indices will be used - for that axis. - - interval: `int` - Animation interval in ms - - colorbar: `bool` - Plot colorbar - - button_labels: `list` - List of strings to label buttons - - button_func: `list` - List of functions to map to the buttons - - unit_x_axis: `astropy.units.Unit` - The unit of x axis. - - unit_y_axis: `astropy.units.Unit` - The unit of y axis. - - Extra keywords are passed to imshow. - - """ - def __init__(self, seq, wcs=None, **kwargs): - if seq._common_axis is None: - raise ValueError("Common axis must be set to use this class. " - "Use ImageAnimatorNDCubeSequence.") - if wcs is None: - wcs = seq[0].wcs - self.sequence = seq.data - self.cumul_cube_lengths = np.cumsum(np.array( - [c.dimensions[0].value for c in self.sequence], dtype=int)) - data_concat = np.concatenate([cube.data for cube in seq.data], axis=seq._common_axis) - # Add dimensions of length 1 of concatenated data array - # shape for an missing axes. - if seq[0].wcs.naxis != len(seq.dimensions) - 1: - new_shape = list(data_concat.shape) - for i in np.arange(seq[0].wcs.naxis)[seq[0].missing_axis[::-1]]: - new_shape.insert(i, 1) - data_concat = data_concat.reshape(new_shape) - - super(ImageAnimatorCommonAxisNDCubeSequence, self).__init__( - data_concat, wcs=wcs, **kwargs) - - def update_plot(self, val, im, slider): - val = int(val) - ax_ind = self.slider_axes[slider.slider_ind] - ind = np.argmin(np.abs(self.axis_ranges[ax_ind] - val)) - self.frame_slice[ax_ind] = ind - list_slices_wcsaxes = list(self.slices_wcsaxes) - sequence_slice = utils.sequence._convert_cube_like_index_to_sequence_slice( - val, self.cumul_cube_lengths) - sequence_index = sequence_slice.sequence_index - cube_index = sequence_slice.common_axis_item - list_slices_wcsaxes[self.wcs.naxis-ax_ind-1] = cube_index - self.slices_wcsaxes = list_slices_wcsaxes - if val != slider.cval: - self.axes.reset_wcs( - wcs=self.sequence[sequence_index].wcs, slices=self.slices_wcsaxes) - self._set_unit_in_axis(self.axes) - im.set_array(self.data[self.frame_slice]) - slider.cval = val From 99aef15057c24a9db60e9797e0d0279360acffe8 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Mon, 26 Mar 2018 14:26:22 -0400 Subject: [PATCH 26/30] Remove LineAnimator capability as it currently depends on sunpy master. Add back in after sunpy 0.9 release. --- ndcube/mixins/sequence_plotting.py | 392 +------------------------ ndcube/tests/test_sequence_plotting.py | 43 --- 2 files changed, 2 insertions(+), 433 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 298308c56..960bfaf09 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -97,12 +97,7 @@ def plot(self, axes=None, plot_axis_indices=None, if len(plot_axis_indices) == 1: # Since sequence has more than 1 dimension and number of plot axes is 1, # produce a 1D line animation. - if axes_units is not None: - unit_x_axis = axes_units[plot_axis_indices[0]] - else: - unit_x_axis = None - ax = LineAnimatorNDCubeSequence(self, plot_axis_indices[0], axes_coordinates, - unit_x_axis, data_unit, **kwargs) + raise NotImplementedError() elif len(plot_axis_indices) == 2: if naxis == 2: # Since sequence has 2 dimensions and number of plot axes is 2, @@ -206,13 +201,7 @@ def plot_as_cube(self, axes=None, plot_axis_indices=None, if len(plot_axis_indices) == 1: # Since sequence has more than 1 cube-like dimension and # number of plot axes is 1, produce a 1D line animation. - if axes_units is not None: - unit_x_axis = axes_units[plot_axis_indices[0]] - else: - unit_x_axis = None - ax = LineAnimatorCubeLikeNDCubeSequence(self, plot_axis_indices[0], - axes_coordinates, unit_x_axis, - data_unit=data_unit, **kwargs) + raise NotImplementedError() elif len(plot_axis_indices) == 2: if naxis == 2: # Since sequence has 2 cube-like dimensions and @@ -847,383 +836,6 @@ def update_plot(self, val, im, slider): slider.cval = val -class LineAnimatorNDCubeSequence(LineAnimator): - """ - Animates N-dimensional data with the associated astropy WCS object. - - The following keyboard shortcuts are defined in the viewer: - - left': previous step on active slider - right': next step on active slider - top': change the active slider up one - bottom': change the active slider down one - 'p': play/pause active slider - - This viewer can have user defined buttons added by specifying the labels - and functions called when those buttons are clicked as keyword arguments. - - Parameters - ---------- - seq: `ndcube.datacube.CubeSequence` - The list of cubes. - - image_axes: `list` - The two axes that make the image - - fig: `matplotlib.figure.Figure` - Figure to use - - axis_ranges: list of physical coordinates for array or None - If None array indices will be used for all axes. - If a list it should contain one element for each axis of the numpy array. - For the image axes a [min, max] pair should be specified which will be - passed to :func:`matplotlib.pyplot.imshow` as extent. - For the slider axes a [min, max] pair can be specified or an array the - same length as the axis which will provide all values for that slider. - If None is specified for an axis then the array indices will be used - for that axis. - - interval: `int` - Animation interval in ms - - colorbar: `bool` - Plot colorbar - - button_labels: `list` - List of strings to label buttons - - button_func: `list` - List of functions to map to the buttons - - unit_x_axis: `astropy.units.Unit` - The unit of x axis. - - unit_y_axis: `astropy.units.Unit` - The unit of y axis. - - Extra keywords are passed to imshow. - - """ - def __init__(self, seq, plot_axis_index=None, axis_ranges=None, unit_x_axis=None, - data_unit=None, xlabel=None, ylabel=None, xlim=None, ylim=None, **kwargs): - if plot_axis_index is None: - plot_axis_index = -1 - # Combine data from cubes in sequence. If all cubes have a unit, - # put data into data_unit. - sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) - if sequence_units is None: - if data_unit is None: - data_concat = np.stack([cube.data for i, cube in enumerate(seq.data)]) - else: - raise TypeError(NON_COMPATIBLE_UNIT_MESSAGE) - else: - data_concat = np.stack([(cube.data * sequence_units[i]).to(data_unit).value - for i, cube in enumerate(seq.data)]) - - # If some cubes have a mask set, convert data to masked array. - # If other cubes do not have a mask set, set all mask to False. - # If no cubes have a mask, keep data as a simple array. - cubes_with_mask = np.array([False if cube.mask is None else True for cube in seq.data]) - if cubes_with_mask.any(): - if cubes_with_mask.all(): - mask_concat = np.stack([cube.mask for cube in seq.data]) - else: - masks = [] - for i, cube in enumerate(seq.data): - if cubes_with_mask[i]: - masks.append(cube.mask) - else: - masks.append(np.zeros_like(cube.data, dtype=bool)) - mask_concat = np.stack(masks) - data_concat = np.ma.masked_array(data_concat, mask_concat) - - # Ensure plot_axis_index is represented in the positive convention. - if plot_axis_index < 0: - plot_axis_index = len(seq.dimensions) + plot_axis_index - # Calculate the x-axis values if axis_ranges not supplied. - if axis_ranges is None: - axis_ranges = [None] * len(seq.dimensions) - if plot_axis_index == 0: - axis_ranges[plot_axis_index] = np.arange(len(seq.data)) - else: - cube_plot_axis_index = plot_axis_index - 1 - # Define unit of x-axis if not supplied by user. - if unit_x_axis is None: - wcs_plot_axis_index = utils.cube.data_axis_to_wcs_axis( - cube_plot_axis_index, seq[0].missing_axis) - unit_x_axis = np.asarray(seq[0].wcs.wcs.cunit)[wcs_plot_axis_index] - # Get x-axis values from each cube and combine into a single - # array for axis_ranges kwargs. - x_axis_coords = _get_non_common_axis_x_axis_coords(seq.data, cube_plot_axis_index) - axis_ranges[plot_axis_index] = np.stack(x_axis_coords) - # Set x-axis label. - if xlabel is None: - xlabel = "{0} [{1}]".format(seq.world_axis_physical_types[plot_axis_index], - unit_x_axis) - else: - # If the axis range is being defined by an extra coordinate... - if isinstance(axis_ranges[plot_axis_index], str): - axis_extra_coord = axis_ranges[plot_axis_index] - if plot_axis_index == 0: - # If the sequence axis is the plot axis, use - # sequence_axis_extra_coords to get the extra coord values - # for whole sequence. - x_axis_coords = seq.sequence_axis_extra_coords[axis_extra_coord] - if isinstance(x_axis_coords, u.Quantity) and (unit_x_axis is not None): - x_axis_coords = x_axis_coords.to(unit_x_axis).value - else: - # Else get extra coord values from each cube and - # combine into a single array for axis_ranges kwargs. - # First, confirm extra coord is of same type and corresponds - # to same axes in each cube. - extra_coord_type = np.empty(len(seq.data), dtype=object) - extra_coord_axes = np.empty(len(seq.data), dtype=object) - x_axis_coords = [] - for i, cube in enumerate(seq.data): - cube_axis_extra_coord = cube.extra_coords[axis_extra_coord] - extra_coord_type[i] = type(cube_axis_extra_coord["value"]) - extra_coord_axes[i] = cube_axis_extra_coord["axis"] - x_axis_coords.append(cube_axis_extra_coord["value"]) - if extra_coord_type.all() == extra_coord_type[0]: - extra_coord_type = extra_coord_type[0] - else: - raise TypeError("Extra coord {0} must be of same type for all NDCubes to " - "use it to define a plot axis.".format(axis_extra_coord)) - if extra_coord_axes.all() == extra_coord_axes[0]: - if isinstance(extra_coord_axes[0], (int, np.int64)): - extra_coord_axes = [int(extra_coord_axes[0])] - else: - extra_coord_axes = list(extra_coord_axes[0]).sort() - else: - raise ValueError("Extra coord {0} must correspond to same axes in each " - "NDCube to use it to define a plot axis.".format( - axis_extra_coord)) - # If the extra coord is a quantity, convert to the correct unit. - if extra_coord_type is u.Quantity: - if unit_x_axis is None: - unit_x_axis = seq[0].extra_coords[axis_extra_coord]["value"].unit - x_axis_coords = [x_axis_value.to(unit_x_axis).value - for x_axis_value in x_axis_coords] - # If extra coord is same for each cube, storing - # values as single 1D axis range will suffice. - if ((np.array(x_axis_coords) == x_axis_coords[0]).all() and - (len(extra_coord_axes) == 1)): - x_axis_coords = x_axis_coords[0] - else: - # Else if all axes are not dependent, create an array of x-axis - # coords for each cube that are the same shape as the data in the - # respective cubes where the x coords are replicated in the extra - # dimensions. Then stack them together along the sequence axis so - # the final x-axis coord array is the same shape as the data array. - # This will be used in determining the correct x-axis coords for - # each frame of the animation. - if len(extra_coord_axes) != data_concat.ndim: - x_axis_coords_copy = copy.deepcopy(x_axis_coords) - x_axis_coords = [] - for i, x_axis_cube_coords in enumerate(x_axis_coords_copy): - # For each cube in the sequence, use np.tile to replicate - # the x-axis coords through the higher dimensions. - # But first give extra dummy (length 1) dimensions to the - # x-axis coords array so its number of dimensions is the - # same as the cube's data array. - # First, create shape of pre-np.tiled x-coord array for the cube. - coords_reshape = np.array([1] * seq[i].data.ndim) - coords_reshape[extra_coord_axes] = x_axis_cube_coords.shape - # Then reshape x-axis array to give it the dummy dimensions. - x_axis_cube_coords = x_axis_cube_coords.reshape( - tuple(coords_reshape)) - # Now the correct dummy dimensions are in place so the - # number of dimensions in the x-axis coord array equals - # the number of dimensions of the cube's data array, - # replicating the coords through the higher dimensions - # is simple using np.tile. - x_axis_cube_coords = np.tile(x_axis_cube_coords, tile_shape) - # Append new dimension-ed x-axis coords array for this cube - # sequence x-axis coords list. - x_axis_coords.append(x_axis_cube_coords) - # Stack the x-axis coords along a new axis for the sequence axis so - # its the same shape as the data array. - x_axis_coords = np.stack(x_axis_coords) - # Set x-axis label. - if xlabel is None: - xlabel = "{0} [{1}]".format(axis_extra_coord, unit_x_axis) - # Re-enter x-axis values into axis_ranges - axis_ranges[plot_axis_index] = x_axis_coords - # Make label for y-axis. - if ylabel is None: - ylabel = "Data [{0}]".format(data_unit) - - super(LineAnimatorNDCubeSequence, self).__init__( - data_concat, plot_axis_index=plot_axis_index, axis_ranges=axis_ranges, - xlabel=xlabel, ylabel=ylabel, xlim=xlim, ylim=ylim, **kwargs) - - -class LineAnimatorCubeLikeNDCubeSequence(LineAnimator): - """ - Animates N-dimensional data with the associated astropy WCS object. - - The following keyboard shortcuts are defined in the viewer: - - left': previous step on active slider - right': next step on active slider - top': change the active slider up one - bottom': change the active slider down one - 'p': play/pause active slider - - This viewer can have user defined buttons added by specifying the labels - and functions called when those buttons are clicked as keyword arguments. - - Parameters - ---------- - seq: `ndcube.datacube.CubeSequence` - The list of cubes. - - image_axes: `list` - The two axes that make the image - - fig: `matplotlib.figure.Figure` - Figure to use - - axis_ranges: list of physical coordinates for array or None - If None array indices will be used for all axes. - If a list it should contain one element for each axis of the numpy array. - For the image axes a [min, max] pair should be specified which will be - passed to :func:`matplotlib.pyplot.imshow` as extent. - For the slider axes a [min, max] pair can be specified or an array the - same length as the axis which will provide all values for that slider. - If None is specified for an axis then the array indices will be used - for that axis. - - interval: `int` - Animation interval in ms - - colorbar: `bool` - Plot colorbar - - button_labels: `list` - List of strings to label buttons - - button_func: `list` - List of functions to map to the buttons - - unit_x_axis: `astropy.units.Unit` - The unit of x axis. - - unit_y_axis: `astropy.units.Unit` - The unit of y axis. - - Extra keywords are passed to imshow. - - """ - def __init__(self, seq, plot_axis_index=None, axis_ranges=None, unit_x_axis=None, - data_unit=None, xlabel=None, ylabel=None, xlim=None, ylim=None, **kwargs): - if plot_axis_index is None: - plot_axis_index = -1 - # Combine data from cubes in sequence. If all cubes have a unit, - # put data into data_unit. - sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) - if sequence_units is None: - if data_unit is None: - data_concat = np.concatenate([cube.data for i, cube in enumerate(seq.data)], - axis=seq._common_axis) - else: - raise TypeError(NON_COMPATIBLE_UNIT_MESSAGE) - else: - data_concat = np.concatenate([(cube.data * sequence_units[i]).to(data_unit).value - for i, cube in enumerate(seq.data)], - axis=seq._common_axis) - - # If some cubes have a mask set, convert data to masked array. - # If other cubes do not have a mask set, set all mask to False. - # If no cubes have a mask, keep data as a simple array. - cubes_with_mask = np.array([False if cube.mask is None else True for cube in seq.data]) - if cubes_with_mask.any(): - if cubes_with_mask.all(): - mask_concat = np.concatenate([cube.mask for cube in seq.data], axis=seq._common_axis) - else: - masks = [] - for i, cube in enumerate(seq.data): - if cubes_with_mask[i]: - masks.append(cube.mask) - else: - masks.append(np.zeros_like(cube.data, dtype=bool)) - mask_concat = np.concatenate(masks, axis=seq._common_axis) - data_concat = np.ma.masked_array(data_concat, mask_concat) - - # Ensure plot_axis_index is represented in the positive convention. - if plot_axis_index < 0: - plot_axis_index = len(seq.cube_like_dimensions) + plot_axis_index - # Calculate the x-axis values if axis_ranges not supplied. - if axis_ranges is None: - axis_ranges = [None] * len(seq.cube_like_dimensions) - # Define unit of x-axis if not supplied by user. - if unit_x_axis is None: - wcs_plot_axis_index = utils.cube.data_axis_to_wcs_axis( - plot_axis_index, seq[0].missing_axis) - unit_x_axis = np.asarray( - seq[0].wcs.wcs.cunit)[np.invert(seq[0].missing_axis)][wcs_plot_axis_index] - if plot_axis_index == seq._common_axis: - # Determine whether common axis is dependent. - x_axis_coords = np.concatenate( - [cube.axis_world_coords(plot_axis_index).to(unit_x_axis).value - for cube in seq.data], axis=plot_axis_index) - dependent_axes = utils.wcs.get_dependent_data_axes( - seq[0].wcs, plot_axis_index, seq[0].missing_axis) - if len(dependent_axes) > 1: - independent_axes = list(range(data_concat.ndim)) - for i in list(dependent_axes)[::-1]: - independent_axes.pop(i) - # Expand dimensionality of x_axis_cube_coords using np.tile - tile_shape = tuple(list(np.array( - data_concat.shape)[independent_axes]) + [1]*len(dependent_axes)) - x_axis_coords = np.tile(x_axis_cube_coords, tile_shape) - # Since np.tile puts original array's dimensions as last, - # reshape x_axis_cube_coords to cube's shape. - x_axis_coords = x_axis_coords.reshape(seq.cube_like_dimensions.value) - else: - # Get x-axis values from each cube and combine into a single - # array for axis_ranges kwargs. - x_axis_coords = _get_non_common_axis_x_axis_coords(seq.data, plot_axis_index) - axis_ranges[plot_axis_index] = np.concatenate(x_axis_coords, axis=seq._common_axis) - # Set axis labels and limits, etc. - if xlabel is None: - xlabel = "{0} [{1}]".format( - seq.cube_like_world_axis_physical_types[plot_axis_index], unit_x_axis) - if ylabel is None: - ylabel = "Data [{0}]".format(data_unit) - - super(LineAnimatorCubeLikeNDCubeSequence, self).__init__( - data_concat, plot_axis_index=plot_axis_index, axis_ranges=axis_ranges, - xlabel=xlabel, ylabel=ylabel, xlim=xlim, ylim=ylim, **kwargs) - - -def _get_non_common_axis_x_axis_coords(seq_data, plot_axis_index): - """Get coords of an axis from NDCubes and combine into single array.""" - x_axis_coords = [] - for i, cube in enumerate(seq_data): - # Get the x-axis coordinates for each cube. - #x_axis_cube_coords = cube.axis_world_coords(plot_axis_index).to(unit_x_axis).value - x_axis_cube_coords = cube.axis_world_coords(plot_axis_index).value - # If the returned x-values have fewer dimensions than the cube, - # repeat the x-values through the higher dimensions. - if x_axis_cube_coords.shape != cube.data.shape: - # Get sequence axes dependent and independent of plot_axis_index. - dependent_axes = utils.wcs.get_dependent_data_axes( - cube.wcs, plot_axis_index, cube.missing_axis) - independent_axes = list(range(len(cube.dimensions))) - for i in list(dependent_axes)[::-1]: - independent_axes.pop(i) - # Expand dimensionality of x_axis_cube_coords using np.tile - tile_shape = tuple(list(np.array( - cube.data.shape)[independent_axes]) + [1]*len(dependent_axes)) - x_axis_cube_coords = np.tile(x_axis_cube_coords, tile_shape) - # Since np.tile puts original array's dimensions as last, - # reshape x_axis_cube_coords to cube's shape. - x_axis_cube_coords = x_axis_cube_coords.reshape(cube.data.shape) - x_axis_coords.append(x_axis_cube_coords) - return x_axis_coords - - def _determine_sequence_units(cubesequence_data, unit=None): """ Returns units of cubes in sequence and derives data unit if not set. diff --git a/ndcube/tests/test_sequence_plotting.py b/ndcube/tests/test_sequence_plotting.py index eaf240b35..f08c66ac0 100644 --- a/ndcube/tests/test_sequence_plotting.py +++ b/ndcube/tests/test_sequence_plotting.py @@ -380,49 +380,6 @@ def test_sequence_plot_as_cube_error(): seq_no_common_axis.plot_as_cube() -""" -@pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ - (seq, {"plot_axis_indices": 3}, - (ndcube.mixins.sequence_plotting.LineAnimatorNDCubeSequence, seq_stack.data, - none_axis_ranges_axis3, "time [min]", "Data [None]", - (none_axis_ranges_axis3[-1].min(), none_axis_ranges_axis3[-1].max()), - (seq_stack.data.min(), seq_stack.data.max()))), - (seq, {"plot_axis_indices": -1, "data_unit": u.km}, - (ndcube.mixins.sequence_plotting.LineAnimatorNDCubeSequence, seq_stack_km.data, - none_axis_ranges_axis3, "time [min]", "Data [None]", - (none_axis_ranges_axis3[-1].min(), none_axis_ranges_axis3[-1].max()), - (seq_stack.data.min(), seq_stack.data.max()))), - (seq, {"plot_axis_indices": -1}, - (ndcube.mixins.sequence_plotting.LineAnimatorNDCubeSequence, seq_stack, - none_axis_ranges_axis3, "time [min]", "Data [None]", - (none_axis_ranges_axis3[-1].min(), none_axis_ranges_axis3[-1].max()), - (seq_stack.data.min(), seq_stack.data.max())))]) -def test_sequence_plot_LineAnimator(test_input, test_kwargs, expected_values): - # Unpack expected values - expected_type, expected_data, expected_axis_ranges, expected_xlabel, \ - expected_ylabel, expected_xlim, expected_ylim = expected_values - # Run plot method. - output = seq.plot(**test_kwargs) - # Check right type of plot object is produced. - assert type(output) is expected_type - # Check data being plotted is correct - np.testing.assert_array_equal(output.data, expected_data) - if type(expected_data) is np.ma.core.MaskedArray: - np.testing.assert_array_equal(output.data.mask, expected_data.mask) - # Check values of axes and sliders is correct. - for i in range(len(output.axis_ranges)): - print(i) - np.testing.assert_array_equal(output.axis_ranges[i], expected_axis_ranges[i]) - # Check plot axis labels and limits are correct - assert output.xlabel == expected_xlabel - assert output.ylabel == expected_ylabel - assert output.xlim == expected_xlim - assert output.ylim == expected_ylim -""" - -def test_sequence_plot_as_cube_LineAnimator(): - pass - @pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ (seq[:, :, 0, 0], {}, (seq_stack[:, :, 0, 0], From bfe945c62021b1349cc3f203f46783ba387b600c Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Mon, 26 Mar 2018 14:39:59 -0400 Subject: [PATCH 27/30] PEP8 changes. --- ndcube/mixins/sequence_plotting.py | 33 ++++---- ndcube/tests/test_sequence_plotting.py | 103 +++++++++++++------------ 2 files changed, 73 insertions(+), 63 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 960bfaf09..fc9936845 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -13,6 +13,7 @@ AXES_UNIT_ERRONESLY_SET_MESSAGE = \ "axes_unit element must be None unless corresponding axes_coordinate is None or a Quantity." + class NDCubeSequencePlotMixin: def plot(self, axes=None, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): @@ -83,7 +84,7 @@ def plot(self, axes=None, plot_axis_indices=None, Returns ------- ax: ??? - + """ # Check kwargs are in consistent formats and set default values if not done so by user. naxis = len(self.dimensions) @@ -183,7 +184,7 @@ def plot_as_cube(self, axes=None, plot_axis_indices=None, Returns ------- ax: ??? - + """ # Verify common axis is set. if self._common_axis is None: @@ -392,7 +393,6 @@ def _plot_2D_sequence_as_1Dline(self, axes_coordinates=None, fig, ax = _make_1D_sequence_plot(xdata, ydata, yerror, unit_y_axis, default_xlabel, kwargs) return ax - def _plot_2D_sequence(self, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): """ @@ -530,7 +530,7 @@ def _plot_3D_sequence_as_2Dimage(self, axes=None, plot_axis_indices=None, if sequence_units is not None: data = np.concatenate([(cube.data * sequence_units[i]).to(data_unit).value for i, cube in enumerate(self.data)], - axis=self._common_axis) + axis=self._common_axis) else: data = np.concatenate([cube.data for cube in self.data], axis=self._common_axis) @@ -548,7 +548,7 @@ def _plot_3D_sequence_as_2Dimage(self, axes=None, plot_axis_indices=None, cube_axis_unit = np.array(self[0].wcs.wcs.cunit)[ np.invert(self[0].missing_axis)][0] cube_axis_coords = \ - self[0].axis_world_coords()[cube_axis_index].to(cube_axis_unit).value + self[0].axis_world_coords()[cube_axis_index].to(cube_axis_unit).value cube_axis_name = self.cube_like_world_axis_physical_types[1] else: if isinstance(axes_coordinates[cube_axis_index], str): @@ -610,14 +610,19 @@ def _plot_3D_sequence_as_2Dimage(self, axes=None, plot_axis_indices=None, # Since we can't assume the x-axis will be uniform, create NonUniformImage # axes and add it to the axes object. im_ax = mpl.image.NonUniformImage( - ax, extent=(axes_coordinates[plot_axis_indices[0]][0], axes_coordinates[plot_axis_indices[0]][-1], - axes_coordinates[plot_axis_indices[1]][0], axes_coordinates[plot_axis_indices[1]][-1]), + ax, extent=(axes_coordinates[plot_axis_indices[0]][0], + axes_coordinates[plot_axis_indices[0]][-1], + axes_coordinates[plot_axis_indices[1]][0], + axes_coordinates[plot_axis_indices[1]][-1]), **kwargs) - im_ax.set_data(axes_coordinates[plot_axis_indices[0]], axes_coordinates[plot_axis_indices[1]], data) + im_ax.set_data(axes_coordinates[plot_axis_indices[0]], + axes_coordinates[plot_axis_indices[1]], data) ax.add_image(im_ax) # Set the limits, labels, etc. of the axes. - ax.set_xlim((axes_coordinates[plot_axis_indices[0]][0], axes_coordinates[plot_axis_indices[0]][-1])) - ax.set_ylim((axes_coordinates[plot_axis_indices[1]][0], axes_coordinates[plot_axis_indices[1]][-1])) + ax.set_xlim((axes_coordinates[plot_axis_indices[0]][0], + axes_coordinates[plot_axis_indices[0]][-1])) + ax.set_ylim((axes_coordinates[plot_axis_indices[1]][0], + axes_coordinates[plot_axis_indices[1]][-1])) ax.set_xlabel(axes_labels[plot_axis_indices[0]]) ax.set_ylabel(axes_labels[plot_axis_indices[1]]) @@ -683,14 +688,14 @@ class ImageAnimatorNDCubeSequence(ImageAnimatorWCS): """ def __init__(self, seq, wcs=None, axes=None, plot_axis_indices=None, axes_coordinates=None, axes_units=None, data_unit=None, **kwargs): - self.sequence = seq.data # Required by parent class. + self.sequence = seq.data # Required by parent class. # Set default values of kwargs if not set. if wcs is None: wcs = seq[0].wcs if axes_coordinates is None: axes_coordinates = [None] * len(seq.dimensions) if axes_units is None: - axes_units = [None] * len(seq.dimensions) + axes_units = [None] * len(seq.dimensions) # Determine units of each cube in sequence. sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) # If all cubes have unit set, create a data quantity from cube's data. @@ -781,14 +786,14 @@ def __init__(self, seq, wcs=None, axes=None, plot_axis_indices=None, if seq._common_axis is None: raise TypeError("Common axis must be set to use this class. " "Use ImageAnimatorNDCubeSequence.") - self.sequence = seq.data # Required by parent class. + self.sequence = seq.data # Required by parent class. # Set default values of kwargs if not set. if wcs is None: wcs = seq[0].wcs if axes_coordinates is None: axes_coordinates = [None] * len(seq.cube_like_dimensions) if axes_units is None: - axes_units = [None] * len(seq.cube_like_dimensions) + axes_units = [None] * len(seq.cube_like_dimensions) # Determine units of each cube in sequence. sequence_units, data_unit = _determine_sequence_units(seq.data, data_unit) # If all cubes have unit set, create a data quantity from cube's data. diff --git a/ndcube/tests/test_sequence_plotting.py b/ndcube/tests/test_sequence_plotting.py index f08c66ac0..0382c8b66 100644 --- a/ndcube/tests/test_sequence_plotting.py +++ b/ndcube/tests/test_sequence_plotting.py @@ -190,13 +190,13 @@ @pytest.mark.parametrize("test_input, test_kwargs, expected_values", [ (seq[:, 0, 0, 0], {}, - (np.arange(len(seq.data)), np.array([ 1, 11, 1, 11]), + (np.arange(len(seq.data)), np.array([1, 11, 1, 11]), "meta.obs.sequence [None]", "Data [None]", (0, len(seq[:, 0, 0, 0].data)-1), (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))), (seq_with_units[:, 0, 0, 0], {}, - (np.arange(len(seq_with_units.data)), np.array([ 1, 0.011, 1, 0.011]), + (np.arange(len(seq_with_units.data)), np.array([1, 0.011, 1, 0.011]), "meta.obs.sequence [None]", "Data [km]", (0, len(seq_with_units[:, 0, 0, 0].data)-1), (min([(cube.data * cube.unit).to(seq_with_units[:, 0, 0, 0].data[0].unit).value for cube in seq_with_units[:, 0, 0, 0].data]), @@ -204,13 +204,13 @@ for cube in seq_with_units[:, 0, 0, 0].data])))), (seq_with_uncertainty[:, 0, 0, 0], {}, - (np.arange(len(seq_with_uncertainty.data)), np.array([ 1, 11, 1, 11]), + (np.arange(len(seq_with_uncertainty.data)), np.array([1, 11, 1, 11]), "meta.obs.sequence [None]", "Data [None]", (0, len(seq_with_uncertainty[:, 0, 0, 0].data)-1), (min([cube.data for cube in seq_with_uncertainty[:, 0, 0, 0].data]), max([cube.data for cube in seq_with_uncertainty[:, 0, 0, 0].data])))), (seq_with_units_and_uncertainty[:, 0, 0, 0], {}, - (np.arange(len(seq_with_units_and_uncertainty.data)), np.array([ 1, 0.011, 1, 0.011]), + (np.arange(len(seq_with_units_and_uncertainty.data)), np.array([1, 0.011, 1, 0.011]), "meta.obs.sequence [None]", "Data [km]", (0, len(seq_with_units_and_uncertainty[:, 0, 0, 0].data)-1), (min([(cube.data*cube.unit).to(seq_with_units_and_uncertainty[:, 0, 0, 0].data[0].unit).value @@ -219,35 +219,37 @@ for cube in seq_with_units_and_uncertainty[:, 0, 0, 0].data])))), (seq_with_units_and_some_uncertainty[:, 0, 0, 0], {}, - (np.arange(len(seq_with_units_and_some_uncertainty.data)), np.array([ 1, 0.011, 1, 0.011]), + (np.arange(len(seq_with_units_and_some_uncertainty.data)), np.array([1, 0.011, 1, 0.011]), "meta.obs.sequence [None]", "Data [km]", (0, len(seq_with_units_and_some_uncertainty[:, 0, 0, 0].data)-1), - (min([(cube.data*cube.unit).to(seq_with_units_and_some_uncertainty[:, 0, 0, 0].data[0].unit).value - for cube in seq_with_units_and_some_uncertainty[:, 0, 0, 0].data]), - max([(cube.data*cube.unit).to(seq_with_units_and_some_uncertainty[:, 0, 0, 0].data[0].unit).value - for cube in seq_with_units_and_some_uncertainty[:, 0, 0, 0].data])))), + (min([(cube.data*cube.unit).to( + seq_with_units_and_some_uncertainty[:, 0, 0, 0].data[0].unit).value + for cube in seq_with_units_and_some_uncertainty[:, 0, 0, 0].data]), + max([(cube.data*cube.unit).to( + seq_with_units_and_some_uncertainty[:, 0, 0, 0].data[0].unit).value + for cube in seq_with_units_and_some_uncertainty[:, 0, 0, 0].data])))), (seq[:, 0, 0, 0], {"axes_coordinates": "distance"}, - ((seq.sequence_axis_extra_coords["distance"]), np.array([ 1, 11, 1, 11]), + ((seq.sequence_axis_extra_coords["distance"]), np.array([1, 11, 1, 11]), "distance [{0}]".format(seq.sequence_axis_extra_coords["distance"].unit), "Data [None]", (min(seq.sequence_axis_extra_coords["distance"].value), max(seq.sequence_axis_extra_coords["distance"].value)), (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), - max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))), + max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))), (seq[:, 0, 0, 0], {"axes_coordinates": u.Quantity(np.arange(len(seq.data)), unit=u.cm), "axes_units": u.km}, - (u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km), np.array([ 1, 11, 1, 11]), + (u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km), np.array([1, 11, 1, 11]), "meta.obs.sequence [km]", "Data [None]", (min((u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km).value)), max((u.Quantity(np.arange(len(seq.data)), unit=u.cm).to(u.km).value))), (min([cube.data.min() for cube in seq[:, 0, 0, 0].data]), - max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))) + max([cube.data.max() for cube in seq[:, 0, 0, 0].data])))) ]) def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): # Unpack expected values expected_x_data, expected_y_data, expected_xlabel, expected_ylabel, \ - expected_xlim, expected_ylim = expected_values + expected_xlim, expected_ylim = expected_values # Run plot method output = test_input.plot(**test_kwargs) # Check values are correct @@ -268,18 +270,18 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): (seq[:, :, 0, 0], {}, (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848]), - np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), - "{0} [{1}]".format(seq[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), - "Data [None]", tuple(seq_axis1_lim_deg), + np.array([1, 2, 11, 22, 1, 2, 11, 22]), + "{0} [{1}]".format(seq[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), + "Data [None]", tuple(seq_axis1_lim_deg), (min([cube.data.min() for cube in seq[:, :, 0, 0].data]), max([cube.data.max() for cube in seq[:, :, 0, 0].data])))), (seq_with_units[:, :, 0, 0], {}, (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848]), - np.array([ 1, 2, 0.011, 0.022, 1, 2, 0.011, 0.022]), - "{0} [{1}]".format(seq[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), - "Data [km]", tuple(seq_axis1_lim_deg), + np.array([1, 2, 0.011, 0.022, 1, 2, 0.011, 0.022]), + "{0} [{1}]".format(seq[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], "deg"), + "Data [km]", tuple(seq_axis1_lim_deg), (min([min((cube.data * cube.unit).to(u.km).value) for cube in seq_with_units[:, :, 0, 0].data]), max([max((cube.data * cube.unit).to(u.km).value) @@ -288,32 +290,32 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): (seq_with_uncertainty[:, :, 0, 0], {}, (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848]), - np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), - "{0} [{1}]".format( - seq_with_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], - "deg"), - "Data [None]", tuple(seq_axis1_lim_deg), + np.array([1, 2, 11, 22, 1, 2, 11, 22]), + "{0} [{1}]".format( + seq_with_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[ + common_axis], "deg"), + "Data [None]", tuple(seq_axis1_lim_deg), (min([cube.data.min() for cube in seq_with_uncertainty[:, :, 0, 0].data]), max([cube.data.max() for cube in seq_with_uncertainty[:, :, 0, 0].data])))), (seq_with_some_uncertainty[:, :, 0, 0], {}, (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848]), - np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), - "{0} [{1}]".format( - seq_with_some_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], - "deg"), - "Data [None]", tuple(seq_axis1_lim_deg), + np.array([1, 2, 11, 22, 1, 2, 11, 22]), + "{0} [{1}]".format( + seq_with_some_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[ + common_axis], "deg"), + "Data [None]", tuple(seq_axis1_lim_deg), (min([cube.data.min() for cube in seq_with_some_uncertainty[:, :, 0, 0].data]), max([cube.data.max() for cube in seq_with_some_uncertainty[:, :, 0, 0].data])))), (seq_with_units_and_uncertainty[:, :, 0, 0], {}, (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848]), - np.array([ 1, 2, 0.011, 0.022, 1, 2, 0.011, 0.022]), + np.array([1, 2, 0.011, 0.022, 1, 2, 0.011, 0.022]), "{0} [{1}]".format( - seq_with_units_and_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], - "deg"), + seq_with_units_and_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[ + common_axis], "deg"), "Data [km]", tuple(seq_axis1_lim_deg), (min([min((cube.data * cube.unit).to(u.km).value) for cube in seq_with_units[:, :, 0, 0].data]), @@ -323,10 +325,10 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): (seq_with_units_and_some_uncertainty[:, :, 0, 0], {}, (np.array([0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848, 0.49998731, 0.99989848]), - np.array([ 1, 2, 0.011, 0.022, 1, 2, 0.011, 0.022]), + np.array([1, 2, 0.011, 0.022, 1, 2, 0.011, 0.022]), "{0} [{1}]".format( - seq_with_units_and_some_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[common_axis], - "deg"), + seq_with_units_and_some_uncertainty[:, :, 0, 0].cube_like_world_axis_physical_types[ + common_axis], "deg"), "Data [km]", tuple(seq_axis1_lim_deg), (min([min((cube.data * cube.unit).to(u.km).value) for cube in seq_with_units[:, :, 0, 0].data]), @@ -335,26 +337,25 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): (seq[:, :, 0, 0], {"axes_coordinates": "pix"}, (seq[:, :, 0, 0].common_axis_extra_coords["pix"].value, - np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), - "pix [pix]", "Data [None]", - (min(seq[:, :, 0, 0].common_axis_extra_coords["pix"].value), - max(seq[:, :, 0, 0].common_axis_extra_coords["pix"].value)), + np.array([1, 2, 11, 22, 1, 2, 11, 22]), "pix [pix]", "Data [None]", + (min(seq[:, :, 0, 0].common_axis_extra_coords["pix"].value), + max(seq[:, :, 0, 0].common_axis_extra_coords["pix"].value)), (min([cube.data.min() for cube in seq[:, :, 0, 0].data]), max([cube.data.max() for cube in seq[:, :, 0, 0].data])))), (seq[:, :, 0, 0], {"axes_coordinates": np.arange(10, 10+seq[:, :, 0, 0].cube_like_dimensions[0].value)}, - (np.arange(10, 10+seq[:, :, 0, 0].cube_like_dimensions[0].value), - np.array([ 1, 2, 11, 22, 1, 2, 11, 22]), - "{0} [{1}]".format("", None), - "Data [None]", (10, 10 + seq[:, :, 0, 0].cube_like_dimensions[0].value - 1), + (np.arange(10, 10 + seq[:, :, 0, 0].cube_like_dimensions[0].value), + np.array([1, 2, 11, 22, 1, 2, 11, 22]), + "{0} [{1}]".format("", None), "Data [None]", + (10, 10 + seq[:, :, 0, 0].cube_like_dimensions[0].value - 1), (min([cube.data.min() for cube in seq[:, :, 0, 0].data]), max([cube.data.max() for cube in seq[:, :, 0, 0].data])))) ]) def test_sequence_plot_as_cube_1D_plot(test_input, test_kwargs, expected_values): # Unpack expected values expected_x_data, expected_y_data, expected_xlabel, expected_ylabel, \ - expected_xlim, expected_ylim = expected_values + expected_xlim, expected_ylim = expected_values # Run plot method output = test_input.plot_as_cube(**test_kwargs) # Check values are correct @@ -402,7 +403,9 @@ def test_sequence_plot_as_cube_error(): (min(seq[0, :, 0, 0].extra_coords["pix"]["value"].value), max(seq[0, :, 0, 0].extra_coords["pix"]["value"].value), min(seq[:, :, 0, 0].sequence_axis_extra_coords["distance"].value), - max(seq[:, :, 0, 0].sequence_axis_extra_coords["distance"].value)))), ## This shows weakness of current extra coord axis values on 2D plotting! + max(seq[:, :, 0, 0].sequence_axis_extra_coords["distance"].value)))), + # This example shows weakness of current extra coord axis values on 2D plotting! + # Only the coordinates from the first cube are shown. (seq[:, :, 0, 0], {"axes_coordinates": [np.arange( 10, 10+seq[:, :, 0, 0].dimensions[-1].value), "distance"], "axes_units": [None, u.m]}, @@ -416,9 +419,9 @@ def test_sequence_plot_as_cube_error(): 10, 10+seq[:, :, 0, 0].dimensions[-1].value)*u.deg, None], "axes_units": [u.arcsec, None]}, (seq_stack[:, :, 0, 0], " [arcsec]", "meta.obs.sequence [None]", - tuple( - list((np.arange(10, 10+seq[:, :, 0, 0].dimensions[-1].value)*u.deg).to(u.arcsec).value) + \ - [0, len(seq.data)-1]))) + tuple(list( + (np.arange(10, 10+seq[:, :, 0, 0].dimensions[-1].value)*u.deg).to(u.arcsec).value) \ + + [0, len(seq.data)-1]))) ]) def test_sequence_plot_2D_image(test_input, test_kwargs, expected_values): # Unpack expected values @@ -507,6 +510,7 @@ def test_sequence_plot_as_cube_2D_image_errors(test_input, test_kwargs, expected with pytest.raises(expected_error): output = test_input.plot_as_cube(**test_kwargs) + @pytest.mark.parametrize("test_input, test_kwargs, expected_data", [ (seq, {}, seq_stack.reshape(4, 1, 2, 3, 4)), (seq_with_units, {}, seq_stack_km.reshape(4, 1, 2, 3, 4)) @@ -548,6 +552,7 @@ def test_determine_sequence_units(): output_seq_unit, output_unit = ndcube.mixins.sequence_plotting._determine_sequence_units( seq.data, u.m) + @pytest.mark.parametrize("test_input, expected", [ ((3, 1, "time", u.s), ([1], [None, 'time', None], [None, u.s, None])), ((3, None, None, None), ([-1, -2], None, None))]) From 4eb102c4242afa87352abe88a6c673684ddf32e8 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Wed, 28 Mar 2018 11:09:12 -0400 Subject: [PATCH 28/30] Add matplotlib backend to travis for testing plots. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 284cfe99c..67237df32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,6 +54,9 @@ matrix: include: + # Add a matploblib backend for generating plots in tests. + - env: MPLBACKEND='Agg' + # Do a basic test. - os: linux stage: Initial tests From f25e39deccd7d14c691ff08be091b649820c0212 Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Wed, 28 Mar 2018 12:35:31 -0400 Subject: [PATCH 29/30] Add description fo return object in NDCubeSequencePlotMixin.plot and plot_as_cube docstrings and add type checks in their tests. --- ndcube/mixins/sequence_plotting.py | 6 ++++-- ndcube/tests/test_sequence_plotting.py | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index fc9936845..33ce4aa7e 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -83,7 +83,8 @@ def plot(self, axes=None, plot_axis_indices=None, Returns ------- - ax: ??? + ax: `matplotlib.axes.Axes`, `ndcube.mixins.sequence_plotting.ImageAnimatorNDCubeSequence` or `ndcube.mixins.sequence_plotting.ImageAnimatorCubeLikeNDCubeSequence` # noqa + Axes or animation object depending on dimensionality of NDCubeSequence """ # Check kwargs are in consistent formats and set default values if not done so by user. @@ -183,7 +184,8 @@ def plot_as_cube(self, axes=None, plot_axis_indices=None, Returns ------- - ax: ??? + ax: ax: `matplotlib.axes.Axes`, `ndcube.mixins.sequence_plotting.ImageAnimatorNDCubeSequence` or `ndcube.mixins.sequence_plotting.ImageAnimatorCubeLikeNDCubeSequence` # noqa + Axes or animation object depending on dimensionality of NDCubeSequence """ # Verify common axis is set. diff --git a/ndcube/tests/test_sequence_plotting.py b/ndcube/tests/test_sequence_plotting.py index 0382c8b66..be0191cde 100644 --- a/ndcube/tests/test_sequence_plotting.py +++ b/ndcube/tests/test_sequence_plotting.py @@ -253,7 +253,7 @@ def test_sequence_plot_1D_plot(test_input, test_kwargs, expected_values): # Run plot method output = test_input.plot(**test_kwargs) # Check values are correct - #assert isinstance(output, matplotlib.axes._subplots.AxesSubplot) + assert isinstance(output, matplotlib.axes.Axes) np.testing.assert_array_equal(output.lines[0].get_xdata(), expected_x_data) np.testing.assert_array_equal(output.lines[0].get_ydata(), expected_y_data) assert output.axes.get_xlabel() == expected_xlabel @@ -360,7 +360,7 @@ def test_sequence_plot_as_cube_1D_plot(test_input, test_kwargs, expected_values) output = test_input.plot_as_cube(**test_kwargs) # Check values are correct # Check type of ouput plot object - #assert isinstance(output, matplotlib.axes._subplots.AxesSubplot) + assert isinstance(output, matplotlib.axes.Axes) # Check x and y data are correct. assert np.allclose(output.lines[0].get_xdata(), expected_x_data) assert np.allclose(output.lines[0].get_ydata(), expected_y_data) @@ -429,6 +429,7 @@ def test_sequence_plot_2D_image(test_input, test_kwargs, expected_values): # Run plot method output = test_input.plot(**test_kwargs) # Check values are correct + assert isinstance(output, matplotlib.axes.Axes) np.testing.assert_array_equal(output.images[0].get_array(), expected_data) assert output.xaxis.get_label_text() == expected_xlabel assert output.yaxis.get_label_text() == expected_ylabel @@ -490,6 +491,7 @@ def test_sequence_plot_as_cube_2D_image(test_input, test_kwargs, expected_values # Run plot method output = test_input.plot_as_cube(**test_kwargs) # Check values are correct + assert isinstance(output, matplotlib.axes.Axes) np.testing.assert_array_equal(output.images[0].get_array(), expected_data) assert output.xaxis.get_label_text() == expected_xlabel assert output.yaxis.get_label_text() == expected_ylabel @@ -519,7 +521,7 @@ def test_sequence_plot_ImageAnimator(test_input, test_kwargs, expected_data): # Run plot method output = test_input.plot(**test_kwargs) # Check plot object properties are correct. - assert type(output) == ndcube.mixins.sequence_plotting.ImageAnimatorNDCubeSequence + assert isinstance(output, ndcube.mixins.sequence_plotting.ImageAnimatorNDCubeSequence) np.testing.assert_array_equal(output.data, expected_data) @@ -531,7 +533,7 @@ def test_sequence_plot_as_cube_ImageAnimator(test_input, test_kwargs, expected_d # Run plot method output = test_input.plot_as_cube(**test_kwargs) # Check plot object properties are correct. - assert type(output) == ndcube.mixins.sequence_plotting.ImageAnimatorCubeLikeNDCubeSequence + assert isinstance(output, ndcube.mixins.sequence_plotting.ImageAnimatorCubeLikeNDCubeSequence) np.testing.assert_array_equal(output.data, expected_data) From d629ec8e34ddda4d68f949035ba8943f1374d5af Mon Sep 17 00:00:00 2001 From: DanRyanIrish Date: Wed, 28 Mar 2018 17:34:58 -0400 Subject: [PATCH 30/30] Set matplotlib backend for testing. --- .travis.yml | 13 +++++++++---- ndcube/mixins/sequence_plotting.py | 4 ++-- ndcube/tests/test_sequence_plotting.py | 3 +++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 67237df32..b0e33ae37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,8 +54,8 @@ matrix: include: - # Add a matploblib backend for generating plots in tests. - - env: MPLBACKEND='Agg' + # Add a matplotlib backend for generating plots in tests. + - env: MPLBACKEND='Agg' # Do a basic test. - os: linux @@ -102,8 +102,13 @@ install: - git clone git://github.com/astropy/ci-helpers.git - source ci-helpers/travis/setup_conda.sh +before_script: + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + - sleep 3 # give xvfb some time to start + script: - - $MAIN_CMD $SETUP_CMD + - $MAIN_CMD $SETUP_CMD after_success: - - if [[ $SETUP_CMD = *"cov"* ]]; then pip install codecov; codecov; fi + - if [[ $SETUP_CMD = *"cov"* ]]; then pip install codecov; codecov; fi diff --git a/ndcube/mixins/sequence_plotting.py b/ndcube/mixins/sequence_plotting.py index 33ce4aa7e..f304dd403 100644 --- a/ndcube/mixins/sequence_plotting.py +++ b/ndcube/mixins/sequence_plotting.py @@ -83,7 +83,7 @@ def plot(self, axes=None, plot_axis_indices=None, Returns ------- - ax: `matplotlib.axes.Axes`, `ndcube.mixins.sequence_plotting.ImageAnimatorNDCubeSequence` or `ndcube.mixins.sequence_plotting.ImageAnimatorCubeLikeNDCubeSequence` # noqa + ax: `matplotlib.axes.Axes`, `ndcube.mixins.sequence_plotting.ImageAnimatorNDCubeSequence` or `ndcube.mixins.sequence_plotting.ImageAnimatorCubeLikeNDCubeSequence` Axes or animation object depending on dimensionality of NDCubeSequence """ @@ -184,7 +184,7 @@ def plot_as_cube(self, axes=None, plot_axis_indices=None, Returns ------- - ax: ax: `matplotlib.axes.Axes`, `ndcube.mixins.sequence_plotting.ImageAnimatorNDCubeSequence` or `ndcube.mixins.sequence_plotting.ImageAnimatorCubeLikeNDCubeSequence` # noqa + ax: ax: `matplotlib.axes.Axes`, `ndcube.mixins.sequence_plotting.ImageAnimatorNDCubeSequence` or `ndcube.mixins.sequence_plotting.ImageAnimatorCubeLikeNDCubeSequence` Axes or animation object depending on dimensionality of NDCubeSequence """ diff --git a/ndcube/tests/test_sequence_plotting.py b/ndcube/tests/test_sequence_plotting.py index be0191cde..b85e5ca2b 100644 --- a/ndcube/tests/test_sequence_plotting.py +++ b/ndcube/tests/test_sequence_plotting.py @@ -10,6 +10,9 @@ from ndcube.utils.wcs import WCS import ndcube.mixins.sequence_plotting +# Set matplotlib display for testing +#matplotlib.use('Agg') + # sample data for tests # TODO: use a fixture reading from a test file. file TBD. data = np.array([[[1, 2, 3, 4], [2, 4, 5, 3], [0, -1, 2, 3]],