Skip to content

Commit

Permalink
Merge b59f94d into 1f3a066
Browse files Browse the repository at this point in the history
  • Loading branch information
Onoddil committed Aug 15, 2019
2 parents 1f3a066 + b59f94d commit 4886efa
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 10 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Bug Fixes
API changes
^^^^^^^^^^^

- ``photutils.psf``

- Added ``output_cols`` as a parameter to ``BasicPSFPhotometry``,
``IterativelySubtractedPSFPhotometry`` and ``DAOPhotPSFPhotometry``. [#745]


0.7 (2019-08-14)
----------------
Expand Down
72 changes: 62 additions & 10 deletions photutils/psf/photometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ class via the ``init_guesses`` keyword. For tabular PSF models
(e.g. an `EPSFModel`), you must input the ``aperture_radius``
keyword. For analytical PSF models, alternatively you may
define a FWHM attribute on your input psf_model.
extra_output_cols : list of str, optional
List of additional columns for parameters derived by any of the
intermediate fitting steps (e.g., ``finder``), such as roundness or
sharpness.
Notes
-----
Expand All @@ -119,7 +123,8 @@ class via the ``init_guesses`` keyword. For tabular PSF models
"""

def __init__(self, group_maker, bkg_estimator, psf_model, fitshape,
finder=None, fitter=LevMarLSQFitter(), aperture_radius=None):
finder=None, fitter=LevMarLSQFitter(), aperture_radius=None,
extra_output_cols=None):
self.group_maker = group_maker
self.bkg_estimator = bkg_estimator
self.psf_model = psf_model
Expand All @@ -130,6 +135,7 @@ def __init__(self, group_maker, bkg_estimator, psf_model, fitshape,
self._pars_to_set = None
self._pars_to_output = None
self._residual_image = None
self._extra_output_cols = extra_output_cols

@property
def fitshape(self):
Expand Down Expand Up @@ -218,7 +224,10 @@ def do_photometry(self, image, init_guesses=None):
used to estimate initial values for the fluxes. Additional
columns of the form '<parametername>_0' will be used to set
the initial guess for any parameters of the ``psf_model``
model that are not fixed.
model that are not fixed. If ``init_guesses`` supplied with
``extra_output_cols`` the initial values are used; if the columns
specified in ``extra_output_cols`` are not given in
``init_guesses`` then NaNs will be returned.
Returns
-------
Expand Down Expand Up @@ -284,6 +293,15 @@ def do_photometry(self, image, init_guesses=None):

init_guesses['flux_0'] = aperture_photometry(
image, apertures)['aperture_sum']

# if extra_output_cols have been given, check whether init_guesses
# was supplied with extra_output_cols pre-attached and populate
# columns not given with NaNs
if self._extra_output_cols is not None:
for col_name in self._extra_output_cols:
if col_name not in init_guesses.colnames:
init_guesses[col_name] = np.full(len(init_guesses),
np.nan)
else:
if self.finder is None:
raise ValueError('Finder cannot be None if init_guesses are '
Expand All @@ -298,11 +316,19 @@ def do_photometry(self, image, init_guesses=None):
sources['aperture_flux'] = aperture_photometry(
image, apertures)['aperture_sum']

# init_guesses should be the initial 3 required parameters --
# x, y, flux -- and then concatentated with any additional
# sources, if there are any
init_guesses = Table(names=['x_0', 'y_0', 'flux_0'],
data=[sources['xcentroid'],
sources['ycentroid'],
sources['aperture_flux']])

# Currently only needed for the finder, as group_maker and
# nstar return the original Table with new columns, unlike
# finder
self._get_additional_columns(sources, init_guesses)

self._define_fit_param_names()
for p0, param in self._pars_to_set.items():
if p0 not in init_guesses.colnames:
Expand Down Expand Up @@ -396,6 +422,18 @@ def nstar(self, image, star_groups):

return result_tab, image

def _get_additional_columns(self, in_table, out_table):
"""
Function to parse additional columns from ``in_table`` and add them to
``out_table``.
"""

if self._extra_output_cols is not None:
for col_name in self._extra_output_cols:
if col_name in in_table.colnames:
out_table[col_name] = in_table[col_name]

def _define_fit_param_names(self):
"""
Convenience function to define mappings between the names of the
Expand Down Expand Up @@ -451,7 +489,7 @@ def _get_uncertainties(self, star_group_size):
n_fit_params = len(unc_tab.colnames)
for i in range(star_group_size):
unc_tab[i] = np.sqrt(np.diag(
self.fitter.fit_info['param_cov'])
self.fitter.fit_info['param_cov'])
)[k: k + n_fit_params]
k = k + n_fit_params
return unc_tab
Expand Down Expand Up @@ -490,7 +528,8 @@ def _model_params2table(self, fit_model, star_group_size):
'_' + str(i)).value
else:
for param_tab_name, param_name in self._pars_to_output.items():
param_tab[param_tab_name] = getattr(fit_model, param_name).value
param_tab[param_tab_name] = getattr(fit_model,
param_name).value

return param_tab

Expand Down Expand Up @@ -563,6 +602,10 @@ class IterativelySubtractedPSFPhotometry(BasicPSFPhotometry):
stars remain. Note that in this case it is *possible* that the
loop will never end if the PSF has structure that causes
subtraction to create new sources infinitely.
extra_output_cols : list of str, optional
List of additional columns for parameters derived by any of the
intermediate fitting steps (e.g., ``finder``), such as roundness or
sharpness.
Notes
-----
Expand All @@ -581,10 +624,10 @@ class IterativelySubtractedPSFPhotometry(BasicPSFPhotometry):

def __init__(self, group_maker, bkg_estimator, psf_model, fitshape,
finder, fitter=LevMarLSQFitter(), niters=3,
aperture_radius=None):
aperture_radius=None, extra_output_cols=None):

super().__init__(group_maker, bkg_estimator, psf_model, fitshape,
finder, fitter, aperture_radius)
finder, fitter, aperture_radius, extra_output_cols)
self.niters = niters

@property
Expand Down Expand Up @@ -647,7 +690,10 @@ def do_photometry(self, image, init_guesses=None):
used to estimate initial values for the fluxes. Additional
columns of the form '<parametername>_0' will be used to set
the initial guess for any parameters of the ``psf_model``
model that are not fixed.
model that are not fixed. If ``init_guesses`` supplied with
``extra_output_cols`` the initial values are used; if the columns
specified in ``extra_output_cols`` are not given in
``init_guesses`` then NaNs will be returned.
Returns
-------
Expand Down Expand Up @@ -694,7 +740,7 @@ def _do_photometry(self, param_tab, n_start=1):
Parameters
----------
param_names : list
param_names : list
Names of the columns which represent the initial guesses.
For example, ['x_0', 'y_0', 'flux_0'], for intial guesses on
the center positions and the flux.
Expand Down Expand Up @@ -734,6 +780,7 @@ def _do_photometry(self, param_tab, n_start=1):
data=[sources['id'], sources['xcentroid'],
sources['ycentroid'],
sources['aperture_flux']])
self._get_additional_columns(sources, init_guess_tab)

for param_tab_name, param_name in self._pars_to_set.items():
if param_tab_name not in (['x_0', 'y_0', 'flux_0']):
Expand Down Expand Up @@ -855,6 +902,10 @@ class DAOPhotPSFPhotometry(IterativelySubtractedPSFPhotometry):
The radius (in units of pixels) used to compute initial
estimates for the fluxes of sources. If ``None``, one FWHM will
be used if it can be determined from the ```psf_model``.
extra_output_cols : list of str, optional
List of additional columns for parameters derived by any of the
intermediate fitting steps (e.g., ``finder``), such as roundness or
sharpness.
Notes
-----
Expand All @@ -875,7 +926,7 @@ def __init__(self, crit_separation, threshold, fwhm, psf_model, fitshape,
sigma=3., ratio=1.0, theta=0.0, sigma_radius=1.5,
sharplo=0.2, sharphi=1.0, roundlo=-1.0, roundhi=1.0,
fitter=LevMarLSQFitter(),
niters=3, aperture_radius=None):
niters=3, aperture_radius=None, extra_output_cols=None):

self.crit_separation = crit_separation
self.threshold = threshold
Expand All @@ -900,4 +951,5 @@ def __init__(self, crit_separation, threshold, fwhm, psf_model, fitshape,
super().__init__(group_maker=group_maker, bkg_estimator=bkg_estimator,
psf_model=psf_model, fitshape=fitshape,
finder=finder, fitter=fitter, niters=niters,
aperture_radius=aperture_radius)
aperture_radius=aperture_radius,
extra_output_cols=extra_output_cols)
49 changes: 49 additions & 0 deletions photutils/psf/tests/test_photometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,3 +602,52 @@ def test_psf_fitting_data_on_edge():
for n in ['x', 'y', 'flux']:
assert_allclose(outtab[n + '_0'], outtab[n + '_fit'],
rtol=0.05, atol=0.1)


@pytest.mark.skipif('not HAS_SCIPY')
@pytest.mark.parametrize("sigma_psf, sources", [(sigma_psfs[2], sources3)])
def test_psf_extra_output_cols(sigma_psf, sources):
"""
Test the handling of a non-None extra_output_cols
"""

psf_model = IntegratedGaussianPRF(sigma=sigma_psf)
tshape = (32, 32)
image = (make_gaussian_prf_sources_image(tshape, sources) +
make_noise_image(tshape, distribution='poisson', mean=6.,
random_state=1) +
make_noise_image(tshape, distribution='gaussian', mean=0.,
stddev=2., random_state=1))

init_guess1 = None
init_guess2 = Table(names=['x_0', 'y_0', 'sharpness', 'roundness1',
'roundness2'],
data=[[17.4], [16], [0.4], [0], [0]])
init_guess3 = Table(names=['x_0', 'y_0'],
data=[[17.4], [16]])
init_guess4 = Table(names=['x_0', 'y_0', 'sharpness'],
data=[[17.4], [16], [0.4]])
for init_guesses in [init_guess1, init_guess2, init_guess3]:
dao_phot = DAOPhotPSFPhotometry(crit_separation=8, threshold=40,
fwhm=2*2*np.sqrt(2*np.log(2)),
psf_model=psf_model, fitshape=(11, 11),
extra_output_cols=['sharpness',
'roundness1',
'roundness2'])
phot_results = dao_phot(image, init_guesses=init_guesses)
# test that the original required columns are also passed back, as well
# as extra_output_cols
assert np.all([name in phot_results.colnames for name in
['x_0', 'y_0']])
assert np.all([name in phot_results.colnames for name in
['sharpness', 'roundness1', 'roundness2']])
assert len(phot_results) == 2
# checks to verify that half-passing init_guesses results in NaN output
# for extra_output_cols not passed as initial guesses
if init_guesses == init_guess3:
assert(np.all(np.all(np.isnan(phot_results[o])) for o in
['sharpness', 'roundness1', 'roundness2']))
if init_guesses == init_guess4:
assert(np.all(np.all(np.isnan(phot_results[o])) for o in
['roundness1', 'roundness2']))
assert(np.all(~np.isnan(phot_results['sharpness'])))

0 comments on commit 4886efa

Please sign in to comment.