35
35
]
36
36
37
37
import astropy .table
38
+ import astropy .units as u
38
39
import numpy as np
39
40
import esutil
41
+ from smatch .matcher import Matcher
40
42
41
43
42
44
import lsst .pex .config as pexConfig
@@ -81,6 +83,22 @@ class FinalizeCharacterizationConnectionsBase(
81
83
deferLoad = True ,
82
84
multiple = True ,
83
85
)
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" )
84
102
85
103
86
104
class FinalizeCharacterizationConnections (
@@ -327,6 +345,9 @@ def __init__(self, initInputs=None, **kwargs):
327
345
328
346
# Only log warning and fatal errors from the source_selector
329
347
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
330
351
331
352
def _make_output_schema_mapper (self , input_schema ):
332
353
"""Make the schema mapper from the input schema to the output schema.
@@ -401,6 +422,17 @@ def _make_output_schema_mapper(self, input_schema):
401
422
type = np .int32 ,
402
423
doc = 'Detector number for the sources.' ,
403
424
)
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
+ )
404
436
405
437
alias_map = input_schema .getAliasMap ()
406
438
alias_map_output = afwTable .AliasMap ()
@@ -434,6 +466,18 @@ def _make_selection_schema_mapper(self, input_schema):
434
466
435
467
selection_schema .setAliasMap (input_schema .getAliasMap ())
436
468
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
+
437
481
return mapper , selection_schema
438
482
439
483
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_
542
586
543
587
return isolated_table , isolated_source_table
544
588
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 ):
546
614
"""Compute psf model and aperture correction map for a single exposure.
547
615
548
616
Parameters
@@ -622,6 +690,12 @@ def compute_psf_and_ap_corr_map(self, visit, detector, exposure, src, isolated_s
622
690
# We need to copy over the calib_psf flags because they were not in the mapper
623
691
measured_src ['calib_psf_candidate' ] = selected_src ['calib_psf_candidate' ]
624
692
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 )
625
699
626
700
# Select the psf candidates from the selection catalog
627
701
try :
@@ -639,6 +713,7 @@ def compute_psf_and_ap_corr_map(self, visit, detector, exposure, src, isolated_s
639
713
in zip (psf_selection_result .psfCandidates ,
640
714
~ psf_cand_cat ['calib_psf_reserved' ]) if use ]
641
715
flag_key = psf_cand_cat .schema ['calib_psf_used' ].asKey ()
716
+
642
717
try :
643
718
psf , cell_set = self .psf_determiner .determinePsf (exposure ,
644
719
psf_determiner_list ,
@@ -649,7 +724,7 @@ def compute_psf_and_ap_corr_map(self, visit, detector, exposure, src, isolated_s
649
724
visit , detector , e )
650
725
return None , None , measured_src
651
726
# 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 ()
653
728
if np .isnan (sigma ):
654
729
self .log .warning ('Failed to determine psf for visit %d, detector %d: '
655
730
'Computed final PSF size is NAN.' ,
@@ -727,19 +802,29 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs):
727
802
isolated_star_source_dict = {tract : isolated_star_source_dict_temp [tract ] for
728
803
tract in sorted (isolated_star_source_dict_temp .keys ())}
729
804
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
+
730
813
struct = self .run (
731
814
visit ,
732
815
band ,
733
816
isolated_star_cat_dict ,
734
817
isolated_star_source_dict ,
735
818
src_dict ,
736
819
calexp_dict ,
820
+ fgcm_standard_star_dict = fgcm_standard_star_dict ,
737
821
)
738
822
739
823
butlerQC .put (struct .psf_ap_corr_cat , outputRefs .finalized_psf_ap_corr_cat )
740
824
butlerQC .put (struct .output_table , outputRefs .finalized_src_table )
741
825
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 ):
743
828
"""
744
829
Run the FinalizeCharacterizationTask.
745
830
@@ -757,6 +842,8 @@ def run(self, visit, band, isolated_star_cat_dict, isolated_star_source_dict, sr
757
842
Per-detector dict of src catalog handles.
758
843
calexp_dict : `dict`
759
844
Per-detector dict of calibrated exposure handles.
845
+ fgcm_standard_star_dict : `dict`
846
+ Per tract dict of fgcm isolated stars.
760
847
761
848
Returns
762
849
-------
@@ -807,6 +894,19 @@ def run(self, visit, band, isolated_star_cat_dict, isolated_star_source_dict, sr
807
894
measured_src_tables = []
808
895
measured_src_table = None
809
896
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
+
810
910
self .log .info ("Running finalizeCharacterization on %d detectors." , len (detector_keys ))
811
911
for detector in detector_keys :
812
912
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
818
918
detector ,
819
919
exposure ,
820
920
src ,
821
- isolated_source_table
921
+ isolated_source_table ,
922
+ fgcm_standard_star_cat ,
822
923
)
823
924
824
925
# And now we package it together...
@@ -870,6 +971,14 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs):
870
971
isolated_star_source_dict = {tract : isolated_star_source_dict_temp [tract ] for
871
972
tract in sorted (isolated_star_source_dict_temp .keys ())}
872
973
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
+
873
982
struct = self .run (
874
983
visit ,
875
984
band ,
@@ -878,6 +987,7 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs):
878
987
isolated_star_source_dict ,
879
988
input_handle_dict ['src' ],
880
989
input_handle_dict ['calexp' ],
990
+ fgcm_standard_star_dict = fgcm_standard_star_dict ,
881
991
)
882
992
883
993
butlerQC .put (
@@ -889,7 +999,8 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs):
889
999
outputRefs .finalized_src_detector_table ,
890
1000
)
891
1001
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 ):
893
1004
"""
894
1005
Run the FinalizeCharacterizationDetectorTask.
895
1006
@@ -909,6 +1020,8 @@ def run(self, visit, band, detector, isolated_star_cat_dict, isolated_star_sourc
909
1020
Src catalog.
910
1021
exposure : `lsst.afw.image.Exposure`
911
1022
Calexp exposure.
1023
+ fgcm_standard_star_dict : `dict`
1024
+ Per tract dict of fgcm isolated stars.
912
1025
913
1026
Returns
914
1027
-------
@@ -925,7 +1038,7 @@ def run(self, visit, band, detector, isolated_star_cat_dict, isolated_star_sourc
925
1038
_ , isolated_source_table = self .concat_isolated_star_cats (
926
1039
band ,
927
1040
isolated_star_cat_dict ,
928
- isolated_star_source_dict
1041
+ isolated_star_source_dict ,
929
1042
)
930
1043
931
1044
if isolated_source_table is None :
@@ -943,12 +1056,25 @@ def run(self, visit, band, detector, isolated_star_cat_dict, isolated_star_sourc
943
1056
944
1057
self .log .info ("Starting finalizeCharacterization on detector ID %d." , detector )
945
1058
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
+
946
1071
psf , ap_corr_map , measured_src = self .compute_psf_and_ap_corr_map (
947
1072
visit ,
948
1073
detector ,
949
1074
exposure ,
950
1075
src ,
951
1076
isolated_source_table ,
1077
+ fgcm_standard_star_cat ,
952
1078
)
953
1079
954
1080
# And now we package it together...
0 commit comments