Skip to content

Commit

Permalink
Merge pull request #1704 from larrybradley/repr
Browse files Browse the repository at this point in the history
Add __repr__ and __str__ strings for several classes
  • Loading branch information
larrybradley committed Feb 14, 2024
2 parents dbf6cb0 + 4451934 commit 3d0a4fe
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 6 deletions.
12 changes: 12 additions & 0 deletions photutils/background/background_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from photutils.background.interpolators import BkgZoomInterpolator
from photutils.utils import ShepardIDWInterpolator
from photutils.utils._parameters import as_pair
from photutils.utils._repr import make_repr
from photutils.utils._stats import nanmedian

__all__ = ['Background2D']
Expand Down Expand Up @@ -229,6 +230,17 @@ def __init__(self, data, box_size, *, mask=None, coverage_mask=None,

self._prepare_box_data()

self._params = ('data', 'box_size', 'mask', 'coverage_mask',
'fill_value', 'exclude_percentile', 'filter_size',
'filter_threshold', 'edge_method', 'sigma_clip',
'bkg_estimator', 'bkgrms_estimator', 'interpolator')

def __repr__(self):
return make_repr(self, self._params, ellipsis=('data',))

def __str__(self):
return make_repr(self, self._params, ellipsis=('data',), long=True)

def _validate_array(self, array, name, shape=True):
if name in ('mask', 'coverage_mask') and array is np.ma.nomask:
array = None
Expand Down
19 changes: 15 additions & 4 deletions photutils/background/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import numpy as np
from astropy.stats import SigmaClip, biweight_location, biweight_scale, mad_std

from photutils.utils._repr import make_repr
from photutils.utils._stats import nanmean, nanmedian, nanstd

SIGMA_CLIP = SigmaClip(sigma=3.0, maxiters=10)
Expand Down Expand Up @@ -41,8 +42,7 @@ def __init__(self, sigma_clip=SIGMA_CLIP):
self.sigma_clip = sigma_clip

def __repr__(self):
return (f'<{self.__class__.__name__}'
f'(sigma_clip={repr(self.sigma_clip)})>')
return make_repr(self, ('sigma_clip',))

def __call__(self, data, axis=None, masked=False):
return self.calc_background(data, axis=axis, masked=masked)
Expand Down Expand Up @@ -97,8 +97,7 @@ def __init__(self, sigma_clip=SIGMA_CLIP):
self.sigma_clip = sigma_clip

def __repr__(self):
return (f'<{self.__class__.__name__}'
f'(sigma_clip={repr(self.sigma_clip)})>')
return make_repr(self, ('sigma_clip',))

def __call__(self, data, axis=None, masked=False):
return self.calc_background_rms(data, axis=axis, masked=masked)
Expand Down Expand Up @@ -289,6 +288,10 @@ def __init__(self, median_factor=3.0, mean_factor=2.0, **kwargs):
self.median_factor = median_factor
self.mean_factor = mean_factor

def __repr__(self):
params = ('median_factor', 'mean_factor', 'sigma_clip')
return make_repr(self, params)

def calc_background(self, data, axis=None, masked=False):
if self.sigma_clip is not None:
data = self.sigma_clip(data, axis=axis, masked=False)
Expand Down Expand Up @@ -476,6 +479,10 @@ def __init__(self, c=6, M=None, **kwargs):
self.c = c
self.M = M

def __repr__(self):
params = ('c', 'M', 'sigma_clip')
return make_repr(self, params)

def calc_background(self, data, axis=None, masked=False):
if self.sigma_clip is not None:
data = self.sigma_clip(data, axis=axis, masked=False)
Expand Down Expand Up @@ -664,6 +671,10 @@ def __init__(self, c=9.0, M=None, **kwargs):
self.c = c
self.M = M

def __repr__(self):
params = ('c', 'M', 'sigma_clip')
return make_repr(self, params)

def calc_background_rms(self, data, axis=None, masked=False):
if self.sigma_clip is not None:
data = self.sigma_clip(data, axis=axis, masked=False)
Expand Down
9 changes: 9 additions & 0 deletions photutils/background/interpolators.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import numpy as np

from photutils.utils import ShepardIDWInterpolator
from photutils.utils._repr import make_repr

__all__ = ['BkgZoomInterpolator', 'BkgIDWInterpolator']

Expand Down Expand Up @@ -59,6 +60,10 @@ def __init__(self, *, order=3, mode='reflect', cval=0.0, grid_mode=True,
self.grid_mode = grid_mode
self.clip = clip

def __repr__(self):
params = ('order', 'mode', 'cval', 'grid_mode', 'clip')
return make_repr(self, params)

def __call__(self, mesh, bkg2d_obj):
"""
Resize the 2D mesh array.
Expand Down Expand Up @@ -139,6 +144,10 @@ def __init__(self, *, leafsize=10, n_neighbors=10, power=1.0, reg=0.0):
self.power = power
self.reg = reg

def __repr__(self):
params = ('leafsize', 'n_neighbors', 'power', 'reg')
return make_repr(self, params)

def __call__(self, mesh, bkg2d_obj):
"""
Resize the 2D mesh array.
Expand Down
5 changes: 5 additions & 0 deletions photutils/background/local_background.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from photutils.aperture import CircularAnnulus
from photutils.background import MedianBackground
from photutils.utils._repr import make_repr

__all__ = ['LocalBackground']

Expand Down Expand Up @@ -41,6 +42,10 @@ def __init__(self, inner_radius, outer_radius,
self.bkg_estimator = bkg_estimator
self._aperture = CircularAnnulus((0, 0), inner_radius, outer_radius)

def __repr__(self):
params = ('inner_radius', 'outer_radius', 'bkg_estimator')
return make_repr(self, params)

def __call__(self, data, x, y, mask=None):
"""
Measure the local background in a circular annulus.
Expand Down
6 changes: 6 additions & 0 deletions photutils/background/tests/test_background_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,12 @@ def test_crop(self):
assert_allclose(bkg.background_rms_median, 0.0)
assert_allclose(bkg.background_mesh.shape, (4, 5))

def test_repr(self):
data = np.ones((300, 500))
bkg = Background2D(data, (74, 99), edge_method='crop')
cls_repr = repr(bkg)
assert cls_repr.startswith(f'{bkg.__class__.__name__}')


@pytest.mark.skipif(not HAS_SCIPY, reason='scipy is required')
def test_bkgzoominterp_clip():
Expand Down
4 changes: 2 additions & 2 deletions photutils/background/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,12 @@ def test_background_repr(bkg_class):
bkg = bkg_class()
bkg_repr = repr(bkg)
assert bkg_repr == str(bkg)
assert bkg_repr.startswith(f'<{bkg.__class__.__name__}(sigma_clip=')
assert bkg_repr.startswith(f'{bkg.__class__.__name__}')


@pytest.mark.parametrize('rms_class', RMS_CLASS)
def test_background_rms_repr(rms_class):
bkgrms = rms_class()
rms_repr = repr(bkgrms)
assert rms_repr == str(bkgrms)
assert rms_repr.startswith(f'<{bkgrms.__class__.__name__}(sigma_clip=')
assert rms_repr.startswith(f'{bkgrms.__class__.__name__}')
42 changes: 42 additions & 0 deletions photutils/background/tests/test_interpolators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Tests for the interpolators module.
"""

import numpy as np
import pytest

from photutils.background.background_2d import Background2D
from photutils.background.interpolators import (BkgIDWInterpolator,
BkgZoomInterpolator)
from photutils.utils._optional_deps import HAS_SCIPY


@pytest.mark.skipif(not HAS_SCIPY, reason='scipy is required')
def test_zoom_interp():
bkg = Background2D(np.ones((300, 300)), 100)
mesh = np.array([[0.01, 0.01, 0.02],
[0.01, 0.02, 0.03],
[0.03, 0.03, 12.9]])

interp = BkgZoomInterpolator(clip=False)
zoom = interp(mesh, bkg)
assert zoom.shape == (300, 300)

cls_repr = repr(interp)
assert cls_repr.startswith(f'{interp.__class__.__name__}')


@pytest.mark.skipif(not HAS_SCIPY, reason='scipy is required')
def test_idw_interp():
bkg = Background2D(np.ones((300, 300)), 100)
mesh = np.array([[0.01, 0.01, 0.02],
[0.01, 0.02, 0.03],
[0.03, 0.03, 12.9]])

interp = BkgIDWInterpolator()
zoom = interp(mesh, bkg)
assert zoom.shape == (300, 300)

cls_repr = repr(interp)
assert cls_repr.startswith(f'{interp.__class__.__name__}')
3 changes: 3 additions & 0 deletions photutils/background/tests/test_local_background.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ def test_local_background():

with pytest.raises(ValueError):
_ = local_bkg(data, x[2], np.inf)

cls_repr = repr(local_bkg)
assert cls_repr.startswith(local_bkg.__class__.__name__)
6 changes: 6 additions & 0 deletions photutils/segmentation/finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from photutils.segmentation.deblend import deblend_sources
from photutils.segmentation.detect import detect_sources
from photutils.utils._parameters import as_pair
from photutils.utils._repr import make_repr

__all__ = ['SourceFinder']

Expand Down Expand Up @@ -164,6 +165,11 @@ def __init__(self, npixels, *, connectivity=8, deblend=True, nlevels=32,
self.nproc = nproc
self.progress_bar = progress_bar

def __repr__(self):
params = ('npixels', 'deblend', 'connectivity', 'nlevels', 'contrast',
'mode', 'relabel', 'nproc', 'progress_bar')
return make_repr(self, params)

def __call__(self, data, threshold, mask=None):
"""
Detect sources, including deblending, in an image using using
Expand Down
6 changes: 6 additions & 0 deletions photutils/segmentation/tests/test_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,9 @@ def test_npixels_tuple(self):
sf2 = SourceFinder(npixels=(200, 5))
segm2 = sf2(data, threshold=0.1)
assert segm2.nlabels == 3

def test_repr(self):
finder = SourceFinder(npixels=self.npixels, deblend=False,
progress_bar=False)
cls_repr = repr(finder)
assert cls_repr.startswith(finder.__class__.__name__)
33 changes: 33 additions & 0 deletions photutils/utils/_repr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
This module provides tools for class __repr__ and __str__ strings.
"""


def make_repr(self, params, ellipsis=(), long=False):
cls_name = f'{self.__class__.__name__}'
if long:
cls_name = f'{self.__class__.__module__}.{cls_name}'

cls_info = []
for param in params:
if param in ellipsis:
value = '...'
else:
value = getattr(self, param)
cls_info.append((param, value))

if long:
delim = ': '
join_str = '\n'
else:
delim = '='
join_str = ', '

fmt = [f'{key}{delim}{val!r}' for key, val in cls_info]
fmt = f'{join_str}'.join(fmt)

if long:
return f'<{cls_name}>\n{fmt}'
else:
return f'{cls_name}({fmt})'
7 changes: 7 additions & 0 deletions photutils/utils/depths.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from photutils.utils._coords import apply_separation
from photutils.utils._progress_bars import add_progress_bar
from photutils.utils._repr import make_repr
from photutils.utils.footprints import circular_footprint

__all__ = ['ImageDepth']
Expand Down Expand Up @@ -218,6 +219,12 @@ def __init__(self, aper_radius, *, nsigma=5.0, mask_pad=5, napers=1000,
self.flux_limits = np.array([])
self.mag_limits = np.array([])

def __repr__(self):
params = ('aper_radius', 'nsigma', 'mask_pad', 'napers', 'niters',
'overlap', 'overlap_maxiters', 'seed', 'zeropoint',
'sigma_clip', 'progress_bar')
return make_repr(self, params)

def __call__(self, data, mask):
"""
Calculate the limiting flux and magnitude of an image.
Expand Down
7 changes: 7 additions & 0 deletions photutils/utils/tests/test_depths.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,10 @@ def test_inputs(self):
ImageDepth(12.4, nsigma=5.0, napers=500, niters=2,
mask_pad=-7.1, overlap=True, seed=123, zeropoint=23.9,
progress_bar=False)

def test_repr(self):
depth = ImageDepth(aper_radius=4, nsigma=5.0, napers=100, niters=2,
overlap=False, seed=123, zeropoint=23.9,
progress_bar=False)
cls_repr = repr(depth)
assert cls_repr.startswith(f'{depth.__class__.__name__}')

0 comments on commit 3d0a4fe

Please sign in to comment.