Skip to content

Commit

Permalink
Merge branch 'main' of github.com:sunpy/sunraster into HEAD
Browse files Browse the repository at this point in the history
  • Loading branch information
nabobalis committed Nov 11, 2024
2 parents e8cab17 + a28532a commit 2bfc160
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 153 deletions.
26 changes: 21 additions & 5 deletions .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,30 @@ extend-ignore = [
]

[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
]
"examples/**.py" = [
"T201", # allow use of print in examples
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ filterwarnings =
ignore: invalid value encountered in true_divide
# https://github.com/pytest-dev/pytest-cov/issues/557
ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning
ignore:Please use astropy.wcs.wcsapi.high_level_api.values_to_high_level_objects:DeprecationWarning
97 changes: 48 additions & 49 deletions sunraster/extern/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def __init__(self, header=None, comments=None, axes=None, data_shape=None):
axes = dict(axes)
if not set(axes.keys()).issubset(set(header_keys)):
raise ValueError("All axes must correspond to a value in header under the same key.")
self._axes = dict([(key, self._sanitize_axis_value(axis, header[key], key)) for key, axis in axes.items()])
self._axes = {key: self._sanitize_axis_value(axis, header[key], key) for key, axis in axes.items()}

def _sanitize_axis_value(self, axis, value, key):
if axis is None:
Expand All @@ -100,7 +100,7 @@ def _sanitize_axis_value(self, axis, value, key):
shape_error_msg = f"{key} must have shape {tuple(self.shape[axis])} as it is associated with axes {axis}"
if len(axis) == 1 and not hasattr(value, "__len__") or len(axis) != 1 and not hasattr(value, "shape"):
raise TypeError(shape_error_msg)
elif len(axis) == 1:
if len(axis) == 1:
meta_shape = (len(value),)
else:
meta_shape = value.shape
Expand Down Expand Up @@ -189,51 +189,50 @@ def __getitem__(self, item):
# If item is single string, slicing is simple.
if isinstance(item, str):
return super().__getitem__(item)
elif self.shape is None:
if self.shape is None:
raise TypeError("Meta object does not have a shape and so cannot be sliced.")
else:
new_meta = copy.deepcopy(self)
# Convert item to array of ints and slices for consistent behaviour.
if isinstance(item, (numbers.Integral, slice)):
item = [item]
item = np.array(list(item) + [slice(None)] * (len(self.shape) - len(item)), dtype=object)

# Edit data shape and calculate which axis will be dropped.
dropped_axes = np.zeros(len(self.shape), dtype=bool)
new_shape = new_meta.shape
for i, axis_item in enumerate(item):
if isinstance(axis_item, numbers.Integral):
dropped_axes[i] = True
elif isinstance(axis_item, slice):
start = axis_item.start
if start is None:
start = 0
if start < 0:
start = self.shape[i] - start
stop = axis_item.stop
if stop is None:
stop = self.shape[i]
if stop < 0:
stop = self.shape[i] - stop
new_shape[i] = stop - start
else:
raise TypeError("Unrecognized slice type. " "Must be an int, slice and tuple of the same.")
new_meta._data_shape = new_shape[np.invert(dropped_axes)]

# Calculate the cumulative number of dropped axes.
cumul_dropped_axes = np.cumsum(dropped_axes)

# Slice all metadata associated with axes.
for key, value in self.items():
axis = self.axes.get(key, None)
if axis is not None:
new_item = tuple(item[axis])
new_value = value[new_item[0]] if len(new_item) == 1 else value[new_item]
new_axis = np.array([-1 if isinstance(i, numbers.Integral) else a for i, a in zip(new_item, axis)])
new_axis -= cumul_dropped_axes[axis]
new_axis = new_axis[new_axis >= 0]
if len(new_axis) == 0:
new_axis = None
new_meta.add(key, new_value, self.comments.get(key, None), new_axis, overwrite=True)

return new_meta
new_meta = copy.deepcopy(self)
# Convert item to array of ints and slices for consistent behaviour.
if isinstance(item, (numbers.Integral, slice)):
item = [item]
item = np.array(list(item) + [slice(None)] * (len(self.shape) - len(item)), dtype=object)

# Edit data shape and calculate which axis will be dropped.
dropped_axes = np.zeros(len(self.shape), dtype=bool)
new_shape = new_meta.shape
for i, axis_item in enumerate(item):
if isinstance(axis_item, numbers.Integral):
dropped_axes[i] = True
elif isinstance(axis_item, slice):
start = axis_item.start
if start is None:
start = 0
if start < 0:
start = self.shape[i] - start
stop = axis_item.stop
if stop is None:
stop = self.shape[i]
if stop < 0:
stop = self.shape[i] - stop
new_shape[i] = stop - start
else:
raise TypeError("Unrecognized slice type. " "Must be an int, slice and tuple of the same.")
new_meta._data_shape = new_shape[np.invert(dropped_axes)]

# Calculate the cumulative number of dropped axes.
cumul_dropped_axes = np.cumsum(dropped_axes)

# Slice all metadata associated with axes.
for key, value in self.items():
axis = self.axes.get(key, None)
if axis is not None:
new_item = tuple(item[axis])
new_value = value[new_item[0]] if len(new_item) == 1 else value[new_item]
new_axis = np.array([-1 if isinstance(i, numbers.Integral) else a for i, a in zip(new_item, axis)])
new_axis -= cumul_dropped_axes[axis]
new_axis = new_axis[new_axis >= 0]
if len(new_axis) == 0:
new_axis = None
new_meta.add(key, new_value, self.comments.get(key, None), new_axis, overwrite=True)

return new_meta
9 changes: 3 additions & 6 deletions sunraster/extern/tests/test_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def assert_metas_equal(test_input, expected_output):
for test_value, expected_value in zip(test_input.values(), expected_output.values()):
try:
assert test_value == expected_value
except ValueError as err:
except ValueError as err: # NOQA: PERF203
if multi_element_msg in err.args[0]:
assert np.allclose(test_value, expected_value)
# Check axes are the same.
Expand Down Expand Up @@ -109,18 +109,15 @@ def test_slice_away_independent_axis(basic_meta):
axes["c"] -= 1
axes["d"] -= 1
shape = meta.shape[1:]
print(values, comments, axes, shape)
expected = Meta(values, comments, axes, shape)
# Compare output and expected.
assert_metas_equal(output, expected)


def test_slice_dependent_axes(basic_meta):
meta = basic_meta
print(meta["a"])
# Get output
output = meta[:, 1:3, 1]
print(meta["a"])
# Build expected result.
values = dict(list(meta.items()))
values["c"] = values["c"][1:3, 1]
Expand Down Expand Up @@ -175,13 +172,13 @@ def test_add_overwrite(basic_meta):

def test_add_overwrite_error(basic_meta):
meta = basic_meta
with pytest.raises(KeyError):
with pytest.raises(KeyError, match="A"):
meta.add("a", "world", None, None)


def test_add_axis_without_shape(no_shape_meta):
meta = no_shape_meta
with pytest.raises(TypeError):
with pytest.raises(TypeError, match="A"):
meta.add("z", [100], axis=0)


Expand Down
6 changes: 3 additions & 3 deletions sunraster/instr/spice.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def read_spice_l2_fits(filenames, windows=None, memmap=True, read_dumbbells=Fals
if len(filenames) > 1:
# Wrap windows from first file in lists
# so windows from other files can be appended.
cube_lists = dict([(key, [value]) for key, value in first_cubes.items()])
cube_lists = {key: [value] for key, value in first_cubes.items()}
# Get info from first file for consistency checks between files.
first_meta = _get_meta_from_last_added(cube_lists)
first_obs_id = _get_obsid(first_meta)
Expand All @@ -79,7 +79,7 @@ def read_spice_l2_fits(filenames, windows=None, memmap=True, read_dumbbells=Fals
output=cube_lists,
spice_id=first_obs_id,
)
except ValueError as err:
except ValueError as err: # NOQA: PERF203
err_message = err.args[0]
if INCORRECT_OBSID_MESSAGE in err_message:
this_obs_id = err_message.split()[-1]
Expand Down Expand Up @@ -291,7 +291,7 @@ def __str__(self):
)

def __repr__(self):
return f"{object.__repr__(self)}\n{str(self)}"
return f"{object.__repr__(self)}\n{self!s}"

# ---------- Inherited ABC properties ----------
@property
Expand Down
98 changes: 49 additions & 49 deletions sunraster/instr/tests/test_spice.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import os.path

import numpy as np
import pytest

Expand All @@ -13,7 +11,7 @@

from sunraster import RasterSequence, SpectrogramCube, SpectrogramSequence
from sunraster.instr.spice import SPICEMeta, read_spice_l2_fits
from sunraster.tests import test_data_dir
from sunraster.tests import TEST_DATA_PATH

READ_SPICE_L2_FITS_RETURN_TYPE = NDCollection
SPECTRAL_WINDOW = ("WINDOW0_74.73", "Extension name")
Expand Down Expand Up @@ -53,35 +51,35 @@
@pytest.fixture
def spice_fits_header():
hdr = fits.Header()
hdr.append(tuple(["EXTNAME"] + list(SPECTRAL_WINDOW)))
hdr.append(tuple(["DETECTOR"] + list(DETECTOR)))
hdr.append(tuple(["INSTRUME"] + list(INSTRUMENT)))
hdr.append(tuple(["OBSRVTRY"] + list(OBSERVATORY)))
hdr.append(tuple(["LEVEL"] + list(PROCESSING_LEVEL)))
hdr.append(tuple(["RSUN_REF"] + list(RSUN_METERS)))
hdr.append(tuple(["RSUN_ARC"] + list(RSUN_ANGULAR)))
hdr.append(tuple(["OBS_ID"] + list(OBSERVING_MODE_ID)))
hdr.append(tuple(["OBS_VR"] + list(OBSERVATORY_RADIAL_VELOCITY)))
hdr.append(tuple(["DSUN_OBS"] + list(DISTANCE_TO_SUN)))
hdr.append(tuple(["DATE-OBS"] + list(DATE_REFERENCE)))
hdr.append(tuple(["DATE-BEG"] + list(DATE_START)))
hdr.append(tuple(["DATE-END"] + list(DATE_END)))
hdr.append(tuple(["HGLN_OBS"] + list(HGLN_OBS)))
hdr.append(tuple(["HGLT_OBS"] + list(HGLT_OBS)))
hdr.append(tuple(["SPIOBSID"] + list(SPICE_OBSERVING_MODE_ID)))
hdr.append(tuple(["DARKMAP"] + list(DARKMAP)))
hdr.append(tuple(["BLACKLEV"] + list(BLACKLEV)))
hdr.append(tuple(["WIN_TYPE"] + list(WINDOW_TYPE)))
hdr.append(tuple(["WINTABID"] + list(WINDOW_TABLE_ID)))
hdr.append(tuple(["SLIT_ID"] + list(SLIT_ID)))
hdr.append(tuple(["SLIT_WID"] + list(SLIT_WIDTH)))
hdr.append(tuple(["DUMBBELL"] + list(DUMBBELL)))
hdr.append(tuple(["SOLAR_B0"] + list(SOLAR_B0)))
hdr.append(tuple(["SOLAR_P0"] + list(SOLAR_P0)))
hdr.append(tuple(["SOLAR_EP"] + list(SOLAR_EP)))
hdr.append(tuple(["CAR_ROT"] + list(CARRINGTON_ROTATION_NUMBER)))
hdr.append(tuple(["DATE_EAR"] + list(DATE_START_EARTH)))
hdr.append(tuple(["DATE_SUN"] + list(DATE_START_SUN)))
hdr.append(("EXTNAME", *list(SPECTRAL_WINDOW)))
hdr.append(("DETECTOR", *list(DETECTOR)))
hdr.append(("INSTRUME", *list(INSTRUMENT)))
hdr.append(("OBSRVTRY", *list(OBSERVATORY)))
hdr.append(("LEVEL", *list(PROCESSING_LEVEL)))
hdr.append(("RSUN_REF", *list(RSUN_METERS)))
hdr.append(("RSUN_ARC", *list(RSUN_ANGULAR)))
hdr.append(("OBS_ID", *list(OBSERVING_MODE_ID)))
hdr.append(("OBS_VR", *list(OBSERVATORY_RADIAL_VELOCITY)))
hdr.append(("DSUN_OBS", *list(DISTANCE_TO_SUN)))
hdr.append(("DATE-OBS", *list(DATE_REFERENCE)))
hdr.append(("DATE-BEG", *list(DATE_START)))
hdr.append(("DATE-END", *list(DATE_END)))
hdr.append(("HGLN_OBS", *list(HGLN_OBS)))
hdr.append(("HGLT_OBS", *list(HGLT_OBS)))
hdr.append(("SPIOBSID", *list(SPICE_OBSERVING_MODE_ID)))
hdr.append(("DARKMAP", *list(DARKMAP)))
hdr.append(("BLACKLEV", *list(BLACKLEV)))
hdr.append(("WIN_TYPE", *list(WINDOW_TYPE)))
hdr.append(("WINTABID", *list(WINDOW_TABLE_ID)))
hdr.append(("SLIT_ID", *list(SLIT_ID)))
hdr.append(("SLIT_WID", *list(SLIT_WIDTH)))
hdr.append(("DUMBBELL", *list(DUMBBELL)))
hdr.append(("SOLAR_B0", *list(SOLAR_B0)))
hdr.append(("SOLAR_P0", *list(SOLAR_P0)))
hdr.append(("SOLAR_EP", *list(SOLAR_EP)))
hdr.append(("CAR_ROT", *list(CARRINGTON_ROTATION_NUMBER)))
hdr.append(("DATE_EAR", *list(DATE_START_EARTH)))
hdr.append(("DATE_SUN", *list(DATE_START_SUN)))
return hdr


Expand All @@ -101,20 +99,21 @@ def spice_rasdb_filename(tmp_path):
A new FITS file is saved in a tmp file path.
"""
rng_gen = np.random.default_rng()
filename = "solo_L2_spice-n-ras-db_20200602T081733_V01_12583760-000.fits"
with fits.open(os.path.join(test_data_dir, filename)) as hdulist:
with fits.open(TEST_DATA_PATH / filename) as hdulist:
new_hdulist = fits.HDUList()
new_hdulist.append(fits.PrimaryHDU(np.random.rand(1, 48, 832, 30), header=hdulist[0].header))
new_hdulist.append(fits.ImageHDU(np.random.rand(1, 48, 832, 30), header=hdulist[1].header))
new_hdulist.append(fits.ImageHDU(np.random.rand(1, 56, 64, 30), header=hdulist[2].header))
new_hdulist.append(fits.ImageHDU(np.random.rand(1, 56, 64, 30), header=hdulist[3].header))
new_hdulist.append(fits.PrimaryHDU(rng_gen.random((1, 48, 832, 30)), header=hdulist[0].header))
new_hdulist.append(fits.ImageHDU(rng_gen.random((1, 48, 832, 30)), header=hdulist[1].header))
new_hdulist.append(fits.ImageHDU(rng_gen.random((1, 56, 64, 30)), header=hdulist[2].header))
new_hdulist.append(fits.ImageHDU(rng_gen.random((1, 56, 64, 30)), header=hdulist[3].header))
new_hdulist.append(hdulist[-1])
tmp_spice_path = tmp_path / "spice"
if not os.path.exists(tmp_spice_path):
if not tmp_spice_path.exists():
tmp_spice_path.mkdir()
new_filename = os.path.join(tmp_spice_path, filename)
new_filename = tmp_spice_path / filename
new_hdulist.writeto(new_filename, overwrite=True)
return new_filename
return str(new_filename)


@pytest.fixture
Expand All @@ -124,16 +123,17 @@ def spice_sns_filename(tmp_path):
A new FITS file is saved in a tmp file path.
"""
rng_gen = np.random.default_rng()
filename = "solo_L2_spice-n-sit_20200620T235901_V01_16777431-000.fits"
with fits.open(os.path.join(test_data_dir, filename)) as hdulist:
with fits.open(TEST_DATA_PATH / filename) as hdulist:
new_hdulist = fits.HDUList()
new_hdulist.append(fits.PrimaryHDU(np.random.rand(32, 48, 1024, 1), header=hdulist[0].header))
new_hdulist.append(fits.ImageHDU(np.random.rand(32, 48, 1024, 1), header=hdulist[1].header))
new_hdulist.append(fits.PrimaryHDU(rng_gen.random((32, 48, 1024, 1)), header=hdulist[0].header))
new_hdulist.append(fits.ImageHDU(rng_gen.random((32, 48, 1024, 1)), header=hdulist[1].header))
new_hdulist.append(hdulist[-1])
tmp_spice_path = tmp_path / "spice"
if not os.path.exists(tmp_spice_path):
if not tmp_spice_path.exists():
tmp_spice_path.mkdir()
new_filename = os.path.join(tmp_spice_path, filename)
new_filename = tmp_spice_path / filename
new_hdulist.writeto(new_filename, output_verify="fix+ignore", overwrite=True)
return new_filename

Expand Down Expand Up @@ -216,11 +216,11 @@ def test_meta_observing_mode_id_solar_orbiter(spice_meta):


def test_meta_darkmap_subtracted_onboard(spice_meta):
assert spice_meta.darkmap_subtracted_onboard == False
assert spice_meta.darkmap_subtracted_onboard is False


def test_meta_bias_frame_subtracted_onboard(spice_meta):
assert spice_meta.bias_frame_subtracted_onboard == False
assert spice_meta.bias_frame_subtracted_onboard is False


def test_meta_window_type(spice_meta):
Expand Down Expand Up @@ -345,6 +345,6 @@ def test_read_spice_l2_fits_multiple_files_dumbbells(spice_rasdb_filename):


def test_read_spice_l2_fits_incompatible_files(spice_rasdb_filename, spice_sns_filename):
with pytest.raises(ValueError):
filenames = [spice_rasdb_filename, spice_sns_filename]
filenames = [spice_rasdb_filename, spice_sns_filename]
with pytest.raises(ValueError, match="A"):
read_spice_l2_fits(filenames)
Loading

0 comments on commit 2bfc160

Please sign in to comment.