Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ New Features
^^^^^^^^^^^^

- Added an ASDF extension to provide converters for photutils aperture
and PSF classes. [#2211]
and PSF classes. [#2211, #2268]

Bug Fixes
^^^^^^^^^
Expand Down
22 changes: 11 additions & 11 deletions photutils/converters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
ASDF converters.
ASDF converters for photutils objects.

``CircularApertureConverter`` requires only ``asdf``. ``AiryDiskPSFConverter``
requires ``asdf-astropy`` for full functionality; it is always registered but
raises a clear ``ImportError`` when ``asdf-astropy`` is not installed.
"""

_ASDF_ASTROPY_INSTALLED = True
from .apertures import CircularApertureConverter # noqa: F401
from .functional_models import AiryDiskPSFConverter # noqa: F401

try:
import asdf_astropy # noqa: F401 -- needed to register the converters
import asdf_astropy # noqa: F401 -- only used to detect availability
except ImportError:
_ASDF_ASTROPY_INSTALLED = False
else:
_ASDF_ASTROPY_INSTALLED = True

if _ASDF_ASTROPY_INSTALLED:
from .functional_models import AiryDiskPSFConverter
from .apertures import CircularApertureConverter

__all__ = [
'AiryDiskPSFConverter',
'CircularApertureConverter',
]
__all__ = ['AiryDiskPSFConverter', 'CircularApertureConverter']
67 changes: 43 additions & 24 deletions photutils/converters/functional_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
Converters to and from the ASDF format for photutils.psf.functional_models.
"""

from . import _ASDF_ASTROPY_INSTALLED

if _ASDF_ASTROPY_INSTALLED:
try:
from asdf_astropy.converters.transform.core import (TransformConverterBase,
parameter_to_value)
else:
TransformConverterBase = object

_ASDF_ASTROPY_AVAILABLE = True
except ImportError:
from asdf.extension import Converter as TransformConverterBase

_ASDF_ASTROPY_AVAILABLE = False

__all__ = ['AiryDiskPSFConverter']

Expand All @@ -22,22 +24,39 @@ class AiryDiskPSFConverter(TransformConverterBase):
tags = ('tag:astropy.org:photutils/psf/airy_disk_psf-*',)
types = ('photutils.psf.AiryDiskPSF',)

def to_yaml_tree_transform(self, model, tag, ctx): # noqa: ARG002
return {
'flux': parameter_to_value(model.flux),
'x_0': parameter_to_value(model.x_0),
'y_0': parameter_to_value(model.y_0),
'radius': parameter_to_value(model.radius),
'bbox_factor': model.bbox_factor,
}

def from_yaml_tree_transform(self, node, tag, ctx): # noqa: ARG002
from photutils.psf import AiryDiskPSF

return AiryDiskPSF(
flux=node['flux'],
x_0=node['x_0'],
y_0=node['y_0'],
radius=node['radius'],
bbox_factor=node['bbox_factor'],
)
if _ASDF_ASTROPY_AVAILABLE:
def to_yaml_tree_transform(self, model, tag, ctx): # noqa: ARG002
return {
'flux': parameter_to_value(model.flux),
'x_0': parameter_to_value(model.x_0),
'y_0': parameter_to_value(model.y_0),
'radius': parameter_to_value(model.radius),
'bbox_factor': model.bbox_factor,
}

def from_yaml_tree_transform(self, node, tag, ctx): # noqa: ARG002
from photutils.psf import AiryDiskPSF

return AiryDiskPSF(
flux=node['flux'],
x_0=node['x_0'],
y_0=node['y_0'],
radius=node['radius'],
bbox_factor=node['bbox_factor'],
)
else:
def to_yaml_tree(self, obj, tag, ctx): # noqa: ARG002
msg = (
'asdf-astropy must be installed to serialize AiryDiskPSF '
'to ASDF format. Install it with:\n'
' pip install asdf-astropy'
)
raise ImportError(msg)

def from_yaml_tree(self, node, tag, ctx): # noqa: ARG002
msg = (
'asdf-astropy must be installed to deserialize AiryDiskPSF '
'from ASDF format. Install it with:\n'
' pip install asdf-astropy'
)
raise ImportError(msg)
3 changes: 0 additions & 3 deletions photutils/converters/tests/test_apertures.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@
import pytest

from photutils.aperture import CircularAperture
from photutils.converters import _ASDF_ASTROPY_INSTALLED

apertures = [
CircularAperture(positions=[(1, 2), (3, 4)], r=5),
CircularAperture(positions=(5, 6), r=7),
]


@pytest.mark.skipif(not _ASDF_ASTROPY_INSTALLED,
reason='asdf-astropy is not installed')
@pytest.mark.parametrize('aperture', apertures)
def test_aperture_converters(tmp_path, aperture):
"""
Expand Down
32 changes: 32 additions & 0 deletions photutils/converters/tests/test_psf.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,35 @@ def test_psf_converters(tmp_path, airy_disk_psf):
for parameter in pars:
assert_array_equal(getattr(psf, parameter),
getattr(psf2, parameter))


@pytest.mark.skipif(_ASDF_ASTROPY_INSTALLED,
reason='asdf-astropy is installed; error not expected')
def test_airy_disk_psf_serialize_no_asdf_astropy(tmp_path):
"""
Test that serializing AiryDiskPSF without asdf-astropy raises a
friendly ImportError.
"""
from photutils.psf import AiryDiskPSF

psf = AiryDiskPSF(flux=1, x_0=0, y_0=0, radius=5)
with asdf.AsdfFile() as af:
af['psf'] = psf
with pytest.raises(ImportError,
match='asdf-astropy must be installed'):
af.write_to(tmp_path / 'psf.asdf')


@pytest.mark.skipif(_ASDF_ASTROPY_INSTALLED,
reason='asdf-astropy is installed; error not expected')
def test_airy_disk_psf_deserialize_no_asdf_astropy():
"""
Test that deserializing AiryDiskPSF without asdf-astropy raises a
friendly ImportError.
"""
from photutils.converters.functional_models import AiryDiskPSFConverter

converter = AiryDiskPSFConverter()
with pytest.raises(ImportError,
match='asdf-astropy must be installed'):
converter.from_yaml_tree({}, None, None)
13 changes: 6 additions & 7 deletions photutils/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,23 @@
from asdf.extension import ManifestExtension
from asdf.resource import DirectoryResourceMapping

from .converters import apertures # import CircularApertureConverter
from .converters import functional_models # import AiryDiskPSFConverter
from .converters import apertures, functional_models

__all__ = [
'PHOTUTILS_APERTURE_CONVERTERS',
'PHOTUTILS_CONVERTERS',
'PHOTUTILS_MANIFEST_URIS',
'PHOTUTILS_PSF_CONVERTERS',
]

PHOTUTILS_PSF_CONVERTERS = [
functional_models.AiryDiskPSFConverter(),
]

PHOTUTILS_APERTURE_CONVERTERS = [
apertures.CircularApertureConverter(),
]
PHOTUTILS_PSF_CONVERTERS = [
functional_models.AiryDiskPSFConverter(),
]

PHOTUTILS_CONVERTERS = PHOTUTILS_PSF_CONVERTERS + PHOTUTILS_APERTURE_CONVERTERS
PHOTUTILS_CONVERTERS = PHOTUTILS_APERTURE_CONVERTERS + PHOTUTILS_PSF_CONVERTERS

# The order here is important; asdf will prefer to use extensions
# that occur earlier in the list.
Expand Down
Loading