Skip to content

Commit a680d86

Browse files
committed
Add color from fgcm for fitting a PSF color term in Piff.
1 parent 03df021 commit a680d86

File tree

2 files changed

+159
-9
lines changed

2 files changed

+159
-9
lines changed

python/lsst/pipe/tasks/finalizeCharacterization.py

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535
]
3636

3737
import astropy.table
38+
import astropy.units as u
3839
import numpy as np
3940
import esutil
41+
from smatch.matcher import Matcher
4042

4143

4244
import lsst.pex.config as pexConfig
@@ -81,6 +83,22 @@ class FinalizeCharacterizationConnectionsBase(
8183
deferLoad=True,
8284
multiple=True,
8385
)
86+
fgcm_standard_star = pipeBase.connectionTypes.Input(
87+
doc=('Catalog of fgcm for color corrections, and indexes to the '
88+
'isolated_star_cats catalogs.'),
89+
name='fgcm_standard_star',
90+
storageClass='ArrowAstropy',
91+
dimensions=('instrument', 'tract', 'skymap'),
92+
deferLoad=True,
93+
multiple=True,
94+
)
95+
96+
def __init__(self, *, config=None):
97+
super().__init__(config=config)
98+
if config is None:
99+
return None
100+
if not self.config.psf_determiner['piff'].useColor:
101+
self.inputs.remove("fgcm_standard_star")
84102

85103

86104
class FinalizeCharacterizationConnections(
@@ -327,6 +345,9 @@ def __init__(self, initInputs=None, **kwargs):
327345

328346
# Only log warning and fatal errors from the source_selector
329347
self.source_selector.log.setLevel(self.source_selector.log.WARN)
348+
self.isPsfDeterminerPiff = False
349+
if isinstance(self.psf_determiner, lsst.meas.extensions.piff.piffPsfDeterminer.PiffPsfDeterminerTask):
350+
self.isPsfDeterminerPiff = True
330351

331352
def _make_output_schema_mapper(self, input_schema):
332353
"""Make the schema mapper from the input schema to the output schema.
@@ -401,6 +422,17 @@ def _make_output_schema_mapper(self, input_schema):
401422
type=np.int32,
402423
doc='Detector number for the sources.',
403424
)
425+
output_schema.addField(
426+
'psf_color_value',
427+
type=np.float32,
428+
doc="Color used in PSF fit."
429+
)
430+
output_schema.addField(
431+
'psf_color_type',
432+
type=str,
433+
size=10,
434+
doc="Color used in PSF fit."
435+
)
404436

405437
alias_map = input_schema.getAliasMap()
406438
alias_map_output = afwTable.AliasMap()
@@ -434,6 +466,18 @@ def _make_selection_schema_mapper(self, input_schema):
434466

435467
selection_schema.setAliasMap(input_schema.getAliasMap())
436468

469+
selection_schema.addField(
470+
'psf_color_value',
471+
type=np.float32,
472+
doc="Color used in PSF fit."
473+
)
474+
selection_schema.addField(
475+
'psf_color_type',
476+
type=str,
477+
size=10,
478+
doc="Color used in PSF fit."
479+
)
480+
437481
return mapper, selection_schema
438482

439483
def concat_isolated_star_cats(self, band, isolated_star_cat_dict, isolated_star_source_dict):
@@ -542,7 +586,31 @@ def concat_isolated_star_cats(self, band, isolated_star_cat_dict, isolated_star_
542586

543587
return isolated_table, isolated_source_table
544588

545-
def compute_psf_and_ap_corr_map(self, visit, detector, exposure, src, isolated_source_table):
589+
def add_src_colors(self, srcCat, fgcmCat, band):
590+
591+
if self.isPsfDeterminerPiff and fgcmCat is not None:
592+
593+
raSrc = (srcCat['coord_ra'] * u.radian).to(u.degree).value
594+
decSrc = (srcCat['coord_dec'] * u.radian).to(u.degree).value
595+
596+
with Matcher(raSrc, decSrc) as matcher:
597+
idx, idxSrcCat, idxColorCat, d = matcher.query_radius(
598+
fgcmCat["ra"],
599+
fgcmCat["dec"],
600+
1. / 3600.0,
601+
return_indices=True,
602+
)
603+
604+
magStr1 = self.psf_determiner.config.color[band][0]
605+
magStr2 = self.psf_determiner.config.color[band][2]
606+
colors = fgcmCat[f'mag_{magStr1}'] - fgcmCat[f'mag_{magStr2}']
607+
608+
for idSrc, idColor in zip(idxSrcCat, idxColorCat):
609+
srcCat[idSrc]['psf_color_value'] = colors[idColor]
610+
srcCat[idSrc]['psf_color_type'] = f"{magStr1}-{magStr2}"
611+
612+
def compute_psf_and_ap_corr_map(self, visit, detector, exposure, src,
613+
isolated_source_table, fgcm_standard_star_cat):
546614
"""Compute psf model and aperture correction map for a single exposure.
547615
548616
Parameters
@@ -622,6 +690,12 @@ def compute_psf_and_ap_corr_map(self, visit, detector, exposure, src, isolated_s
622690
# We need to copy over the calib_psf flags because they were not in the mapper
623691
measured_src['calib_psf_candidate'] = selected_src['calib_psf_candidate']
624692
measured_src['calib_psf_reserved'] = selected_src['calib_psf_reserved']
693+
if exposure.filter.hasBandLabel():
694+
band = exposure.filter.bandLabel
695+
else:
696+
band = None
697+
self.add_src_colors(selected_src, fgcm_standard_star_cat, band)
698+
self.add_src_colors(measured_src, fgcm_standard_star_cat, band)
625699

626700
# Select the psf candidates from the selection catalog
627701
try:
@@ -639,6 +713,7 @@ def compute_psf_and_ap_corr_map(self, visit, detector, exposure, src, isolated_s
639713
in zip(psf_selection_result.psfCandidates,
640714
~psf_cand_cat['calib_psf_reserved']) if use]
641715
flag_key = psf_cand_cat.schema['calib_psf_used'].asKey()
716+
642717
try:
643718
psf, cell_set = self.psf_determiner.determinePsf(exposure,
644719
psf_determiner_list,
@@ -649,7 +724,7 @@ def compute_psf_and_ap_corr_map(self, visit, detector, exposure, src, isolated_s
649724
visit, detector, e)
650725
return None, None, measured_src
651726
# Verify that the PSF is usable by downstream tasks
652-
sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
727+
sigma = psf.computeShape(psf.getAveragePosition(), psf.getAverageColor()).getDeterminantRadius()
653728
if np.isnan(sigma):
654729
self.log.warning('Failed to determine psf for visit %d, detector %d: '
655730
'Computed final PSF size is NAN.',
@@ -727,19 +802,29 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs):
727802
isolated_star_source_dict = {tract: isolated_star_source_dict_temp[tract] for
728803
tract in sorted(isolated_star_source_dict_temp.keys())}
729804

805+
if self.config.psf_determiner['piff'].useColor:
806+
fgcm_standard_star_dict_temp = {handle.dataId['tract']: handle
807+
for handle in input_handle_dict['fgcm_standard_star']}
808+
fgcm_standard_star_dict = {tract: fgcm_standard_star_dict_temp[tract] for
809+
tract in sorted(fgcm_standard_star_dict_temp.keys())}
810+
else:
811+
fgcm_standard_star_dict = None
812+
730813
struct = self.run(
731814
visit,
732815
band,
733816
isolated_star_cat_dict,
734817
isolated_star_source_dict,
735818
src_dict,
736819
calexp_dict,
820+
fgcm_standard_star_dict=fgcm_standard_star_dict,
737821
)
738822

739823
butlerQC.put(struct.psf_ap_corr_cat, outputRefs.finalized_psf_ap_corr_cat)
740824
butlerQC.put(struct.output_table, outputRefs.finalized_src_table)
741825

742-
def run(self, visit, band, isolated_star_cat_dict, isolated_star_source_dict, src_dict, calexp_dict):
826+
def run(self, visit, band, isolated_star_cat_dict, isolated_star_source_dict,
827+
src_dict, calexp_dict, fgcm_standard_star_dict=None):
743828
"""
744829
Run the FinalizeCharacterizationTask.
745830
@@ -757,6 +842,8 @@ def run(self, visit, band, isolated_star_cat_dict, isolated_star_source_dict, sr
757842
Per-detector dict of src catalog handles.
758843
calexp_dict : `dict`
759844
Per-detector dict of calibrated exposure handles.
845+
fgcm_standard_star_dict : `dict`
846+
Per tract dict of fgcm isolated stars.
760847
761848
Returns
762849
-------
@@ -807,6 +894,19 @@ def run(self, visit, band, isolated_star_cat_dict, isolated_star_source_dict, sr
807894
measured_src_tables = []
808895
measured_src_table = None
809896

897+
if fgcm_standard_star_dict is not None:
898+
899+
fgcm_standard_star_cat = []
900+
901+
for tract in fgcm_standard_star_dict:
902+
astropy_fgcm = fgcm_standard_star_dict[tract].get()
903+
table_fgcm = np.asarray(astropy_fgcm)
904+
fgcm_standard_star_cat.append(table_fgcm)
905+
906+
fgcm_standard_star_cat = np.concatenate(fgcm_standard_star_cat)
907+
else:
908+
fgcm_standard_star_cat = None
909+
810910
self.log.info("Running finalizeCharacterization on %d detectors.", len(detector_keys))
811911
for detector in detector_keys:
812912
self.log.info("Starting finalizeCharacterization on detector ID %d.", detector)
@@ -818,7 +918,8 @@ def run(self, visit, band, isolated_star_cat_dict, isolated_star_source_dict, sr
818918
detector,
819919
exposure,
820920
src,
821-
isolated_source_table
921+
isolated_source_table,
922+
fgcm_standard_star_cat,
822923
)
823924

824925
# And now we package it together...
@@ -870,6 +971,14 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs):
870971
isolated_star_source_dict = {tract: isolated_star_source_dict_temp[tract] for
871972
tract in sorted(isolated_star_source_dict_temp.keys())}
872973

974+
if self.config.psf_determiner['piff'].useColor:
975+
fgcm_standard_star_dict_temp = {handle.dataId['tract']: handle
976+
for handle in input_handle_dict['fgcm_standard_star']}
977+
fgcm_standard_star_dict = {tract: fgcm_standard_star_dict_temp[tract] for
978+
tract in sorted(fgcm_standard_star_dict_temp.keys())}
979+
else:
980+
fgcm_standard_star_dict = None
981+
873982
struct = self.run(
874983
visit,
875984
band,
@@ -878,6 +987,7 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs):
878987
isolated_star_source_dict,
879988
input_handle_dict['src'],
880989
input_handle_dict['calexp'],
990+
fgcm_standard_star_dict=fgcm_standard_star_dict,
881991
)
882992

883993
butlerQC.put(
@@ -889,7 +999,8 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs):
889999
outputRefs.finalized_src_detector_table,
8901000
)
8911001

892-
def run(self, visit, band, detector, isolated_star_cat_dict, isolated_star_source_dict, src, exposure):
1002+
def run(self, visit, band, detector, isolated_star_cat_dict, isolated_star_source_dict,
1003+
src, exposure, fgcm_standard_star_dict=None):
8931004
"""
8941005
Run the FinalizeCharacterizationDetectorTask.
8951006
@@ -909,6 +1020,8 @@ def run(self, visit, band, detector, isolated_star_cat_dict, isolated_star_sourc
9091020
Src catalog.
9101021
exposure : `lsst.afw.image.Exposure`
9111022
Calexp exposure.
1023+
fgcm_standard_star_dict : `dict`
1024+
Per tract dict of fgcm isolated stars.
9121025
9131026
Returns
9141027
-------
@@ -925,7 +1038,7 @@ def run(self, visit, band, detector, isolated_star_cat_dict, isolated_star_sourc
9251038
_, isolated_source_table = self.concat_isolated_star_cats(
9261039
band,
9271040
isolated_star_cat_dict,
928-
isolated_star_source_dict
1041+
isolated_star_source_dict,
9291042
)
9301043

9311044
if isolated_source_table is None:
@@ -943,12 +1056,25 @@ def run(self, visit, band, detector, isolated_star_cat_dict, isolated_star_sourc
9431056

9441057
self.log.info("Starting finalizeCharacterization on detector ID %d.", detector)
9451058

1059+
if fgcm_standard_star_dict is not None:
1060+
fgcm_standard_star_cat = []
1061+
1062+
for tract in fgcm_standard_star_dict:
1063+
astropy_fgcm = fgcm_standard_star_dict[tract].get()
1064+
table_fgcm = np.asarray(astropy_fgcm)
1065+
fgcm_standard_star_cat.append(table_fgcm)
1066+
1067+
fgcm_standard_star_cat = np.concatenate(fgcm_standard_star_cat)
1068+
else:
1069+
fgcm_standard_star_cat = None
1070+
9461071
psf, ap_corr_map, measured_src = self.compute_psf_and_ap_corr_map(
9471072
visit,
9481073
detector,
9491074
exposure,
9501075
src,
9511076
isolated_source_table,
1077+
fgcm_standard_star_cat,
9521078
)
9531079

9541080
# And now we package it together...

0 commit comments

Comments
 (0)