diff --git a/.cruft.json b/.cruft.json index 373ecde13..48d4b0ea5 100644 --- a/.cruft.json +++ b/.cruft.json @@ -1,6 +1,6 @@ { "template": "https://github.com/sunpy/package-template", - "commit": "7b0225e0a206b7b6249752991334dea7e9ffcfd4", + "commit": "51fb616094a4d7577c8898445aa50effb89afa31", "checkout": null, "context": { "cookiecutter": { @@ -16,7 +16,8 @@ "enable_dynamic_dev_versions": "y", "include_example_code": "n", "include_cruft_update_github_workflow": "y", - "_sphinx_theme": "alabaster", + "use_extended_ruff_linting": "y", + "_sphinx_theme": "sunpy", "_parent_project": "", "_install_requires": "", "_copy_without_render": [ diff --git a/.github/workflows/label_sync.yml b/.github/workflows/label_sync.yml new file mode 100644 index 000000000..7f2177583 --- /dev/null +++ b/.github/workflows/label_sync.yml @@ -0,0 +1,23 @@ +name: Label Sync +on: + workflow_dispatch: + schedule: + # ┌───────── minute (0 - 59) + # │ ┌───────── hour (0 - 23) + # │ │ ┌───────── day of the month (1 - 31) + # │ │ │ ┌───────── month (1 - 12 or JAN-DEC) + # │ │ │ │ ┌───────── day of the week (0 - 6 or SUN-SAT) + - cron: '0 0 * * *' # run every day at midnight UTC + +# Give permissions to write issue labels +permissions: + issues: write + +jobs: + label_sync: + runs-on: ubuntu-latest + name: Label Sync + steps: + - uses: srealmoreno/label-sync-action@850ba5cef2b25e56c6c420c4feed0319294682fd + with: + config-file: https://raw.githubusercontent.com/sunpy/.github/main/labels.yml diff --git a/.github/workflows/sub_package_update.yml b/.github/workflows/sub_package_update.yml index 74558476a..0b657f241 100644 --- a/.github/workflows/sub_package_update.yml +++ b/.github/workflows/sub_package_update.yml @@ -21,14 +21,6 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: true - matrix: - include: - - add-paths: . - body: apply the changes to this repo. - branch: cruft/update - commit-message: "Automatic package template update" - title: Updates from the package template - steps: - uses: actions/checkout@v4 @@ -55,25 +47,47 @@ jobs: echo "has_changes=$CHANGES" >> "$GITHUB_OUTPUT" - name: Run update if available + id: cruft_update if: steps.check.outputs.has_changes == '1' run: | git config --global user.email "${{ github.actor }}@users.noreply.github.com" git config --global user.name "${{ github.actor }}" - cruft update --skip-apply-ask --refresh-private-variables + cruft_output=$(cruft update --skip-apply-ask --refresh-private-variables) + echo $cruft_output git restore --staged . - - name: Create pull request + if [[ "$cruft_output" == *"Failed to cleanly apply the update, there may be merge conflicts."* ]]; then + echo merge_conflicts=1 >> $GITHUB_OUTPUT + else + echo merge_conflicts=0 >> $GITHUB_OUTPUT + fi + + - name: Check if only .cruft.json is modified + id: cruft_json if: steps.check.outputs.has_changes == '1' + run: | + git status --porcelain=1 + if [[ "$(git status --porcelain=1)" == " M .cruft.json" ]]; then + echo "Only .cruft.json is modified. Exiting workflow early." + echo "has_changes=0" >> "$GITHUB_OUTPUT" + else + echo "has_changes=1" >> "$GITHUB_OUTPUT" + fi + + - name: Create pull request + if: steps.cruft_json.outputs.has_changes == '1' uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} - add-paths: ${{ matrix.add-paths }} - commit-message: ${{ matrix.commit-message }} - branch: ${{ matrix.branch }} + add-paths: "." + commit-message: "Automatic package template update" + branch: "cruft/update" delete-branch: true - branch-suffix: timestamp - title: ${{ matrix.title }} + draft: ${{ steps.cruft_update.outputs.merge_conflicts == '1' }} + title: "Updates from the package template" body: | - This is an autogenerated PR, which will ${{ matrix.body }}. - [Cruft](https://cruft.github.io/cruft/) has detected updates from the Package Template + This is an autogenerated PR, which will applies the latest changes from the [SunPy Package Template](https://github.com/sunpy/package-template). + If this pull request has been opened as a draft there are conflicts which need fixing. + + **To run the CI on this pull request you will need to close it and reopen it.** diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 116cd8868..9a6cbc5e9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: # This should be before any formatting hooks like isort - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.6.9" + rev: "v0.7.2" hooks: - id: ruff args: ["--fix"] diff --git a/.ruff.toml b/.ruff.toml index ea73580e8..3432435f2 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,5 +1,5 @@ target-version = "py310" -line-length = 110 +line-length = 120 exclude = [ ".git,", "__pycache__", @@ -12,34 +12,75 @@ select = [ "E", "F", "W", - #"UP", - #"PT" + "UP", + "PT", + "BLE", + "A", + "C4", + "INP", + "PIE", + "T20", + "RET", + "TID", + "PTH", + "PD", + "PLC", + "PLE", + "FLY", + "NPY", + "PERF", + "RUF", ] extend-ignore = [ - "E712", - "E721", # pycodestyle (E, W) - "E501", # LineTooLong # TODO! fix + "E501", # ignore line length will use a formatter instead + # pyupgrade (UP) + "UP038", # Use | in isinstance - not compatible with models and is slower + # numpy + "NPY002", # TODO: migrate from np.random.rand to np.random.Generator # pytest (PT) "PT001", # Always use pytest.fixture() "PT004", # Fixtures which don't return anything should have leading _ - "PT007", # Parametrize should be lists of tuples # TODO! fix - "PT011", # Too broad exception assert # TODO! fix + "PT011", # TODO: except(ValueRaises) is too broad + "PT012", # TODO: except statement is too lengthy "PT023", # Always use () on pytest decorators + # flake8-pie (PIE) + "PIE808", # Disallow passing 0 as the first argument to range + # flake8-use-pathlib (PTH) + "PTH123", # open() should be replaced by Path.open() + # Ruff (RUF) + "RUF003", # Ignore ambiguous quote marks, doesn't allow ' in comments + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + "RUF013", # PEP 484 prohibits implicit `Optional` + "RUF015", # Prefer `next(iter(...))` over single element slice ] [lint.per-file-ignores] -# Part of configuration, not a package. -"setup.py" = ["INP001"] -"conftest.py" = ["INP001"] +"setup.py" = [ + "INP001", # File is part of an implicit namespace package. +] +"conftest.py" = [ + "INP001", # File is part of an implicit namespace package. +] "docs/conf.py" = [ - "E402" # Module imports not at top of file + "E402" # Module imports not at top of file ] "docs/*.py" = [ - "INP001", # Implicit-namespace-package. The examples are not a package. + "INP001", # File is part of an implicit namespace package. +] +"examples/**.py" = [ + "T201", # allow use of print in examples + "INP001", # File is part of an implicit namespace package. +] +"__init__.py" = [ + "E402", # Module level import not at top of cell + "F401", # Unused import + "F403", # from {name} import * used; unable to detect undefined names + "F405", # {name} may be undefined, or defined from star imports +] +"test_*.py" = [ + "E402", # Module level import not at top of cell ] -"__init__.py" = ["E402", "F401", "F403"] -"test_*.py" = ["B011", "D", "E402", "PGH001", "S101"] [lint.pydocstyle] convention = "numpy" diff --git a/docs/conf.py b/docs/conf.py index 30b2b3b05..148300d55 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,6 +3,7 @@ import os import warnings import datetime +from pathlib import Path from astropy.utils.exceptions import AstropyDeprecationWarning from matplotlib import MatplotlibDeprecationWarning @@ -58,7 +59,7 @@ ] # Add any paths that contain templates here, relative to this directory. -# templates_path = ["_templates"] # NOQA: ERA001 +# templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -116,7 +117,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ["_static"] # NOQA: ERA001 +# html_static_path = ["_static"] # By default, when rendering docstrings for classes, sphinx.ext.autodoc will # make docs with the class-level docstring and the class-method docstrings, @@ -146,11 +147,11 @@ # -- Sphinx Gallery --------------------------------------------------------- sphinx_gallery_conf = { - 'backreferences_dir': os.path.join('generated', 'modules'), + 'backreferences_dir': Path('generated/modules'), 'filename_pattern': '^((?!skip_).)*$', - 'examples_dirs': os.path.join('..', 'examples'), + 'examples_dirs': Path('../examples'), 'within_subsection_order': "ExampleTitleSortKey", - 'gallery_dirs': os.path.join('generated', 'gallery'), + 'gallery_dirs': Path('generated/gallery'), 'matplotlib_animations': True, "default_thumb_file": png_icon, 'abort_on_example_error': False, diff --git a/examples/creating_even_spaced_wavelength_visualisation.py b/examples/creating_even_spaced_wavelength_visualisation.py index 8ea953828..995fdd545 100644 --- a/examples/creating_even_spaced_wavelength_visualisation.py +++ b/examples/creating_even_spaced_wavelength_visualisation.py @@ -34,7 +34,7 @@ # `sequence=True` causes a sequence of maps to be returned, one for each image file. sequence_of_maps = sunpy.map.Map(aia_files, sequence=True) # Sort the maps in the sequence in order of wavelength. -sequence_of_maps.maps = list(sorted(sequence_of_maps.maps, key=lambda m: m.wavelength)) +sequence_of_maps.maps = sorted(sequence_of_maps.maps, key=lambda m: m.wavelength) ############################################################################# # Using an `astropy.units.Quantity` of the wavelengths of the images, we can construct diff --git a/ndcube/__init__.py b/ndcube/__init__.py index 74e39392d..7e52b7945 100644 --- a/ndcube/__init__.py +++ b/ndcube/__init__.py @@ -1,8 +1,9 @@ """ +====== ndcube ====== -A base package for multi-dimensional contiguous and non-contiguous coordinate-aware arrays. +A package for multi-dimensional contiguous and non-contiguous coordinate-aware arrays. * Homepage: https://github.com/sunpy/ndcube * Documentation: https://docs.sunpy.org/projects/ndcube/ @@ -14,4 +15,16 @@ from .ndcube_sequence import NDCubeSequence, NDCubeSequenceBase from .version import version as __version__ -__all__ = ['NDCube', 'NDCubeSequence', "NDCollection", "ExtraCoords", "GlobalCoords", "ExtraCoordsABC", "GlobalCoordsABC", "NDCubeBase", "NDCubeSequenceBase", "__version__"] + +__all__ = [ + 'NDCube', + 'NDCubeSequence', + "NDCollection", + "ExtraCoords", + "GlobalCoords", + "ExtraCoordsABC", + "GlobalCoordsABC", + "NDCubeBase", + "NDCubeSequenceBase", + "__version__", +] diff --git a/ndcube/_dev/scm_version.py b/ndcube/_dev/scm_version.py index 1bcf0dd99..988debf51 100644 --- a/ndcube/_dev/scm_version.py +++ b/ndcube/_dev/scm_version.py @@ -1,11 +1,11 @@ # Try to use setuptools_scm to get the current version; this is only used # in development installations from the git repository. -import os.path +from pathlib import Path try: from setuptools_scm import get_version - version = get_version(root=os.path.join('..', '..'), relative_to=__file__) + version = get_version(root=Path('../..'), relative_to=__file__) except ImportError: raise except Exception as e: diff --git a/ndcube/conftest.py b/ndcube/conftest.py index 130941e6a..ad34271d0 100644 --- a/ndcube/conftest.py +++ b/ndcube/conftest.py @@ -479,8 +479,7 @@ def ndcube_2d_ln_lt_uncert(wcs_2d_lt_ln): shape = (10, 12) data_cube = data_nd(shape) uncertainty = astropy.nddata.StdDevUncertainty(data_cube * 0.1) - cube = NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty) - return cube + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty) @pytest.fixture @@ -493,8 +492,7 @@ def ndcube_2d_ln_lt_mask_uncert(wcs_2d_lt_ln): mask[2, 0] = True mask[3, 3] = True mask[4:6, :4] = True - cube = NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask) - return cube + return NDCube(data_cube, wcs=wcs_2d_lt_ln, uncertainty=uncertainty, mask=mask) @pytest.fixture diff --git a/ndcube/extra_coords/extra_coords.py b/ndcube/extra_coords/extra_coords.py index 87f4de9b7..90ac72c83 100644 --- a/ndcube/extra_coords/extra_coords.py +++ b/ndcube/extra_coords/extra_coords.py @@ -149,8 +149,8 @@ def __init__(self, ndcube=None): # Lookup tables is a list of (pixel_dim, LookupTableCoord) to allow for # one pixel dimension having more than one lookup coord. - self._lookup_tables = list() - self._dropped_tables = list() + self._lookup_tables = [] + self._dropped_tables = [] # We need a reference to the parent NDCube self._ndcube = ndcube @@ -230,8 +230,8 @@ def add(self, name, array_dimension, lookup_table, physical_types=None, **kwargs self._lookup_tables.append((array_dimension, coord)) # Sort the LUTs so that the mapping and the wcs are ordered in pixel dim order - self._lookup_tables = list(sorted(self._lookup_tables, - key=lambda x: x[0] if isinstance(x[0], Integral) else x[0][0])) + self._lookup_tables = sorted(self._lookup_tables, + key=lambda x: x[0] if isinstance(x[0], Integral) else x[0][0]) @property def _name_lut_map(self): @@ -243,7 +243,7 @@ def _name_lut_map(self): def keys(self): # docstring in ABC if not self.wcs: - return tuple() + return () return tuple(self.wcs.world_axis_names) if self.wcs.world_axis_names else None @@ -256,7 +256,7 @@ def mapping(self): # If mapping is not set but lookup_tables is empty then the extra # coords is empty, so there is no mapping. if not self._lookup_tables: - return tuple() + return () # The mapping is from the array index (position in the list) to the # pixel dimensions (numbers in the list) @@ -292,7 +292,7 @@ def wcs(self): if not self._lookup_tables: return None - tcoords = set(lt[1] for lt in self._lookup_tables) + tcoords = {lt[1] for lt in self._lookup_tables} # created a sorted list of unique items _tmp = set() # a temporary set tcoords = [x[1] for x in self._lookup_tables if x[1] not in _tmp and _tmp.add(x[1]) is None] @@ -323,8 +323,7 @@ def is_empty(self): # docstring in ABC if not self._wcs and not self._lookup_tables: return True - else: - return False + return False def _getitem_string(self, item): """ @@ -402,7 +401,7 @@ def __getitem__(self, item): if self._wcs: return self._getitem_wcs(item) - elif self._lookup_tables: + if self._lookup_tables: return self._getitem_lookup_tables(item) # If we get here this object is empty, so just return an empty extra coords @@ -425,7 +424,7 @@ def dropped_world_dimensions(self): return mtc.dropped_world_dimensions - return dict() + return {} def resample(self, factor, offset=0, ndcube=None, **kwargs): """ diff --git a/ndcube/extra_coords/table_coord.py b/ndcube/extra_coords/table_coord.py index 503d6f1c7..5ff887351 100644 --- a/ndcube/extra_coords/table_coord.py +++ b/ndcube/extra_coords/table_coord.py @@ -115,10 +115,10 @@ def _generate_generic_frame(naxes, unit, names=None, physical_types=None): if isinstance(unit, (u.Unit, u.IrreducibleUnit, u.CompositeUnit)): unit = tuple([unit] * naxes) - if all([u.m.is_equivalent(un) for un in unit]): + if all(u.m.is_equivalent(un) for un in unit): axes_type = "SPATIAL" - if all([u.pix.is_equivalent(un) for un in unit]): + if all(u.pix.is_equivalent(un) for un in unit): name = "PixelFrame" axes_type = "PIXEL" @@ -202,7 +202,7 @@ def __init__(self, *tables, mesh=False, names=None, physical_types=None): self.names = names if not isinstance(names, str) else [names] self.physical_types = physical_types if not isinstance(physical_types, str) else [physical_types] self._dropped_world_dimensions = defaultdict(list) - self._dropped_world_dimensions["world_axis_object_classes"] = dict() + self._dropped_world_dimensions["world_axis_object_classes"] = {} @abc.abstractmethod def __getitem__(self, item): @@ -224,9 +224,8 @@ def __str__(self): header = f"{self.__class__.__name__} {self.names or ''} {self.physical_types or '[None]'}:" content = str(self.table).lstrip('(').rstrip(',)') if len(header) + len(content) >= np.get_printoptions()['linewidth']: - return '\n'.join((header, content)) - else: - return ' '.join((header, content)) + return f'{header}\n{content}' + return f'{header} {content}' def __repr__(self): return f"{object.__repr__(self)}\n{self}" @@ -302,9 +301,9 @@ class QuantityTableCoordinate(BaseTableCoordinate): """ def __init__(self, *tables, names=None, physical_types=None): - if not all([isinstance(t, u.Quantity) for t in tables]): + if not all(isinstance(t, u.Quantity) for t in tables): raise TypeError("All tables must be astropy Quantity objects") - if not all([t.unit.is_equivalent(tables[0].unit) for t in tables]): + if not all(t.unit.is_equivalent(tables[0].unit) for t in tables): raise u.UnitsError("All tables must have equivalent units.") ndim = len(tables) dims = np.array([t.ndim for t in tables]) @@ -349,7 +348,7 @@ def _slice_table(self, i, table, item, new_components, whole_slice): dwd["world_axis_physical_types"].append(self.frame.axis_physical_types[i]) dwd["world_axis_units"].append(table.unit.to_string()) dwd["world_axis_object_components"].append((f"quantity{i}", 0, "value")) - dwd["world_axis_object_classes"].update({f"quantity{i}": (u.Quantity, tuple(), {"unit", table.unit.to_string()})}) + dwd["world_axis_object_classes"].update({f"quantity{i}": (u.Quantity, (), {"unit", table.unit.to_string()})}) return new_components["tables"].append(table[item]) @@ -382,7 +381,7 @@ def n_inputs(self): return len(self.table) def is_scalar(self): - return all(t.shape == tuple() for t in self.table) + return all(t.shape == () for t in self.table) @property def frame(self): @@ -516,7 +515,7 @@ def n_inputs(self): return len(self.table.data.components) def is_scalar(self): - return self.table.shape == tuple() + return self.table.shape == () @staticmethod def combine_slices(slice1, slice2): @@ -539,16 +538,15 @@ def __getitem__(self, item): mesh=False, names=self.names, physical_types=self.physical_types) - else: - self._slice = [self.combine_slices(a, b) for a, b in zip(sane_item, self._slice)] - if all([isinstance(s, Integral) for s in self._slice]): - # Here we rebuild the SkyCoord with the slice applied to the individual components. - new_sc = SkyCoord(self.table.realize_frame(type(self.table.data)(*self._sliced_components))) - return type(self)(new_sc, - mesh=False, - names=self.names, - physical_types=self.physical_types) - return self + self._slice = [self.combine_slices(a, b) for a, b in zip(sane_item, self._slice)] + if all(isinstance(s, Integral) for s in self._slice): + # Here we rebuild the SkyCoord with the slice applied to the individual components. + new_sc = SkyCoord(self.table.realize_frame(type(self.table.data)(*self._sliced_components))) + return type(self)(new_sc, + mesh=False, + names=self.names, + physical_types=self.physical_types) + return self @property def frame(self): @@ -558,7 +556,7 @@ def frame(self): sc = self.table components = tuple(getattr(sc.data, comp) for comp in sc.data.components) ref_frame = sc.frame.replicate_without_data() - units = list(c.unit for c in components) + units = [c.unit for c in components] # TODO: Currently this limits you to 2D due to gwcs#120 return cf.CelestialFrame(reference_frame=ref_frame, @@ -590,8 +588,7 @@ def ndim(self): """ if self.mesh: return len(self.table.data.components) - else: - return self.table.ndim + return self.table.ndim @property def shape(self): @@ -605,8 +602,7 @@ def shape(self): """ if self.mesh: return tuple(list(self.table.shape) * self.ndim) - else: - return self.table.shape + return self.table.shape def interpolate(self, *new_array_grids, mesh_output=None, **kwargs): """ @@ -733,7 +729,7 @@ def n_inputs(self): return 1 # The time table has to be one dimensional def is_scalar(self): - return self.table.shape == tuple() + return self.table.shape == () @property def frame(self): @@ -814,7 +810,7 @@ def __init__(self, *table_coordinates): raise TypeError("All arguments must be BaseTableCoordinate instances, such as QuantityTableCoordinate, " "and not instances of MultipleTableCoordinate.") self._table_coords = list(table_coordinates) - self._dropped_coords = list() + self._dropped_coords = [] def __str__(self): classname = self.__class__.__name__ @@ -842,7 +838,7 @@ def __rand__(self, other): if not isinstance(other, BaseTableCoordinate) or isinstance(other, MultipleTableCoordinate): return NotImplemented - return type(self)(*([other] + self._table_coords)) + return type(self)(*([other, *self._table_coords])) def __getitem__(self, item): if isinstance(item, (slice, Integral)): @@ -892,24 +888,23 @@ def frame(self): """ if len(self._table_coords) == 1: return self._table_coords[0].frame - else: - frames = [t.frame for t in self._table_coords] + frames = [t.frame for t in self._table_coords] - # We now have to set the axes_order of all the frames so that we - # have one consistent WCS with the correct number of pixel - # dimensions. - ind = 0 - for f in frames: - new_ind = ind + f.naxes - f._axes_order = tuple(range(ind, new_ind)) - ind = new_ind + # We now have to set the axes_order of all the frames so that we + # have one consistent WCS with the correct number of pixel + # dimensions. + ind = 0 + for f in frames: + new_ind = ind + f.naxes + f._axes_order = tuple(range(ind, new_ind)) + ind = new_ind - return cf.CompositeFrame(frames) + return cf.CompositeFrame(frames) @property def dropped_world_dimensions(self): dropped_world_dimensions = defaultdict(list) - dropped_world_dimensions["world_axis_object_classes"] = dict() + dropped_world_dimensions["world_axis_object_classes"] = {} # Combine the dicts on the tables with our dict for lutc in self._table_coords: diff --git a/ndcube/extra_coords/tests/__init__.py b/ndcube/extra_coords/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ndcube/extra_coords/tests/test_extra_coords.py b/ndcube/extra_coords/tests/test_extra_coords.py index 485bda918..d688e1da1 100644 --- a/ndcube/extra_coords/tests/test_extra_coords.py +++ b/ndcube/extra_coords/tests/test_extra_coords.py @@ -54,9 +54,9 @@ def test_empty_ec(wcs_1d_l): # Test slice of an empty EC assert ec[0].wcs is None - assert ec.mapping == tuple() + assert ec.mapping == () assert ec.wcs is None - assert ec.keys() == tuple() + assert ec.keys() == () ec.wcs = wcs_1d_l assert ec.wcs is wcs_1d_l @@ -299,7 +299,7 @@ def test_extra_coords_2d_quantity(quantity_2d_lut): # Extra Coords with NDCube def test_add_coord_after_create(time_lut): - ndc = NDCube(np.random.random((10, 10)), wcs=WCS(naxis=2)) + ndc = NDCube(np.random.random_sample((10, 10)), wcs=WCS(naxis=2)) assert isinstance(ndc.extra_coords, ExtraCoords) ndc.extra_coords.add("time", 0, time_lut) @@ -309,7 +309,7 @@ def test_add_coord_after_create(time_lut): def test_combined_wcs(time_lut): - ndc = NDCube(np.random.random((10, 10)), wcs=WCS(naxis=2)) + ndc = NDCube(np.random.random_sample((10, 10)), wcs=WCS(naxis=2)) assert isinstance(ndc.extra_coords, ExtraCoords) ndc.extra_coords.add("time", 0, time_lut) diff --git a/ndcube/global_coords.py b/ndcube/global_coords.py index a15c1facf..8c3c9f215 100644 --- a/ndcube/global_coords.py +++ b/ndcube/global_coords.py @@ -193,7 +193,7 @@ def remove(self, name): @property def physical_types(self): # Docstring in GlobalCoordsABC - return dict((name, value[0]) for name, value in self._all_coords.items()) + return {name: value[0] for name, value in self._all_coords.items()} def filter_by_physical_type(self, physical_type): """ @@ -232,7 +232,7 @@ def __len__(self): def __str__(self): classname = self.__class__.__name__ - elements = [f"{name} {[ptype]}:\n{repr(coord)}" for (name, coord), ptype in + elements = [f"{name} {[ptype]}:\n{coord!r}" for (name, coord), ptype in zip(self.items(), self.physical_types.values())] length = len(classname) + 2 * len(elements) + sum(len(e) for e in elements) if length > np.get_printoptions()['linewidth']: @@ -243,4 +243,4 @@ def __str__(self): return f"{classname}({joiner.join(elements)})" def __repr__(self): - return f"{object.__repr__(self)}\n{str(self)}" + return f"{object.__repr__(self)}\n{self!s}" diff --git a/ndcube/ndcollection.py b/ndcube/ndcollection.py index 30790b5d1..5bcabbb40 100644 --- a/ndcube/ndcollection.py +++ b/ndcube/ndcollection.py @@ -100,7 +100,7 @@ def __str__(self): Aligned physical types: {self.aligned_axis_physical_types}""")) def __repr__(self): - return f"{object.__repr__(self)}\n{str(self)}" + return f"{object.__repr__(self)}\n{self!s}" @property def aligned_dimensions(self): @@ -113,6 +113,7 @@ def aligned_dimensions(self): return np.asanyarray(self[self._first_key].shape, dtype=object)[ np.array(self.aligned_axes[self._first_key]) ] + return None @property def aligned_axis_physical_types(self): @@ -145,40 +146,39 @@ def __getitem__(self, item): return super().__getitem__(item) # If item is not a single string... + # If item is a sequence, ensure strings and numeric items are not mixed. + item_is_strings = False + if isinstance(item, collections.abc.Sequence): + item_strings = [isinstance(item_, str) for item_ in item] + item_is_strings = all(item_strings) + # Ensure strings are not mixed with slices. + if (not item_is_strings) and (not all(np.invert(item_strings))): + raise TypeError("Cannot mix keys and non-keys when indexing instance.") + + # If sequence is all strings, extract the cubes corresponding to the string keys. + if item_is_strings: + new_data = [self[_item] for _item in item] + new_keys = item + new_aligned_axes = tuple([self.aligned_axes[item_] for item_ in item]) + + # Else, the item is assumed to be a typical slicing item. + # Slice each cube in collection using information in this item. + # However, this can only be done if there are aligned axes. else: - # If item is a sequence, ensure strings and numeric items are not mixed. - item_is_strings = False - if isinstance(item, collections.abc.Sequence): - item_strings = [isinstance(item_, str) for item_ in item] - item_is_strings = all(item_strings) - # Ensure strings are not mixed with slices. - if (not item_is_strings) and (not all(np.invert(item_strings))): - raise TypeError("Cannot mix keys and non-keys when indexing instance.") - - # If sequence is all strings, extract the cubes corresponding to the string keys. - if item_is_strings: - new_data = [self[_item] for _item in item] - new_keys = item - new_aligned_axes = tuple([self.aligned_axes[item_] for item_ in item]) - - # Else, the item is assumed to be a typical slicing item. - # Slice each cube in collection using information in this item. - # However, this can only be done if there are aligned axes. - else: - if self.aligned_axes is None: - raise IndexError("Cannot slice unless collection has aligned axes.") - # Derive item to be applied to each cube in collection and - # whether any aligned axes are dropped by the slicing. - collection_items, new_aligned_axes = self._generate_collection_getitems(item) - # Apply those slice items to each cube in collection. - new_data = [self[key][tuple(cube_item)] - for key, cube_item in zip(self, collection_items)] - # Since item is not strings, no cube in collection is dropped. - # Therefore the collection keys remain unchanged. - new_keys = list(self.keys()) - - return self.__class__(list(zip(new_keys, new_data)), aligned_axes=new_aligned_axes, - meta=self.meta, sanitize_inputs=False) + if self.aligned_axes is None: + raise IndexError("Cannot slice unless collection has aligned axes.") + # Derive item to be applied to each cube in collection and + # whether any aligned axes are dropped by the slicing. + collection_items, new_aligned_axes = self._generate_collection_getitems(item) + # Apply those slice items to each cube in collection. + new_data = [self[key][tuple(cube_item)] + for key, cube_item in zip(self, collection_items)] + # Since item is not strings, no cube in collection is dropped. + # Therefore the collection keys remain unchanged. + new_keys = list(self.keys()) + + return self.__class__(list(zip(new_keys, new_data)), aligned_axes=new_aligned_axes, + meta=self.meta, sanitize_inputs=False) def _generate_collection_getitems(self, item): # There are 3 supported cases of the slice item: int, slice, tuple of ints and/or slices. @@ -288,7 +288,8 @@ def update(self, *args): ) # Update collection super().update(key_data_pairs) - if first_old_aligned_axes is not None: # since the above assertion passed, if one aligned axes is not None, both are not None + # since the above assertion passed, if one aligned axes is not None, both are not None + if first_old_aligned_axes is not None: self.aligned_axes.update(new_aligned_axes) def __delitem__(self, key): diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py index 378b91ffd..249bc6ad4 100644 --- a/ndcube/ndcube.py +++ b/ndcube/ndcube.py @@ -309,7 +309,7 @@ def __set_name__(self, owner, name): def __get__(self, obj, objtype=None): if obj is None: - return + return None if getattr(obj, self._attribute_name, None) is None and self._default_type is not None: self.__set__(obj, self._default_type) @@ -488,7 +488,7 @@ def _generate_world_coords(self, pixel_corners, wcs, needed_axes=None, *, units) # First construct a range of pixel indices for this set of coupled dimensions pixel_ranges_subset = [pixel_ranges[idx] for idx in pixel_axes_indices] # Then get a set of non-correlated dimensions - non_corr_axes = set(list(range(wcs.pixel_n_dim))) - set(pixel_axes_indices) + non_corr_axes = set(range(wcs.pixel_n_dim)) - set(pixel_axes_indices) # And inject 0s for those coordinates for idx in non_corr_axes: pixel_ranges_subset.insert(idx, 0) @@ -586,24 +586,23 @@ def _get_crop_item(self, *points, wcs=None, keepdims=False): # Quit out early if we are no-op if no_op: return tuple([slice(None)] * wcs.pixel_n_dim) - else: - comp = [c[0] for c in wcs.world_axis_object_components] - # Trim to unique component names - `np.unique(..., return_index=True) - # keeps sorting alphabetically, set() seems just nondeterministic. - for k, c in enumerate(comp): - if comp.count(c) > 1: - comp.pop(k) - classes = [wcs.world_axis_object_classes[c][0] for c in comp] - for i, point in enumerate(points): - if len(point) != len(comp): - raise ValueError(f"{len(point)} components in point {i} do not match " - f"WCS with {len(comp)} components.") - for j, value in enumerate(point): - if not (value is None or isinstance(value, classes[j])): - raise TypeError(f"{type(value)} of component {j} in point {i} is " - f"incompatible with WCS component {comp[j]} " - f"{classes[j]}.") - return utils.cube.get_crop_item_from_points(points, wcs, False, keepdims=keepdims) + comp = [c[0] for c in wcs.world_axis_object_components] + # Trim to unique component names - `np.unique(..., return_index=True) + # keeps sorting alphabetically, set() seems just nondeterministic. + for k, c in enumerate(comp): + if comp.count(c) > 1: + comp.pop(k) + classes = [wcs.world_axis_object_classes[c][0] for c in comp] + for i, point in enumerate(points): + if len(point) != len(comp): + raise ValueError(f"{len(point)} components in point {i} do not match " + f"WCS with {len(comp)} components.") + for j, value in enumerate(point): + if not (value is None or isinstance(value, classes[j])): + raise TypeError(f"{type(value)} of component {j} in point {i} is " + f"incompatible with WCS component {comp[j]} " + f"{classes[j]}.") + return utils.cube.get_crop_item_from_points(points, wcs, False, keepdims=keepdims) def crop_by_values(self, *points, units=None, wcs=None, keepdims=False): # The docstring is defined in NDCubeABC @@ -657,7 +656,7 @@ def __str__(self): Data Type: {self.data.dtype}""") def __repr__(self): - return f"{object.__repr__(self)}\n{str(self)}" + return f"{object.__repr__(self)}\n{self!s}" def explode_along_axis(self, axis): """ @@ -692,7 +691,12 @@ def explode_along_axis(self, axis): # Creating a new NDCubeSequence with the result_cubes and common axis as axis return NDCubeSequence(result_cubes, meta=self.meta) - def reproject_to(self, target_wcs, algorithm='interpolation', shape_out=None, return_footprint=False, **reproject_args): + def reproject_to(self, + target_wcs, + algorithm='interpolation', + shape_out=None, + return_footprint=False, + **reproject_args): """ Reprojects the instance to the coordinates described by another WCS object. @@ -726,7 +730,8 @@ def reproject_to(self, target_wcs, algorithm='interpolation', shape_out=None, re Returns ------- reprojected_cube : `ndcube.NDCube` - A new resultant NDCube object, the supplied ``target_wcs`` will be the ``.wcs`` attribute of the output `~ndcube.NDCube`. + A new resultant NDCube object, the supplied ``target_wcs`` will be + the ``.wcs`` attribute of the output `~ndcube.NDCube`. footprint: `numpy.ndarray` Footprint of the input array in the output array. @@ -749,7 +754,8 @@ def reproject_to(self, target_wcs, algorithm='interpolation', shape_out=None, re from reproject import reproject_adaptive, reproject_exact, reproject_interp from reproject.wcs_utils import has_celestial except ModuleNotFoundError: - raise ImportError(f"The {type(self).__name__}.reproject_to method requires the `reproject` library to be installed.") + raise ImportError(f"The {type(self).__name__}.reproject_to method requires " + f"the `reproject` library to be installed.") algorithms = { "interpolation": reproject_interp, @@ -872,14 +878,13 @@ class NDCube(NDCubeBase): def _as_mpl_axes(self): if hasattr(self.plotter, "_as_mpl_axes"): return self.plotter._as_mpl_axes() - else: - warn_user(f"The current plotter {self.plotter} does not have a '_as_mpl_axes' method. " - "The default MatplotlibPlotter._as_mpl_axes method will be used instead.") + warn_user(f"The current plotter {self.plotter} does not have a '_as_mpl_axes' method. " + "The default MatplotlibPlotter._as_mpl_axes method will be used instead.") - from ndcube.visualization.mpl_plotter import MatplotlibPlotter + from ndcube.visualization.mpl_plotter import MatplotlibPlotter - plotter = MatplotlibPlotter(self) - return plotter._as_mpl_axes() + plotter = MatplotlibPlotter(self) + return plotter._as_mpl_axes() def plot(self, *args, **kwargs): """ @@ -962,8 +967,7 @@ def __mul__(self, value): new_data = self.data * value new_uncertainty = (type(self.uncertainty)(self.uncertainty.array * value) if self.uncertainty is not None else None) - new_cube = self._new_instance(data=new_data, unit=new_unit, uncertainty=new_uncertainty) - return new_cube + return self._new_instance(data=new_data, unit=new_unit, uncertainty=new_uncertainty) def __rmul__(self, value): return self.__mul__(value) @@ -985,7 +989,8 @@ def __pow__(self, value): except ValueError as e: if "unsupported operation" in e.args[0]: new_uncertainty = None - warn_user(f"{type(self.uncertainty)} does not support propagation of uncertainties for power. Setting uncertainties to None.") + warn_user(f"{type(self.uncertainty)} does not support propagation of uncertainties for power. " + f"Setting uncertainties to None.") elif "does not support uncertainty propagation" in e.args[0]: new_uncertainty = None warn_user(f"{e.args[0]} Setting uncertainties to None.") @@ -1184,7 +1189,7 @@ def my_propagate(uncertainty, data, mask, **kwargs): warn_user("Uncertainties cannot be propagated as there are no uncertainties, " "i.e., the `uncertainty` keyword was never set on creation of this NDCube.") elif isinstance(self.uncertainty, astropy.nddata.UnknownUncertainty): - warn_user("The uncertainty on this NDCube has no known way to propagate forward and so will be dropped. " + warn_user("The uncertainty on this NDCube has no known way to propagate forward and so will be dropped." "To create an uncertainty that can propagate, please see " "https://docs.astropy.org/en/stable/uncertainty/index.html") elif (not operation_ignores_mask @@ -1203,7 +1208,7 @@ def my_propagate(uncertainty, data, mask, **kwargs): # in each bin can be iterated (all bins being treated in parallel) and # their uncertainties propagated. bin_size = bin_shape.prod() - flat_shape = [bin_size] + list(new_shape) + flat_shape = [bin_size, *list(new_shape)] dummy_axes = tuple(range(1, len(reshape), 2)) flat_data = np.moveaxis(reshaped_data, dummy_axes, tuple(range(naxes))) flat_data = flat_data.reshape(flat_shape) @@ -1272,7 +1277,8 @@ def squeeze(self, axis=None): item[axis] = 0 # Scalar NDCubes are not supported, so we raise error as the operation would cause all the axes to be squeezed. if (item == 0).all(): - raise ValueError("All axes are of length 1, therefore we will not squeeze NDCube to become a scalar. Use `axis=` keyword to specify a subset of axes to squeeze.") + raise ValueError("All axes are of length 1, therefore we will not squeeze NDCube to become a scalar. " + "Use `axis=` keyword to specify a subset of axes to squeeze.") return self[tuple(item)] @@ -1280,11 +1286,10 @@ def _create_masked_array_for_rebinning(data, mask, operation_ignores_mask): m = None if (mask is None or mask is False or operation_ignores_mask) else mask if m is None: return data, m + for array_type, masked_type in ARRAY_MASK_MAP.items(): + if isinstance(data, array_type): + break else: - for array_type, masked_type in ARRAY_MASK_MAP.items(): - if isinstance(data, array_type): - break - else: - masked_type = np.ma.masked_array - warn_user("data and mask arrays of different or unrecognized types. Casting them into a numpy masked array.") - return masked_type(data, m), m + masked_type = np.ma.masked_array + warn_user("data and mask arrays of different or unrecognized types. Casting them into a numpy masked array.") + return masked_type(data, m), m diff --git a/ndcube/ndcube_sequence.py b/ndcube/ndcube_sequence.py index 35d541c7c..5df73512c 100644 --- a/ndcube/ndcube_sequence.py +++ b/ndcube/ndcube_sequence.py @@ -56,7 +56,7 @@ def shape(self): @property def _shape(self): - dimensions = [len(self.data)] + list(self.data[0].data.shape) + dimensions = [len(self.data), *list(self.data[0].data.shape)] if len(dimensions) > 1: # If there is a common axis, length of cube's along it may not # be the same. Therefore if the lengths are different, @@ -74,7 +74,7 @@ def array_axis_physical_types(self): """ The physical types associated with each array axis, including the sequence axis. """ - return [("meta.obs.sequence",)] + self.data[0].array_axis_physical_types + return [("meta.obs.sequence",), *self.data[0].array_axis_physical_types] @property def cube_like_dimensions(self): @@ -92,8 +92,7 @@ def cube_like_dimensions(self): else: cube_like_dimensions[self._common_axis] = sum(dimensions[self._common_axis + 1]) # Combine into single Quantity - cube_like_dimensions = u.Quantity(cube_like_dimensions, unit=u.pix) - return cube_like_dimensions + return u.Quantity(cube_like_dimensions, unit=u.pix) @property def cube_like_shape(self): @@ -208,8 +207,8 @@ def sequence_axis_coords(self): # Collect names of global coords common to all cubes. global_names = set.intersection(*[set(cube.global_coords.keys()) for cube in self.data]) # For each coord, combine values from each cube's global coords property. - return dict([(name, [cube.global_coords[name] for cube in self.data]) - for name in global_names]) + return {name: [cube.global_coords[name] for cube in self.data] + for name in global_names} def explode_along_axis(self, axis): """ @@ -396,7 +395,7 @@ def __str__(self): Common Cube Axis: {self._common_axis}""")) def __repr__(self): - return f"{object.__repr__(self)}\n{str(self)}" + return f"{object.__repr__(self)}\n{self!s}" def __len__(self): return len(self.data) @@ -500,7 +499,7 @@ def __getitem__(self, item): # If common axis item is slice(None), result is trivial as common_axis is not changed. if item[common_axis] == slice(None): # Create item for slicing through the default API and slice. - return self.seq[tuple([slice(None)] + item)] + return self.seq[(slice(None), *item)] if isinstance(item[common_axis], numbers.Integral): # If common_axis item is an int or return an NDCube with dimensionality of N-1 sequence_index, common_axis_index = \ @@ -510,25 +509,24 @@ def __getitem__(self, item): cube_item = copy.deepcopy(item) cube_item[common_axis] = common_axis_index return self.seq.data[sequence_index][tuple(cube_item)] - else: - # item can now only be a tuple whose common axis item is a non-None slice object. - # Convert item into iterable of SequenceItems and slice each cube appropriately. - # item for common_axis must always be a slice for every cube, - # even if it is only a length-1 slice. - # Thus NDCubeSequence.index_as_cube can only slice away common axis if - # item is int or item's first item is an int. - # i.e. NDCubeSequence.index_as_cube cannot cause common_axis to become None - # since in all cases where the common_axis is sliced away involve an NDCube - # is returned, not an NDCubeSequence. - # common_axis of returned sequence must be altered if axes in front of it - # are sliced away. - sequence_items = utils.sequence.cube_like_tuple_item_to_sequence_items( - item, common_axis, common_axis_lengths, n_cube_dims) - # Work out new common axis value if axes in front of it are sliced away. - new_common_axis = common_axis - sum([isinstance(i, numbers.Integral) - for i in item[:common_axis]]) - # Copy sequence and alter the data and common axis. - result = type(self.seq)([], meta=self.seq.meta, common_axis=new_common_axis) - result.data = [self.seq.data[sequence_item.sequence_index][sequence_item.cube_item] - for sequence_item in sequence_items] - return result + # item can now only be a tuple whose common axis item is a non-None slice object. + # Convert item into iterable of SequenceItems and slice each cube appropriately. + # item for common_axis must always be a slice for every cube, + # even if it is only a length-1 slice. + # Thus NDCubeSequence.index_as_cube can only slice away common axis if + # item is int or item's first item is an int. + # i.e. NDCubeSequence.index_as_cube cannot cause common_axis to become None + # since in all cases where the common_axis is sliced away involve an NDCube + # is returned, not an NDCubeSequence. + # common_axis of returned sequence must be altered if axes in front of it + # are sliced away. + sequence_items = utils.sequence.cube_like_tuple_item_to_sequence_items( + item, common_axis, common_axis_lengths, n_cube_dims) + # Work out new common axis value if axes in front of it are sliced away. + new_common_axis = common_axis - sum([isinstance(i, numbers.Integral) + for i in item[:common_axis]]) + # Copy sequence and alter the data and common axis. + result = type(self.seq)([], meta=self.seq.meta, common_axis=new_common_axis) + result.data = [self.seq.data[sequence_item.sequence_index][sequence_item.cube_item] + for sequence_item in sequence_items] + return result diff --git a/ndcube/tests/helpers.py b/ndcube/tests/helpers.py index e82215db0..1a35ddf15 100644 --- a/ndcube/tests/helpers.py +++ b/ndcube/tests/helpers.py @@ -20,13 +20,15 @@ from ndcube import NDCube, NDCubeSequence -__all__ = ['figure_test', - 'get_hash_library_name', - 'assert_extra_coords_equal', - 'assert_metas_equal', - 'assert_cubes_equal', - 'assert_cubesequences_equal', - 'assert_wcs_are_equal'] +__all__ = [ + 'figure_test', + 'get_hash_library_name', + 'assert_extra_coords_equal', + 'assert_metas_equal', + 'assert_cubes_equal', + 'assert_cubesequences_equal', + 'assert_wcs_are_equal', +] def get_hash_library_name(): @@ -107,8 +109,8 @@ def assert_cubes_equal(test_input, expected_cube, check_data=True): assert np.all(test_input.shape == expected_cube.shape) assert_metas_equal(test_input.meta, expected_cube.meta) if type(test_input.extra_coords) is not type(expected_cube.extra_coords): - raise AssertionError("NDCube extra_coords not of same type: {0} != {1}".format( - type(test_input.extra_coords), type(expected_cube.extra_coords))) + raise AssertionError(f"NDCube extra_coords not of same type: " + f"{type(test_input.extra_coords)} != {type(expected_cube.extra_coords)}") if test_input.extra_coords is not None: assert_extra_coords_equal(test_input.extra_coords, expected_cube.extra_coords) diff --git a/ndcube/tests/test_global_coords.py b/ndcube/tests/test_global_coords.py index 38453ba47..165bd7622 100644 --- a/ndcube/tests/test_global_coords.py +++ b/ndcube/tests/test_global_coords.py @@ -31,7 +31,7 @@ def test_add(gc): gc.add('name1', 'custom:physical_type1', coord1) gc.add('name2', 'custom:physical_type2', coord2) assert gc.keys() == {'name1', 'name2'} - assert gc.physical_types == dict((('name1', 'custom:physical_type1'), ('name2', 'custom:physical_type2'))) + assert gc.physical_types == {'name1': 'custom:physical_type1', 'name2': 'custom:physical_type2'} def test_remove(gc_coords): @@ -60,7 +60,7 @@ def test_slicing(gc_coords): def test_physical_types(gc_coords): - assert gc_coords.physical_types == dict((('name1', 'custom:physical_type1'), ('name2', 'custom:physical_type2'))) + assert gc_coords.physical_types == {'name1': 'custom:physical_type1', 'name2': 'custom:physical_type2'} def test_len(gc_coords): diff --git a/ndcube/tests/test_ndcollection.py b/ndcube/tests/test_ndcollection.py index 8ec12630f..79a135027 100644 --- a/ndcube/tests/test_ndcollection.py +++ b/ndcube/tests/test_ndcollection.py @@ -42,7 +42,7 @@ seq_collection = NDCollection([("seq0", sequence02), ("seq1", sequence20)], aligned_axes="all") -@pytest.mark.parametrize("item,collection,expected", [ +@pytest.mark.parametrize(('item', 'collection', 'expected'), [ (0, cube_collection, NDCollection([("cube0", cube0[:, 0]), ("cube1", cube1[:, :, 0]), ("cube2", cube2[:, 0])], aligned_axes=((1,), (0,), (1,)))), @@ -81,7 +81,7 @@ def test_collection_slicing(item, collection, expected): helpers.assert_collections_equal(collection[item], expected) -@pytest.mark.parametrize("item,collection,expected", [("cube1", cube_collection, cube1)]) +@pytest.mark.parametrize(('item', 'collection', 'expected'), [("cube1", cube_collection, cube1)]) def test_slice_cube_from_collection(item, collection, expected): helpers.assert_cubes_equal(collection[item], expected) @@ -91,7 +91,7 @@ def test_collection_copy(): helpers.assert_collections_equal(unaligned_collection.copy(), unaligned_collection) -@pytest.mark.parametrize("collection,popped_key,expected_popped,expected_collection", [ +@pytest.mark.parametrize(('collection', 'popped_key', 'expected_popped', 'expected_collection'), [ (cube_collection, "cube0", cube0, NDCollection([("cube1", cube1), ("cube2", cube2)], aligned_axes=aligned_axes[1:])), (unaligned_collection, "cube0", cube0, NDCollection([("cube1", cube1), ("cube2", cube2)]))]) @@ -102,7 +102,7 @@ def test_collection_pop(collection, popped_key, expected_popped, expected_collec helpers.assert_collections_equal(popped_collection, expected_collection) -@pytest.mark.parametrize("collection,key,expected", [ +@pytest.mark.parametrize(('collection', 'key', 'expected'), [ (cube_collection, "cube0", NDCollection([("cube1", cube1), ("cube2", cube2)], aligned_axes=aligned_axes[1:]))]) def test_del_collection(collection, key, expected): @@ -111,7 +111,7 @@ def test_del_collection(collection, key, expected): helpers.assert_collections_equal(del_collection, expected) -@pytest.mark.parametrize("collection,key,data,aligned_axes,expected", [ +@pytest.mark.parametrize(('collection', 'key', 'data', 'aligned_axes', 'expected'), [ (cube_collection, "cube1", cube2, aligned_axes[2], NDCollection( [("cube0", cube0), ("cube1", cube2), ("cube2", cube2)], aligned_axes=((1, 2), (1, 2), (1, 2)))), @@ -144,14 +144,14 @@ def test_collection_update_without_aligned_axes(): helpers.assert_collections_equal(orig_collection, expected) -@pytest.mark.parametrize("collection, expected_aligned_dimensions", [ +@pytest.mark.parametrize(('collection', 'expected_aligned_dimensions'), [ (cube_collection, [4, 5]), (seq_collection, [2, 3, 4, 5])]) def test_aligned_dimensions(collection, expected_aligned_dimensions): assert np.all(collection.aligned_dimensions == expected_aligned_dimensions) -@pytest.mark.parametrize("collection, expected", [ +@pytest.mark.parametrize(('collection', 'expected'), [ (cube_collection, [('custom:pos.helioprojective.lat', 'custom:pos.helioprojective.lon'), ('em.wl',)]), (seq_collection, [('meta.obs.sequence',), @@ -160,7 +160,6 @@ def test_aligned_dimensions(collection, expected_aligned_dimensions): ('em.wl',)])]) def test_aligned_axis_physical_types(collection, expected): output = collection.aligned_axis_physical_types - print(output) assert len(output) == len(expected) for output_axis_types, expect_axis_types in zip(output, expected): assert set(output_axis_types) == set(expect_axis_types) diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py index 81b230886..e1fab7d05 100644 --- a/ndcube/tests/test_ndcube.py +++ b/ndcube/tests/test_ndcube.py @@ -34,18 +34,15 @@ def test_wcs_object(all_ndcubes): assert isinstance(all_ndcubes.wcs, BaseHighLevelWCS) -@pytest.mark.parametrize("ndc, item", - ( +@pytest.mark.parametrize(("ndc", "item"), + [ ("ndcube_3d_ln_lt_l", np.s_[:, :, 0]), ("ndcube_3d_ln_lt_l", np.s_[..., 0]), ("ndcube_3d_ln_lt_l", np.s_[1:2, 1:2, 0]), - ("ndcube_3d_ln_lt_l", np.s_[..., 0]), - ("ndcube_3d_ln_lt_l", np.s_[:, :, 0]), - ("ndcube_3d_ln_lt_l", np.s_[1:2, 1:2, 0]), ("ndcube_4d_ln_lt_l_t", np.s_[:, :, 0, 0]), ("ndcube_4d_ln_lt_l_t", np.s_[..., 0, 0]), ("ndcube_4d_ln_lt_l_t", np.s_[1:2, 1:2, 1, 1]), - ), + ], indirect=("ndc",)) def test_slicing_ln_lt(ndc, item): sndc = ndc[item] @@ -70,18 +67,15 @@ def test_slicing_ln_lt(ndc, item): assert np.allclose(sndc.wcs.axis_correlation_matrix, np.ones(2, dtype=bool)) -@pytest.mark.parametrize("ndc, item", - ( - ("ndcube_3d_ln_lt_l", np.s_[0, 0, :]), - ("ndcube_3d_ln_lt_l", np.s_[0, 0, ...]), - ("ndcube_3d_ln_lt_l", np.s_[1, 1, 1:2]), +@pytest.mark.parametrize(("ndc", "item"), + [ ("ndcube_3d_ln_lt_l", np.s_[0, 0, :]), ("ndcube_3d_ln_lt_l", np.s_[0, 0, ...]), ("ndcube_3d_ln_lt_l", np.s_[1, 1, 1:2]), ("ndcube_4d_ln_lt_l_t", np.s_[0, 0, :, 0]), ("ndcube_4d_ln_lt_l_t", np.s_[0, 0, ..., 0]), ("ndcube_4d_ln_lt_l_t", np.s_[1, 1, 1:2, 1]), - ), + ], indirect=("ndc",)) def test_slicing_wave(ndc, item): sndc = ndc[item] @@ -105,18 +99,16 @@ def test_slicing_wave(ndc, item): assert np.allclose(sndc.wcs.axis_correlation_matrix, np.ones(1, dtype=bool)) -@pytest.mark.parametrize("ndc, item", - ( +@pytest.mark.parametrize(("ndc", "item"), + [ ("ndcube_3d_ln_lt_l", np.s_[0, :, :]), ("ndcube_3d_ln_lt_l", np.s_[0, ...]), ("ndcube_3d_ln_lt_l", np.s_[1, 1:2]), - ("ndcube_3d_ln_lt_l", np.s_[0, :, :]), - ("ndcube_3d_ln_lt_l", np.s_[0, ...]), ("ndcube_3d_ln_lt_l", np.s_[1, :, 1:2]), ("ndcube_4d_ln_lt_l_t", np.s_[0, :, :, 0]), ("ndcube_4d_ln_lt_l_t", np.s_[0, ..., 0]), ("ndcube_4d_ln_lt_l_t", np.s_[1, 1:2, 1:2, 1]), - ), + ], indirect=("ndc",)) def test_slicing_split_celestial(ndc, item): sndc = ndc[item] @@ -206,9 +198,9 @@ def test_axis_world_coords_empty_ec(ndcube_3d_l_ln_lt_ectime): # slice the cube so extra_coords is empty, and then try and run axis_world_coords awc = sub_cube.axis_world_coords(wcs=sub_cube.extra_coords) - assert awc == tuple() + assert awc == () sub_cube._generate_world_coords(pixel_corners=False, wcs=sub_cube.extra_coords, units=True) - assert awc == tuple() + assert awc == () @pytest.mark.xfail(reason=">1D Tables not supported") @@ -230,7 +222,7 @@ def test_axis_world_coords_complex_ec(ndcube_4d_ln_lt_l_t): assert u.allclose(coords[3], data) -@pytest.mark.parametrize("axes", ([-1], [2], ["em"])) +@pytest.mark.parametrize("axes", [[-1], [2], ["em"]]) def test_axis_world_coords_single(axes, ndcube_3d_ln_lt_l): coords = ndcube_3d_ln_lt_l.axis_world_coords_values(*axes) assert len(coords) == 1 @@ -243,7 +235,7 @@ def test_axis_world_coords_single(axes, ndcube_3d_ln_lt_l): assert u.allclose(coords[0], [1.02e-09, 1.04e-09, 1.06e-09, 1.08e-09] * u.m) -@pytest.mark.parametrize("axes", ([-1], [2], ["em"])) +@pytest.mark.parametrize("axes", [[-1], [2], ["em"]]) def test_axis_world_coords_single_pixel_corners(axes, ndcube_3d_ln_lt_l): coords = ndcube_3d_ln_lt_l.axis_world_coords_values(*axes, pixel_corners=True) assert u.allclose(coords, [1.01e-09, 1.03e-09, 1.05e-09, 1.07e-09, 1.09e-09] * u.m) @@ -252,11 +244,11 @@ def test_axis_world_coords_single_pixel_corners(axes, ndcube_3d_ln_lt_l): assert u.allclose(coords, [1.01e-09, 1.03e-09, 1.05e-09, 1.07e-09, 1.09e-09] * u.m) -@pytest.mark.parametrize("ndc, item", - ( +@pytest.mark.parametrize(("ndc", "item"), + [ ("ndcube_3d_ln_lt_l", np.s_[0, 0, :]), ("ndcube_3d_ln_lt_l", np.s_[0, 0, ...]), - ), + ], indirect=("ndc",)) def test_axis_world_coords_sliced_all_3d(ndc, item): coords = ndc[item].axis_world_coords_values() @@ -266,11 +258,11 @@ def test_axis_world_coords_sliced_all_3d(ndc, item): assert u.allclose(coords, [1.02e-09, 1.04e-09, 1.06e-09, 1.08e-09] * u.m) -@pytest.mark.parametrize("ndc, item", - ( +@pytest.mark.parametrize(("ndc", "item"), + [ ("ndcube_4d_ln_lt_l_t", np.s_[0, 0, :, 0]), ("ndcube_4d_ln_lt_l_t", np.s_[0, 0, ..., 0]), - ), + ], indirect=("ndc",)) def test_axis_world_coords_sliced_all_4d(ndc, item): coords = ndc[item].axis_world_coords_values() @@ -295,12 +287,12 @@ def test_axis_world_coords_all_4d_split(ndcube_4d_ln_l_t_lt): 1.2e-10, 1.4e-10, 1.6e-10, 1.8e-10, 2.0e-10] * u.m) -@pytest.mark.parametrize('wapt', ( +@pytest.mark.parametrize('wapt', [ ('custom:pos.helioprojective.lon', 'custom:pos.helioprojective.lat', 'em.wl'), ('custom:pos.helioprojective.lat', 'em.wl'), (0, 1), (0, 1, 3) -)) +]) def test_axis_world_coords_all_4d_split_sub(ndcube_4d_ln_l_t_lt, wapt): coords = ndcube_4d_ln_l_t_lt.axis_world_coords(*wapt) assert len(coords) == 2 @@ -334,8 +326,8 @@ def test_axis_world_coords_wave(ndcube_3d_ln_lt_l): assert u.allclose(coords[0], [1.02e-09, 1.04e-09, 1.06e-09, 1.08e-09] * u.m) -@pytest.mark.parametrize('wapt', ('custom:pos.helioprojective.lon', - 'custom:pos.helioprojective.lat')) +@pytest.mark.parametrize('wapt', ['custom:pos.helioprojective.lon', + 'custom:pos.helioprojective.lat']) def test_axis_world_coords_sky(ndcube_3d_ln_lt_l, wapt): coords = ndcube_3d_ln_lt_l.axis_world_coords(wapt) assert len(coords) == 1 @@ -402,7 +394,7 @@ def test_array_axis_physical_types(ndcube_3d_ln_lt_l): ('em.wl', 'custom:PIXEL')] output = ndcube_3d_ln_lt_l.array_axis_physical_types for i in range(len(expected)): - assert all([physical_type in expected[i] for physical_type in output[i]]) + assert all(physical_type in expected[i] for physical_type in output[i]) def test_crop(ndcube_4d_ln_lt_l_t): @@ -550,7 +542,7 @@ def test_crop_by_values_with_equivalent_units(ndcube_2d_ln_lt): lower_corner = [(coord[0]*u.deg).to(u.arcsec) for coord in intervals] upper_corner = [(coord[-1]*u.deg).to(u.arcsec) for coord in intervals] expected = ndcube_2d_ln_lt[0:4, 1:7] - output = ndcube_2d_ln_lt.crop_by_values(lower_corner, upper_corner) + output = ndcube_2d_ln_lt.crop_by_values(lower_corner, upper_corner) helpers.assert_cubes_equal(output, expected) @@ -791,7 +783,6 @@ def test_reproject_shape_out(ndcube_4d_ln_l_t_lt, wcs_4d_lt_t_l_ln): wcs_4d_lt_t_l_ln.pixel_shape = None with pytest.raises(Exception): _ = ndcube_4d_ln_l_t_lt.reproject_to(wcs_4d_lt_t_l_ln) - # should not raise an exception when shape_out is specified shape = (5, 10, 12, 8) _ = ndcube_4d_ln_l_t_lt.reproject_to(wcs_4d_lt_t_l_ln, shape_out=shape) diff --git a/ndcube/tests/test_ndcubesequence.py b/ndcube/tests/test_ndcubesequence.py index 1dee3e096..8f3a4ee4b 100644 --- a/ndcube/tests/test_ndcubesequence.py +++ b/ndcube/tests/test_ndcubesequence.py @@ -22,13 +22,13 @@ def derive_sliced_cube_dims(orig_cube_dims, tuple_item): return tuple(expected_cube_dims) -@pytest.mark.parametrize("ndc, item", - ( +@pytest.mark.parametrize(("ndc", "item"), + [ ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0:1], ), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0:1, 0:2]), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0:1, 1]), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1:3, 1, 0:2]) - ), + ], indirect=('ndc',)) def test_slice_sequence_axis(ndc, item): # Calculate expected dimensions of cubes with sequence after slicing. @@ -41,13 +41,13 @@ def test_slice_sequence_axis(ndc, item): assert np.all(sliced_sequence[0].shape == expected_cube0_dims) -@pytest.mark.parametrize("ndc, item", - ( +@pytest.mark.parametrize(("ndc", "item"), + [ ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0]), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[1, 0:1]), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[2, 1]), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[3, 1, 0:2]) - ), + ], indirect=("ndc",)) def test_extract_ndcube(ndc, item): cube = ndc[item] @@ -57,90 +57,90 @@ def test_extract_ndcube(ndc, item): assert np.all(cube.shape == expected_cube_dims) -@pytest.mark.parametrize("ndc, item, expected_common_axis", - ( +@pytest.mark.parametrize(("ndc", "item", "expected_common_axis"), + [ ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[:, 0], 0), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[:, 0:1, 0:2], 1), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[:, :, :, 1], 1), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[:, :, 0], None) - ), + ], indirect=("ndc",)) def test_slice_common_axis(ndc, item, expected_common_axis): sliced_sequence = ndc[item] assert sliced_sequence._common_axis == expected_common_axis -@pytest.mark.parametrize("ndc, item, expected_shape", - ( +@pytest.mark.parametrize(("ndc", "item", "expected_shape"), + [ ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[:, 1:7], (3, 2, (2, 3, 1), 4)), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0, 1:7], (3, (2, 3, 1), 4)), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[:, 2:4], (2, 2, 1, 4)), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[:, 0:6], (2, 2, 3, 4)), ("ndcubesequence_4c_ln_lt_l_cax1", np.s_[0, 0:6], (2, 3, 4)), - ), + ], indirect=("ndc",)) def test_index_as_cube(ndc, item, expected_shape): assert (ndc.index_as_cube[item].shape == expected_shape) -@pytest.mark.parametrize("ndc, axis, expected_shape", - ( +@pytest.mark.parametrize(("ndc", "axis", "expected_shape"), + [ ("ndcubesequence_4c_ln_lt_l", 0, (8, 3, 4)), ("ndcubesequence_4c_ln_lt_l_cax1", 1, (12, 2, 4)) - ), + ], indirect=("ndc",)) -def test_explode_along_axis_common_axis_None(ndc, axis, expected_shape): +def test_explode_along_axis_common_axis_none(ndc, axis, expected_shape): exploded_sequence = ndc.explode_along_axis(axis) assert np.all(exploded_sequence.shape == expected_shape) assert exploded_sequence._common_axis is None -@pytest.mark.parametrize("ndc", (('ndcubesequence_4c_ln_lt_l_cax1',)), indirect=("ndc",)) +@pytest.mark.parametrize("ndc", (['ndcubesequence_4c_ln_lt_l_cax1']), indirect=("ndc",)) def test_explode_along_axis_common_axis_same(ndc): exploded_sequence = ndc.explode_along_axis(2) assert exploded_sequence.shape == (16, 2, 3) assert exploded_sequence._common_axis == ndc._common_axis -@pytest.mark.parametrize("ndc", (('ndcubesequence_4c_ln_lt_l_cax1',)), indirect=("ndc",)) +@pytest.mark.parametrize("ndc", (['ndcubesequence_4c_ln_lt_l_cax1']), indirect=("ndc",)) def test_explode_along_axis_common_axis_changed(ndc): exploded_sequence = ndc.explode_along_axis(0) assert exploded_sequence.shape == (8, 3, 4) assert exploded_sequence._common_axis == ndc._common_axis - 1 -@pytest.mark.parametrize("ndc, expected_shape", - ( +@pytest.mark.parametrize(("ndc", "expected_shape"), + [ ("ndcubesequence_4c_ln_lt_l_cax1", (4, 2., 3., 4.)), - ), + ], indirect=("ndc",)) def test_shape(ndc, expected_shape): np.testing.assert_array_equal(ndc.shape, expected_shape) -@pytest.mark.parametrize("ndc, expected_shape", - ( +@pytest.mark.parametrize(("ndc", "expected_shape"), + [ ("ndcubesequence_4c_ln_lt_l_cax1", [2., 12, 4]), - ), + ], indirect=("ndc",)) def test_cube_like_shape(ndc, expected_shape): assert np.all(ndc.cube_like_shape == expected_shape) -@pytest.mark.parametrize("ndc", (("ndcubesequence_4c_ln_lt_l",)), indirect=("ndc",)) +@pytest.mark.parametrize("ndc", (["ndcubesequence_4c_ln_lt_l"]), indirect=("ndc",)) def test_cube_like_shape_error(ndc): with pytest.raises(TypeError): ndc.cube_like_shape -@pytest.mark.parametrize("ndc", (("ndcubesequence_3c_l_ln_lt_cax1",)), indirect=("ndc",)) +@pytest.mark.parametrize("ndc", (["ndcubesequence_3c_l_ln_lt_cax1"]), indirect=("ndc",)) def test_common_axis_coords(ndc): # Construct expected skycoord common_coords = [cube.axis_world_coords('lon') for cube in ndc] @@ -164,7 +164,7 @@ def test_common_axis_coords(ndc): assert u.allclose(td.to(u.s), 0*u.s, atol=1e-10*u.s) -@pytest.mark.parametrize("ndc", (("ndcubesequence_3c_l_ln_lt_cax1",)), indirect=("ndc",)) +@pytest.mark.parametrize("ndc", (["ndcubesequence_3c_l_ln_lt_cax1"]), indirect=("ndc",)) def test_sequence_axis_coords(ndc): expected = {'distance': [1*u.m, 2*u.m, 3*u.m]} output = ndc.sequence_axis_coords diff --git a/ndcube/utils/collection.py b/ndcube/utils/collection.py index 452344c8d..ac01649ba 100644 --- a/ndcube/utils/collection.py +++ b/ndcube/utils/collection.py @@ -9,11 +9,11 @@ def _sanitize_aligned_axes(keys, data, aligned_axes): if aligned_axes is None: return None # If aligned_axes set to "all", assume all axes are aligned in order. - elif isinstance(aligned_axes, str) and aligned_axes.lower() == "all": + if isinstance(aligned_axes, str) and aligned_axes.lower() == "all": # Check all cubes are of same shape cube0_dims = data[0].shape - cubes_same_shape = all([all([d.shape[i] == dim for i, dim in enumerate(cube0_dims)]) - for d in data]) + cubes_same_shape = all(all(d.shape[i] == dim for i, dim in enumerate(cube0_dims)) + for d in data) if cubes_same_shape is not True: raise ValueError( "All cubes in data not of same shape. Please set aligned_axes kwarg.") @@ -49,8 +49,8 @@ def _sanitize_user_aligned_axes(data, aligned_axes): if not isinstance(aligned_axes, tuple): raise ValueError(aligned_axes_error_message) # Check type of each element. - axes_all_ints = all([isinstance(axis, numbers.Integral) for axis in aligned_axes]) - axes_all_tuples = all([isinstance(axis, tuple) for axis in aligned_axes]) + axes_all_ints = all(isinstance(axis, numbers.Integral) for axis in aligned_axes) + axes_all_tuples = all(isinstance(axis, tuple) for axis in aligned_axes) # If all elements are int, duplicate tuple so there is one for each cube. n_cubes = len(data) if axes_all_ints: @@ -71,7 +71,7 @@ def _sanitize_user_aligned_axes(data, aligned_axes): # and the dimensions of the aligned axes in each cube are the same. subtuples_are_ints = [False] * n_cubes aligned_axes_same_lengths = [False] * n_cubes - if not all([len(axes) == n_aligned_axes for axes in aligned_axes]): + if not all(len(axes) == n_aligned_axes for axes in aligned_axes): raise ValueError("Each element in aligned_axes must have same length.") for i in range(n_cubes): # Check each cube has at least as many dimensions as there are aligned axes @@ -101,9 +101,9 @@ def _sanitize_user_aligned_axes(data, aligned_axes): raise ValueError(aligned_axes_error_message) # Ensure all aligned axes are of same length. - check_dimensions = set([len(set([cube.shape[cube_aligned_axes[j]] - for cube, cube_aligned_axes in zip(data, aligned_axes)])) - for j in range(n_aligned_axes)]) + check_dimensions = {len({cube.shape[cube_aligned_axes[j]] + for cube, cube_aligned_axes in zip(data, aligned_axes)}) + for j in range(n_aligned_axes)} if check_dimensions != {1}: raise ValueError("Aligned axes are not all of same length.") diff --git a/ndcube/utils/cube.py b/ndcube/utils/cube.py index af6485d24..9f55083e7 100644 --- a/ndcube/utils/cube.py +++ b/ndcube/utils/cube.py @@ -75,7 +75,7 @@ def sanitize_crop_inputs(points, wcs): # Later we will ensure all points have same number of objects. n_coords[i] = len(points[i]) # Confirm whether point contains at least one None entry. - if all([coord is None for coord in points[i]]): + if all(coord is None for coord in points[i]): values_are_none[i] = True # If no points contain a coord, i.e. if all entries in all points are None, # set no-op flag to True and exit. @@ -257,7 +257,7 @@ def propagate_rebin_uncertainties(uncertainty, data, mask, operation, operation_ if operation in {np.sum, np.nansum, np.mean, np.nanmean}: propagation_operation = np.add # TODO: product was renamed to prod for numpy 2.0 - elif operation in {np.prod, np.nanprod, np.product if hasattr(np, "product") else np.prod}: + elif operation in {np.prod, np.nanprod, np.prod if hasattr(np, "product") else np.prod}: propagation_operation = np.multiply else: raise ValueError("propagation_operation not recognized.") diff --git a/ndcube/utils/sphinx/__init__.py b/ndcube/utils/sphinx/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ndcube/utils/tests/test_utils_collection.py b/ndcube/utils/tests/test_utils_collection.py index 63e7d7000..394fb333f 100644 --- a/ndcube/utils/tests/test_utils_collection.py +++ b/ndcube/utils/tests/test_utils_collection.py @@ -4,7 +4,7 @@ from ndcube.utils import collection as collection_utils -@pytest.mark.parametrize("data_dimensions1,data_dimensions2,data_axes1,data_axes2", [ +@pytest.mark.parametrize(("data_dimensions1", "data_dimensions2", "data_axes1", "data_axes2"), [ ([3., 4., 5.], [3., 5., 15.], (0, 2), (0, 1))]) def test_assert_aligned_axes_compatible(data_dimensions1, data_dimensions2, data_axes1, data_axes2): @@ -12,7 +12,7 @@ def test_assert_aligned_axes_compatible(data_dimensions1, data_dimensions2, data_axes1, data_axes2) -@pytest.mark.parametrize("data_dimensions1,data_dimensions2,data_axes1,data_axes2", [ +@pytest.mark.parametrize(("data_dimensions1", "data_dimensions2", "data_axes1", "data_axes2"), [ ([3., 4., 5.], [3., 5., 15.], (0, 1), (0, 1)), ([3., 4., 5.], [3., 5., 15.], (0, 1), (0, 1, 2)), ([3., 4., 5.], [3., 5., 15.], (0, 1), None)]) diff --git a/ndcube/utils/tests/test_utils_cube.py b/ndcube/utils/tests/test_utils_cube.py index 394c8c49f..5f410e435 100644 --- a/ndcube/utils/tests/test_utils_cube.py +++ b/ndcube/utils/tests/test_utils_cube.py @@ -62,7 +62,8 @@ def test_propagate_rebin_uncertainties_prod(stacked_pixel_data): # Build expected output binned_data = data.prod(axis=0) - expected = np.sqrt(((uncertainty.array / data)**2).sum(axis=0)) * binned_data / 2 # Why do I have to divide by a factor 2 here? + # TODO: Why do I have to divide by a factor 2 here? + expected = np.sqrt(((uncertainty.array / data)**2).sum(axis=0)) * binned_data / 2 expected = StdDevUncertainty(expected) # Run function diff --git a/ndcube/utils/tests/test_utils_sequence.py b/ndcube/utils/tests/test_utils_sequence.py index 373b4baf8..1aa4a1e60 100644 --- a/ndcube/utils/tests/test_utils_sequence.py +++ b/ndcube/utils/tests/test_utils_sequence.py @@ -14,7 +14,7 @@ @pytest.mark.parametrize( - "cube_like_index, common_axis, common_axis_lengths, expected_seq_idx, expected_common_idx", + ("cube_like_index", "common_axis", "common_axis_lengths", "expected_seq_idx", "expected_common_idx"), [(3, 1, [4, 4], 0, 3), (3, 1, [2, 2], 1, 1)] ) @@ -28,7 +28,7 @@ def test_cube_like_index_to_sequence_and_common_axis_indices( @pytest.mark.parametrize( - "item, common_axis, common_axis_lengths, n_cube_dims, expected_sequence_items", [ + ("item", "common_axis", "common_axis_lengths", "n_cube_dims", "expected_sequence_items"), [ ((slice(None), slice(4, 6)), 1, [3, 3], 4, [SequenceItem(sequence_index=1, cube_item=slice(1, 3))]), diff --git a/ndcube/utils/tests/test_utils_wcs.py b/ndcube/utils/tests/test_utils_wcs.py index f21244ca4..5795ca0ce 100644 --- a/ndcube/utils/tests/test_utils_wcs.py +++ b/ndcube/utils/tests/test_utils_wcs.py @@ -86,7 +86,7 @@ def test_physical_type_to_pixel_axes(test_wcs): assert all(output == expected) -@pytest.mark.parametrize("test_input,expected", [('wl', 2), ('em.wl', 2)]) +@pytest.mark.parametrize(('test_input', 'expected'), [('wl', 2), ('em.wl', 2)]) def test_physical_type_to_world_axis(test_input, expected): world_axis_physical_types = ['custom:pos.helioprojective.lon', 'custom:pos.helioprojective.lat', 'em.wl', 'time'] diff --git a/ndcube/utils/wcs.py b/ndcube/utils/wcs.py index 9268676e2..dcb99eaf5 100644 --- a/ndcube/utils/wcs.py +++ b/ndcube/utils/wcs.py @@ -83,9 +83,8 @@ def convert_between_array_and_pixel_axes(axis, naxes): raise IndexError("Axis out of range. " f"Number of axes = {naxes}; Axis numbers requested = {axis}") # Reflect axis about center of number of axes. - reflected_axis = naxes - 1 - axis + return naxes - 1 - axis - return reflected_axis def pixel_axis_to_world_axes(pixel_axis, axis_correlation_matrix): @@ -242,8 +241,7 @@ def get_dependent_pixel_axes(pixel_axis, axis_correlation_matrix): # To do this we take a column from the matrix and find if there are # any entries in common with all other columns in the matrix. world_dep = axis_correlation_matrix[:, pixel_axis:pixel_axis + 1] - dependent_pixel_axes = np.sort(np.nonzero((world_dep & axis_correlation_matrix).any(axis=0))[0]) - return dependent_pixel_axes + return np.sort(np.nonzero((world_dep & axis_correlation_matrix).any(axis=0))[0]) def get_dependent_array_axes(array_axis, axis_correlation_matrix): @@ -308,8 +306,7 @@ def get_dependent_world_axes(world_axis, axis_correlation_matrix): # To do this we take a row from the matrix and find if there are # any entries in common with all other rows in the matrix. pixel_dep = axis_correlation_matrix[world_axis:world_axis + 1] - dependent_world_axes = np.sort(np.nonzero((pixel_dep & axis_correlation_matrix).any(axis=1))[0]) - return dependent_world_axes + return np.sort(np.nonzero((pixel_dep & axis_correlation_matrix).any(axis=1))[0]) def get_dependent_physical_types(physical_type, wcs): @@ -332,8 +329,7 @@ def get_dependent_physical_types(physical_type, wcs): world_axis_physical_types = wcs.world_axis_physical_types world_axis = physical_type_to_world_axis(physical_type, world_axis_physical_types) dependent_world_axes = get_dependent_world_axes(world_axis, wcs.axis_correlation_matrix) - dependent_physical_types = np.array(world_axis_physical_types)[dependent_world_axes] - return dependent_physical_types + return np.array(world_axis_physical_types)[dependent_world_axes] def validate_physical_types(physical_types): @@ -450,10 +446,9 @@ def get_low_level_wcs(wcs, name='wcs'): if isinstance(wcs, BaseHighLevelWCS): return wcs.low_level_wcs - elif isinstance(wcs, BaseLowLevelWCS): + if isinstance(wcs, BaseLowLevelWCS): return wcs - else: - raise ValueError(f'{name} must implement either BaseHighLevelWCS or BaseLowLevelWCS') + raise ValueError(f'{name} must implement either BaseHighLevelWCS or BaseLowLevelWCS') def compare_wcs_physical_types(source_wcs, target_wcs): diff --git a/ndcube/visualization/descriptor.py b/ndcube/visualization/descriptor.py index 06926fe80..d20c27afe 100644 --- a/ndcube/visualization/descriptor.py +++ b/ndcube/visualization/descriptor.py @@ -47,22 +47,22 @@ def _resolve_default_type(self, raise_error=True): except ImportError as e: if raise_error: raise ImportError(MISSING_ANIMATORS_ERROR_MSG) from e + return None - elif self._default_type is not None: + if self._default_type is not None: return self._default_type # If we have no default type then just return None - else: - return + return None def __get__(self, obj, objtype=None): if obj is None: - return + return None if getattr(obj, self._attribute_name, None) is None: plotter_type = self._resolve_default_type() if plotter_type is None: - return + return None self.__set__(obj, plotter_type) diff --git a/ndcube/visualization/mpl_plotter.py b/ndcube/visualization/mpl_plotter.py index 3bedbf510..e19a07faa 100644 --- a/ndcube/visualization/mpl_plotter.py +++ b/ndcube/visualization/mpl_plotter.py @@ -96,7 +96,7 @@ def _not_visible_coords(self, axes, axes_coordinates): """ Based on an axes object and axes_coords, work out which coords should not be visible. """ - visible_coords = set(item[1] for item in axes.coords._aliases.items() if item[0] in axes_coordinates) + visible_coords = {item[1] for item in axes.coords._aliases.items() if item[0] in axes_coordinates} return set(axes.coords._aliases.values()).difference(visible_coords) def _apply_axes_coordinates(self, axes, axes_coordinates): diff --git a/ndcube/visualization/mpl_sequence_plotter.py b/ndcube/visualization/mpl_sequence_plotter.py index 5fff46939..1b1e477c3 100644 --- a/ndcube/visualization/mpl_sequence_plotter.py +++ b/ndcube/visualization/mpl_sequence_plotter.py @@ -32,8 +32,7 @@ def plot(self, sequence_axis_coords=None, sequence_axis_unit=None, **kwargs): sequence_dims = self._ndcube.shape if len(sequence_dims) == 2: raise NotImplementedError("Visualizing sequences of 1-D cubes not currently supported.") - else: - return self.animate(sequence_axis_coords, sequence_axis_unit, **kwargs) + return self.animate(sequence_axis_coords, sequence_axis_unit, **kwargs) def animate(self, sequence_axis_coords=None, sequence_axis_unit=None, **kwargs): """ diff --git a/ndcube/visualization/plotting_utils.py b/ndcube/visualization/plotting_utils.py index 8e60c5722..b9d982611 100644 --- a/ndcube/visualization/plotting_utils.py +++ b/ndcube/visualization/plotting_utils.py @@ -65,10 +65,8 @@ def prep_plot_kwargs(naxis, wcs, plot_axes, axes_coordinates, axes_units): # Ensure all elements in axes_coordinates are of correct types. ax_coord_types = (str, type(None)) for axis_coordinate in axes_coordinates: - if isinstance(axis_coordinate, str): - # coordinates can be accessed by either name or type - if axis_coordinate not in set(wcs.world_axis_physical_types).union(set(wcs.world_axis_names)): - raise ValueError(f"{axis_coordinate} is not one of this cubes world axis physical types.") + if isinstance(axis_coordinate, str) and axis_coordinate not in set(wcs.world_axis_physical_types).union(set(wcs.world_axis_names)): + raise ValueError(f"{axis_coordinate} is not one of this cubes world axis physical types.") if not isinstance(axis_coordinate, ax_coord_types): raise TypeError(f"axes_coordinates must be one of {ax_coord_types} or list of those, not {type(axis_coordinate)}.") @@ -77,7 +75,7 @@ def prep_plot_kwargs(naxis, wcs, plot_axes, axes_coordinates, axes_units): if len(axes_units) != wcs.world_n_dim: raise ValueError(f"The length of the axes_units argument must be {wcs.world_n_dim}.") # Convert all non-None elements to astropy units - axes_units = list(map(lambda x: u.Unit(x) if x is not None else None, axes_units))[::-1] + axes_units = [u.Unit(x) if x is not None else None for x in axes_units][::-1] for i, axis_unit in enumerate(axes_units): wau = wcs.world_axis_units[i] if axis_unit is not None and not axis_unit.is_equivalent(wau): diff --git a/ndcube/visualization/tests/__init__.py b/ndcube/visualization/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ndcube/visualization/tests/figure_hashes_mpl_382_ft_261_astropy_600_animators_111.json b/ndcube/visualization/tests/figure_hashes_mpl_382_ft_261_astropy_600_animators_111.json index 59cf7d7dd..dab89a317 100644 --- a/ndcube/visualization/tests/figure_hashes_mpl_382_ft_261_astropy_600_animators_111.json +++ b/ndcube/visualization/tests/figure_hashes_mpl_382_ft_261_astropy_600_animators_111.json @@ -1,29 +1,29 @@ { - "test_plotting.test_plot_1D_cube": "040edf223f40754b7a53915da165d10dad9d4456622f1f8217269679c3b22550", - "test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "ea89b5d1c2fdcf34ba353dffe528f5ecd5ce364a77f43a08d00a6a7f11a869f4", - "test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "f5d10df37509d1ba9429deb95ee29cf37dddea1a30f4c84cf2b10034c74c31bd", - "test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "e48765a561ea0f43f5a80d3da27fd71654b18d276cbb7617e1918843012425f9", - "test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice3-kwargs3]": "b8d2d026c12a600b36ebe8cd943a5d443519443436f755e54fac12a681f1e3e4", - "test_plotting.test_plot_1D_cube_from_slice[uncertainty-cslice4-kwargs4]": "04083a963e79e9aaf2d1771e9a86158ef4be51c4b8cde7b6b7b85f80e1452ea9", - "test_plotting.test_plot_1D_cube_from_slice[unit_uncertainty-cslice5-kwargs5]": "53f90627c0612aac7edcca97e108069b915bc7edaf871e62ba747732510e7cb2", - "test_plotting.test_plot_1D_cube_from_slice[mask-cslice6-kwargs6]": "b888887d05f9f7654968bd59a33fd86a12111503222904bfd369475210da7abb", - "test_plotting.test_plot_2D_cube": "cc6ff47be658bdcc2e8dd226f50ab04da89cc23dc834fa5b0ce2a5a724414ada", - "test_plotting.test_plot_2D_cube_colorbar": "2cdc18629da36f96ddeb24631f70387bc3090c128ad994914816b1be7864af79", - "test_plotting.test_plot_2D_cube_custom_axis": "cc6ff47be658bdcc2e8dd226f50ab04da89cc23dc834fa5b0ce2a5a724414ada", - "test_plotting.test_plot_2D_cube_custom_axis_plot_axes": "c61173e4904ffd7c6a88a9a18e68014ee258d5d59fb226c021f49a57230dfefc", - "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "77b0b9a0067897786777a78974cff240a3268ed38937047c3945473e82bc4cf0", - "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "2af86ad3672ef70d51b3637325c5e7860cad26f70032b51877d45d0a9b647a44", - "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "b00ec9344620cd9f640bbb3410e1fc508b5a86d02826d378147b33b999606d05", - "test_plotting.test_plot_2D_cube_from_slice[unit_uncertainty-cslice3-kwargs3]": "77b0b9a0067897786777a78974cff240a3268ed38937047c3945473e82bc4cf0", - "test_plotting.test_plot_2D_cube_from_slice[mask-cslice4-kwargs4]": "490f4d37a93ab800ab2eb4fa077c448bcd6ebc30d64ab374118f5a9288246fb3", - "test_plotting.test_animate_2D_cube": "5dbe5e20670b3809365605637157f46d11235d7a28358cd03db075899e7ff2ae", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "47a0279e3244139f5b7126625a1aeb2bd038b3b39b38829a6b5a9129d8bba09d", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "8e9303bb77aef56991d2cf470de6840cea1994dd8ffb17bec48be9428ec5f7b8", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs2]": "4ae632474865d7df8434f66560c83205597437eadb61840266c334a836e4df7d", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs3]": "6ed647bf57c788e3f8bbdb730b1efa5c77f3b4f3cc44a149c16b62ee1fd8c6e3", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs4]": "4a983ef4f55fefde7c157f0d2799084db2bc4241e64d7a1d3b2e752bbba81518", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice5-kwargs5]": "b6acd10439bdc945b762ef9be430b5805c735e715ac4a1621d2fb834e48e91c1", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice6-kwargs6]": "4ae632474865d7df8434f66560c83205597437eadb61840266c334a836e4df7d", - "test_plotting.test_animate_cube_from_slice[unit_uncertainty-cslice7-kwargs7]": "b6acd10439bdc945b762ef9be430b5805c735e715ac4a1621d2fb834e48e91c1", - "test_plotting.test_animate_cube_from_slice[mask-cslice8-kwargs8]": "27db07c262fdb78bc45e9302c9c18d60e73c840d6f26e4af156355f73c3ef6e0" -} + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube": "040edf223f40754b7a53915da165d10dad9d4456622f1f8217269679c3b22550", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "ea89b5d1c2fdcf34ba353dffe528f5ecd5ce364a77f43a08d00a6a7f11a869f4", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "f5d10df37509d1ba9429deb95ee29cf37dddea1a30f4c84cf2b10034c74c31bd", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "e48765a561ea0f43f5a80d3da27fd71654b18d276cbb7617e1918843012425f9", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice3-kwargs3]": "b8d2d026c12a600b36ebe8cd943a5d443519443436f755e54fac12a681f1e3e4", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[uncertainty-cslice4-kwargs4]": "04083a963e79e9aaf2d1771e9a86158ef4be51c4b8cde7b6b7b85f80e1452ea9", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[unit_uncertainty-cslice5-kwargs5]": "53f90627c0612aac7edcca97e108069b915bc7edaf871e62ba747732510e7cb2", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[mask-cslice6-kwargs6]": "b888887d05f9f7654968bd59a33fd86a12111503222904bfd369475210da7abb", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube": "cc6ff47be658bdcc2e8dd226f50ab04da89cc23dc834fa5b0ce2a5a724414ada", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_colorbar": "2cdc18629da36f96ddeb24631f70387bc3090c128ad994914816b1be7864af79", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_custom_axis": "cc6ff47be658bdcc2e8dd226f50ab04da89cc23dc834fa5b0ce2a5a724414ada", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_custom_axis_plot_axes": "c61173e4904ffd7c6a88a9a18e68014ee258d5d59fb226c021f49a57230dfefc", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "77b0b9a0067897786777a78974cff240a3268ed38937047c3945473e82bc4cf0", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "2af86ad3672ef70d51b3637325c5e7860cad26f70032b51877d45d0a9b647a44", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "b00ec9344620cd9f640bbb3410e1fc508b5a86d02826d378147b33b999606d05", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[unit_uncertainty-cslice3-kwargs3]": "77b0b9a0067897786777a78974cff240a3268ed38937047c3945473e82bc4cf0", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[mask-cslice4-kwargs4]": "490f4d37a93ab800ab2eb4fa077c448bcd6ebc30d64ab374118f5a9288246fb3", + "ndcube.visualization.tests.test_plotting.test_animate_2D_cube": "5dbe5e20670b3809365605637157f46d11235d7a28358cd03db075899e7ff2ae", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "47a0279e3244139f5b7126625a1aeb2bd038b3b39b38829a6b5a9129d8bba09d", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "8e9303bb77aef56991d2cf470de6840cea1994dd8ffb17bec48be9428ec5f7b8", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs2]": "4ae632474865d7df8434f66560c83205597437eadb61840266c334a836e4df7d", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs3]": "6ed647bf57c788e3f8bbdb730b1efa5c77f3b4f3cc44a149c16b62ee1fd8c6e3", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs4]": "4a983ef4f55fefde7c157f0d2799084db2bc4241e64d7a1d3b2e752bbba81518", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice5-kwargs5]": "b6acd10439bdc945b762ef9be430b5805c735e715ac4a1621d2fb834e48e91c1", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice6-kwargs6]": "4ae632474865d7df8434f66560c83205597437eadb61840266c334a836e4df7d", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[unit_uncertainty-cslice7-kwargs7]": "b6acd10439bdc945b762ef9be430b5805c735e715ac4a1621d2fb834e48e91c1", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[mask-cslice8-kwargs8]": "27db07c262fdb78bc45e9302c9c18d60e73c840d6f26e4af156355f73c3ef6e0" +} \ No newline at end of file diff --git a/ndcube/visualization/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json b/ndcube/visualization/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json index 600675865..c3757929f 100644 --- a/ndcube/visualization/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json +++ b/ndcube/visualization/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json @@ -1,29 +1,29 @@ { - "test_plotting.test_plot_1D_cube": "040edf223f40754b7a53915da165d10dad9d4456622f1f8217269679c3b22550", - "test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "ea89b5d1c2fdcf34ba353dffe528f5ecd5ce364a77f43a08d00a6a7f11a869f4", - "test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "f5d10df37509d1ba9429deb95ee29cf37dddea1a30f4c84cf2b10034c74c31bd", - "test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "e48765a561ea0f43f5a80d3da27fd71654b18d276cbb7617e1918843012425f9", - "test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice3-kwargs3]": "7d9c6a470077d7ea1e2a38160d9dd27e9c82e9106d8bfdcec789055e9089c8cd", - "test_plotting.test_plot_1D_cube_from_slice[uncertainty-cslice4-kwargs4]": "04083a963e79e9aaf2d1771e9a86158ef4be51c4b8cde7b6b7b85f80e1452ea9", - "test_plotting.test_plot_1D_cube_from_slice[unit_uncertainty-cslice5-kwargs5]": "53f90627c0612aac7edcca97e108069b915bc7edaf871e62ba747732510e7cb2", - "test_plotting.test_plot_1D_cube_from_slice[mask-cslice6-kwargs6]": "b888887d05f9f7654968bd59a33fd86a12111503222904bfd369475210da7abb", - "test_plotting.test_plot_2D_cube": "4b194dbc850bfd9ae983ec5f0e07e7295f537e591e61177aadca28e41d74bd98", - "test_plotting.test_plot_2D_cube_colorbar": "8adf60402dbd71d066547a08c81757f99db4f55b85288212420cf3a70f967c3e", - "test_plotting.test_plot_2D_cube_custom_axis": "4b194dbc850bfd9ae983ec5f0e07e7295f537e591e61177aadca28e41d74bd98", - "test_plotting.test_plot_2D_cube_custom_axis_plot_axes": "712809a75e7d587c064d9631ed218214071a9dd2d8017d937ae73c613e4c2ab4", - "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "77b0b9a0067897786777a78974cff240a3268ed38937047c3945473e82bc4cf0", - "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "2af86ad3672ef70d51b3637325c5e7860cad26f70032b51877d45d0a9b647a44", - "test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "af99b2460f2ed4a99f159fe854cdf63285eaffb55c09932f74f15f2198a7f1b1", - "test_plotting.test_plot_2D_cube_from_slice[unit_uncertainty-cslice3-kwargs3]": "77b0b9a0067897786777a78974cff240a3268ed38937047c3945473e82bc4cf0", - "test_plotting.test_plot_2D_cube_from_slice[mask-cslice4-kwargs4]": "490f4d37a93ab800ab2eb4fa077c448bcd6ebc30d64ab374118f5a9288246fb3", - "test_plotting.test_animate_2D_cube": "f0622456d02808c9845336e7bead8f4ac22e99d2450bf6f14bb74686a45fd5ed", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "47a0279e3244139f5b7126625a1aeb2bd038b3b39b38829a6b5a9129d8bba09d", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "8e9303bb77aef56991d2cf470de6840cea1994dd8ffb17bec48be9428ec5f7b8", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs2]": "4ae632474865d7df8434f66560c83205597437eadb61840266c334a836e4df7d", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs3]": "6ed647bf57c788e3f8bbdb730b1efa5c77f3b4f3cc44a149c16b62ee1fd8c6e3", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs4]": "4a983ef4f55fefde7c157f0d2799084db2bc4241e64d7a1d3b2e752bbba81518", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice5-kwargs5]": "b6acd10439bdc945b762ef9be430b5805c735e715ac4a1621d2fb834e48e91c1", - "test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice6-kwargs6]": "4ae632474865d7df8434f66560c83205597437eadb61840266c334a836e4df7d", - "test_plotting.test_animate_cube_from_slice[unit_uncertainty-cslice7-kwargs7]": "b6acd10439bdc945b762ef9be430b5805c735e715ac4a1621d2fb834e48e91c1", - "test_plotting.test_animate_cube_from_slice[mask-cslice8-kwargs8]": "27db07c262fdb78bc45e9302c9c18d60e73c840d6f26e4af156355f73c3ef6e0" -} + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube": "040edf223f40754b7a53915da165d10dad9d4456622f1f8217269679c3b22550", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "ea89b5d1c2fdcf34ba353dffe528f5ecd5ce364a77f43a08d00a6a7f11a869f4", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "f5d10df37509d1ba9429deb95ee29cf37dddea1a30f4c84cf2b10034c74c31bd", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "e48765a561ea0f43f5a80d3da27fd71654b18d276cbb7617e1918843012425f9", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice3-kwargs3]": "7d9c6a470077d7ea1e2a38160d9dd27e9c82e9106d8bfdcec789055e9089c8cd", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[uncertainty-cslice4-kwargs4]": "04083a963e79e9aaf2d1771e9a86158ef4be51c4b8cde7b6b7b85f80e1452ea9", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[unit_uncertainty-cslice5-kwargs5]": "53f90627c0612aac7edcca97e108069b915bc7edaf871e62ba747732510e7cb2", + "ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[mask-cslice6-kwargs6]": "b888887d05f9f7654968bd59a33fd86a12111503222904bfd369475210da7abb", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube": "4b194dbc850bfd9ae983ec5f0e07e7295f537e591e61177aadca28e41d74bd98", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_colorbar": "8adf60402dbd71d066547a08c81757f99db4f55b85288212420cf3a70f967c3e", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_custom_axis": "4b194dbc850bfd9ae983ec5f0e07e7295f537e591e61177aadca28e41d74bd98", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_custom_axis_plot_axes": "712809a75e7d587c064d9631ed218214071a9dd2d8017d937ae73c613e4c2ab4", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "77b0b9a0067897786777a78974cff240a3268ed38937047c3945473e82bc4cf0", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "2af86ad3672ef70d51b3637325c5e7860cad26f70032b51877d45d0a9b647a44", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "af99b2460f2ed4a99f159fe854cdf63285eaffb55c09932f74f15f2198a7f1b1", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[unit_uncertainty-cslice3-kwargs3]": "77b0b9a0067897786777a78974cff240a3268ed38937047c3945473e82bc4cf0", + "ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[mask-cslice4-kwargs4]": "490f4d37a93ab800ab2eb4fa077c448bcd6ebc30d64ab374118f5a9288246fb3", + "ndcube.visualization.tests.test_plotting.test_animate_2D_cube": "f0622456d02808c9845336e7bead8f4ac22e99d2450bf6f14bb74686a45fd5ed", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "47a0279e3244139f5b7126625a1aeb2bd038b3b39b38829a6b5a9129d8bba09d", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "8e9303bb77aef56991d2cf470de6840cea1994dd8ffb17bec48be9428ec5f7b8", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs2]": "4ae632474865d7df8434f66560c83205597437eadb61840266c334a836e4df7d", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs3]": "6ed647bf57c788e3f8bbdb730b1efa5c77f3b4f3cc44a149c16b62ee1fd8c6e3", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs4]": "4a983ef4f55fefde7c157f0d2799084db2bc4241e64d7a1d3b2e752bbba81518", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice5-kwargs5]": "b6acd10439bdc945b762ef9be430b5805c735e715ac4a1621d2fb834e48e91c1", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice6-kwargs6]": "4ae632474865d7df8434f66560c83205597437eadb61840266c334a836e4df7d", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[unit_uncertainty-cslice7-kwargs7]": "b6acd10439bdc945b762ef9be430b5805c735e715ac4a1621d2fb834e48e91c1", + "ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[mask-cslice8-kwargs8]": "27db07c262fdb78bc45e9302c9c18d60e73c840d6f26e4af156355f73c3ef6e0" +} \ No newline at end of file diff --git a/ndcube/visualization/tests/test_plotting.py b/ndcube/visualization/tests/test_plotting.py index 39672331f..ce9f1db85 100644 --- a/ndcube/visualization/tests/test_plotting.py +++ b/ndcube/visualization/tests/test_plotting.py @@ -22,7 +22,7 @@ def test_plot_1D_cube(ndcube_1d_l): @figure_test @pytest.mark.parametrize(("ndcube_4d", "cslice", "kwargs"), - ( + [ ("ln_lt_l_t", np.s_[0, 0, 0, :], {}), ("ln_lt_l_t", np.s_[0, 0, :, 0], {}), ("ln_lt_l_t", np.s_[0, :, 0, 0], {}), @@ -31,7 +31,7 @@ def test_plot_1D_cube(ndcube_1d_l): ("uncertainty", np.s_[0, 0, 0, :], {}), ("unit_uncertainty", np.s_[0, 0, 0, :], {'data_unit': u.mJ}), - ("mask", np.s_[0, 0, 0, :], {'marker': 'o'}),), + ("mask", np.s_[0, 0, 0, :], {'marker': 'o'}),], indirect=["ndcube_4d"]) def test_plot_1D_cube_from_slice(ndcube_4d, cslice, kwargs): # TODO: The output for the spatial plots is inconsistent between the lat @@ -80,12 +80,12 @@ def test_plot_2D_cube_custom_axis_plot_axes(ndcube_2d_ln_lt): @figure_test @pytest.mark.parametrize(("ndcube_4d", "cslice", "kwargs"), - ( + [ ("ln_lt_l_t", np.s_[0, 0, :, :], {}), ("ln_lt_l_t", np.s_[0, :, :, 0], {}), ("ln_lt_l_t", np.s_[:, :, 0, 0], {}), ("unit_uncertainty", np.s_[0, 0, :, :], {'data_unit': u.mJ}), - ("mask", np.s_[0, :, 0, :], {}),), + ("mask", np.s_[0, :, 0, :], {}),], indirect=["ndcube_4d"]) def test_plot_2D_cube_from_slice(ndcube_4d, cslice, kwargs): fig = plt.figure() @@ -108,16 +108,17 @@ def test_animate_2D_cube(ndcube_2d_ln_lt): @figure_test @pytest.mark.parametrize(("ndcube_4d", "cslice", "kwargs"), - ( + [ ("ln_lt_l_t", np.s_[:, :, 0, :], {}), ("ln_lt_l_t", np.s_[:, :, 0, :], {'plot_axes': [..., 'x']}), ("ln_lt_l_t", None, {}), - ("ln_lt_l_t", None, {"plot_axes": [0, 0, 'x', 'y'], "axes_units": [None, None, u.pm, None]}), + ("ln_lt_l_t", None, {"plot_axes": [0, 0, 'x', 'y'], + "axes_units": [None, None, u.pm, None]}), ("ln_lt_l_t", None, {"plot_axes": [0, 'x', 0, 'y']}), ("ln_lt_l_t", np.s_[0, :, :, :], {}), ("ln_lt_l_t", np.s_[:, :, :, :], {}), ("unit_uncertainty", np.s_[0, :, :, :], {'data_unit': u.mJ}), - ("mask", np.s_[:, :, :, :], {}),), + ("mask", np.s_[:, :, :, :], {}),], indirect=["ndcube_4d"]) def test_animate_cube_from_slice(ndcube_4d, cslice, kwargs): if cslice: @@ -139,7 +140,7 @@ def test_mpl_axes(ndcube_4d, cslice): plt.close() -def test_plotter_is_None(ndcube_1d_l): +def test_plotter_is_none(ndcube_1d_l): class NewCube(NDCube): plotter = PlotterDescriptor(default_type=None) diff --git a/ndcube/visualization/tests/test_plotting_utils.py b/ndcube/visualization/tests/test_plotting_utils.py index e82c9a3d2..ccd4d31fb 100644 --- a/ndcube/visualization/tests/test_plotting_utils.py +++ b/ndcube/visualization/tests/test_plotting_utils.py @@ -5,7 +5,7 @@ import ndcube.visualization.plotting_utils as utils -@pytest.mark.parametrize("ndim, plist, output", ( +@pytest.mark.parametrize(("ndim", "plist", "output"), [ (2, ['x', 'y'], ['x', 'y']), (2, [..., 'x', 'y'], ['x', 'y']), (2, ['x', 'y', ...], ['x', 'y']), @@ -14,7 +14,7 @@ (5, [..., 'x'], [None, None, None, None, 'x']), (5, [..., 'x', None], [None, None, None, 'x', None]), (5, [None, ..., 'x', None, 'y'], [None, None, 'x', None, 'y']), -)) +]) def test_expand_ellipsis(ndim, plist, output): result = utils._expand_ellipsis(ndim, plist) assert result == output @@ -53,7 +53,7 @@ def test_prep_plot_kwargs_errors(ndcube_4d_ln_lt_l_t): utils.prep_plot_kwargs(4, ndcube_4d_ln_lt_l_t.wcs, None, None, [u.eV, u.m, u.m, u.m]) -@pytest.mark.parametrize("ndcube_2d, args, output", ( +@pytest.mark.parametrize(("ndcube_2d", "args", "output"), [ ("ln_lt", (None, None, None), (['x', 'y'], None, None)), @@ -63,7 +63,7 @@ def test_prep_plot_kwargs_errors(ndcube_4d_ln_lt_l_t): ("ln_lt", (None, None, [u.deg, 'arcsec']), (['x', 'y'], None, [u.arcsec, u.deg])), -), indirect=['ndcube_2d']) +], indirect=['ndcube_2d']) def test_prep_plot_kwargs(ndcube_2d, args, output): result = utils.prep_plot_kwargs(2, ndcube_2d.wcs, *args) assert result == output diff --git a/ndcube/wcs/tests/__init__.py b/ndcube/wcs/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ndcube/wcs/tools.py b/ndcube/wcs/tools.py index 4924bd1e3..14b80a20b 100644 --- a/ndcube/wcs/tools.py +++ b/ndcube/wcs/tools.py @@ -55,13 +55,13 @@ def unwrap_wcs_to_fitswcs(wcs): for low_level_wrapper in wrapper_chain[::-1]: if isinstance(low_level_wrapper, SlicedLowLevelWCS): slice_items = np.array([slice(None)] * fitswcs.naxis) - slice_items[dropped_data_axes == False] = low_level_wrapper._slices_array # numpy order + slice_items[dropped_data_axes == False] = low_level_wrapper._slices_array # numpy order # NOQA: E712 fitswcs, dda = _slice_fitswcs(fitswcs, slice_items, numpy_order=True) dropped_data_axes[dda] = True elif isinstance(low_level_wrapper, ResampledLowLevelWCS): factor = np.ones(fitswcs.naxis) offset = np.zeros(fitswcs.naxis) - kept_wcs_axes = dropped_data_axes[::-1] == False # WCS-order + kept_wcs_axes = dropped_data_axes[::-1] == False # WCS-order # NOQA: E712 factor[kept_wcs_axes] = low_level_wrapper._factor offset[kept_wcs_axes] = low_level_wrapper._offset fitswcs = _resample_fitswcs(fitswcs, factor, offset) diff --git a/ndcube/wcs/wrappers/__init__.py b/ndcube/wcs/wrappers/__init__.py index d040709cf..4fcb58fdd 100644 --- a/ndcube/wcs/wrappers/__init__.py +++ b/ndcube/wcs/wrappers/__init__.py @@ -1,3 +1,3 @@ -from .compound_wcs import * # NOQA -from .reordered_wcs import * # NOQA -from .resampled_wcs import * # NOQA +from .compound_wcs import * +from .reordered_wcs import * +from .resampled_wcs import * diff --git a/ndcube/wcs/wrappers/compound_wcs.py b/ndcube/wcs/wrappers/compound_wcs.py index 85aa49e22..447e6aed6 100644 --- a/ndcube/wcs/wrappers/compound_wcs.py +++ b/ndcube/wcs/wrappers/compound_wcs.py @@ -148,8 +148,8 @@ def world_to_pixel_values(self, *world_arrays): def world_axis_object_components(self): all_components = [] for iw, w in enumerate(self._wcs): - for component in w.world_axis_object_components: - all_components.append((f'{component[0]}_{iw}',) + component[1:]) + all_components += [(f'{component[0]}_{iw}',) + component[1:] for component + in w.world_axis_object_components] return all_components @property @@ -169,20 +169,25 @@ def pixel_shape(self): for i, ix in enumerate(self.mapping.mapping): if out_shape[ix] != pixel_shape[i]: raise ValueError( - "The pixel shapes of the supplied WCSes do not match for the dimensions shared by the supplied mapping.") + "The pixel shapes of the supplied WCSes do not match " + "for the dimensions shared by the supplied mapping.") return out_shape + return None @property def pixel_bounds(self): if any(w.pixel_bounds is not None for w in self._wcs): - pixel_bounds = tuplesum(w.pixel_bounds or [tuple() for _ in range(w.pixel_n_dim)] for w in self._wcs) + pixel_bounds = tuplesum(w.pixel_bounds or [() for _ in range(w.pixel_n_dim)] for w in self._wcs) out_bounds = self.mapping.inverse(*pixel_bounds) for i, ix in enumerate(self.mapping.mapping): if pixel_bounds[i] and (out_bounds[ix] != pixel_bounds[i]): raise ValueError( - "The pixel bounds of the supplied WCSes do not match for the dimensions shared by the supplied mapping.") + "The pixel bounds of the supplied WCSes do not match " + "for the dimensions shared by the supplied mapping.") iint = np.iinfo(int) return tuple(o or (iint.min, iint.max) for o in out_bounds) + return None + @property def pixel_axis_names(self): @@ -216,4 +221,4 @@ def axis_correlation_matrix(self): @property def serialized_classes(self): - return any([w.serialized_classes for w in self._wcs]) + return any(w.serialized_classes for w in self._wcs) diff --git a/ndcube/wcs/wrappers/reordered_wcs.py b/ndcube/wcs/wrappers/reordered_wcs.py index 11639e01b..84e5565dd 100644 --- a/ndcube/wcs/wrappers/reordered_wcs.py +++ b/ndcube/wcs/wrappers/reordered_wcs.py @@ -54,14 +54,12 @@ def world_axis_names(self): def pixel_to_world_values(self, *pixel_arrays): pixel_arrays = [pixel_arrays[idx] for idx in self._pixel_order_inv] world_arrays = self._wcs.pixel_to_world_values(*pixel_arrays) - world_arrays = [world_arrays[idx] for idx in self._world_order] - return world_arrays + return [world_arrays[idx] for idx in self._world_order] def world_to_pixel_values(self, *world_arrays): world_arrays = [world_arrays[idx] for idx in self._world_order_inv] pixel_arrays = self._wcs.world_to_pixel_values(*world_arrays) - pixel_arrays = [pixel_arrays[idx] for idx in self._pixel_order] - return pixel_arrays + return [pixel_arrays[idx] for idx in self._pixel_order] @property def world_axis_object_components(self): @@ -71,11 +69,13 @@ def world_axis_object_components(self): def pixel_shape(self): if self._wcs.pixel_shape: return tuple([self._wcs.pixel_shape[idx] for idx in self._pixel_order]) + return None @property def pixel_bounds(self): if self._wcs.pixel_bounds: return tuple([self._wcs.pixel_bounds[idx] for idx in self._pixel_order]) + return None @property def axis_correlation_matrix(self): diff --git a/pyproject.toml b/pyproject.toml index bec645c10..be5bd7c14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,8 @@ authors = [ dependencies = [ "astropy>=5.0.6,!=5.1.0", "gwcs>=0.18", - "numpy>=1.23.0" + "numpy>=1.23.0", + "scipy>=1.8.0", ] dynamic = ["version"] @@ -38,13 +39,13 @@ tests = [ docs = [ "sphinx", "sphinx-automodapi", + "sunpy-sphinx-theme", "packaging", "matplotlib", "mpl-animators>=1.0", "sphinx-changelog>=1.1.0", "sphinx-gallery", "sphinxext-opengraph", - "sunpy-sphinx-theme", "sunpy>=5.0.0", ] plotting = [ diff --git a/pytest.ini b/pytest.ini index 4fe506eef..9528576c5 100644 --- a/pytest.ini +++ b/pytest.ini @@ -25,6 +25,9 @@ addopts = -p no:threadexception -m "not mpl_image_compare" --doctest-ignore-import-errors + --doctest-continue-on-failure +mpl-results-path = figure_test_images +mpl-use-full-test-name = true filterwarnings = # Turn all warnings into errors so they do not pass silently. error diff --git a/tox.ini b/tox.ini index 75af641a6..d9000660f 100644 --- a/tox.ini +++ b/tox.ini @@ -86,7 +86,11 @@ commands = --cov=ndcube \ --cov-config={toxinidir}/.coveragerc \ online: --remote-data=any \ - figure: -m "mpl_image_compare" --mpl --remote-data=any --mpl-generate-summary=html --mpl-baseline-path=https://raw.githubusercontent.com/sunpy/sunpy-figure-tests/ndcube-main/figures/{envname}/ \ + figure: -m "mpl_image_compare" \ + figure: --mpl \ + figure: --remote-data=any \ + figure: --mpl-generate-summary=html \ + figure: --mpl-baseline-path=https://raw.githubusercontent.com/sunpy/sunpy-figure-tests/ndcube-main/figures/{envname}/ \ {toxinidir}/docs \ {posargs}