LSST Applications 26.0.0,g0265f82a02+6660c170cc,g07994bdeae+30b05a742e,g0a0026dc87+17526d298f,g0a60f58ba1+17526d298f,g0e4bf8285c+96dd2c2ea9,g0ecae5effc+c266a536c8,g1e7d6db67d+6f7cb1f4bb,g26482f50c6+6346c0633c,g2bbee38e9b+6660c170cc,g2cc88a2952+0a4e78cd49,g3273194fdb+f6908454ef,g337abbeb29+6660c170cc,g337c41fc51+9a8f8f0815,g37c6e7c3d5+7bbafe9d37,g44018dc512+6660c170cc,g4a941329ef+4f7594a38e,g4c90b7bd52+5145c320d2,g58be5f913a+bea990ba40,g635b316a6c+8d6b3a3e56,g67924a670a+bfead8c487,g6ae5381d9b+81bc2a20b4,g93c4d6e787+26b17396bd,g98cecbdb62+ed2cb6d659,g98ffbb4407+81bc2a20b4,g9ddcbc5298+7f7571301f,ga1e77700b3+99e9273977,gae46bcf261+6660c170cc,gb2715bf1a1+17526d298f,gc86a011abf+17526d298f,gcf0d15dbbd+96dd2c2ea9,gdaeeff99f8+0d8dbea60f,gdb4ec4c597+6660c170cc,ge23793e450+96dd2c2ea9,gf041782ebf+171108ac67
LSST Data Management Base Package
Loading...
Searching...
No Matches
finalizeCharacterization.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22"""Task to run a finalized image characterization, using additional data.
23"""
24
25__all__ = ['FinalizeCharacterizationConnections',
26 'FinalizeCharacterizationConfig',
27 'FinalizeCharacterizationTask']
28
29import numpy as np
30import esutil
31import pandas as pd
32
33import lsst.pex.config as pexConfig
34import lsst.pipe.base as pipeBase
35import lsst.daf.base as dafBase
36import lsst.afw.table as afwTable
37import lsst.meas.algorithms as measAlg
39from lsst.meas.algorithms import MeasureApCorrTask
40from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask
41from lsst.meas.algorithms.sourceSelector import sourceSelectorRegistry
42
43from .reserveIsolatedStars import ReserveIsolatedStarsTask
44
45
46class FinalizeCharacterizationConnections(pipeBase.PipelineTaskConnections,
47 dimensions=('instrument', 'visit',),
48 defaultTemplates={}):
49 src_schema = pipeBase.connectionTypes.InitInput(
50 doc='Input schema used for src catalogs.',
51 name='src_schema',
52 storageClass='SourceCatalog',
53 )
54 srcs = pipeBase.connectionTypes.Input(
55 doc='Source catalogs for the visit',
56 name='src',
57 storageClass='SourceCatalog',
58 dimensions=('instrument', 'visit', 'detector'),
59 deferLoad=True,
60 multiple=True,
61 )
62 calexps = pipeBase.connectionTypes.Input(
63 doc='Calexps for the visit',
64 name='calexp',
65 storageClass='ExposureF',
66 dimensions=('instrument', 'visit', 'detector'),
67 deferLoad=True,
68 multiple=True,
69 )
70 isolated_star_cats = pipeBase.connectionTypes.Input(
71 doc=('Catalog of isolated stars with average positions, number of associated '
72 'sources, and indexes to the isolated_star_sources catalogs.'),
73 name='isolated_star_cat',
74 storageClass='DataFrame',
75 dimensions=('instrument', 'tract', 'skymap'),
76 deferLoad=True,
77 multiple=True,
78 )
79 isolated_star_sources = pipeBase.connectionTypes.Input(
80 doc=('Catalog of isolated star sources with sourceIds, and indexes to the '
81 'isolated_star_cats catalogs.'),
82 name='isolated_star_sources',
83 storageClass='DataFrame',
84 dimensions=('instrument', 'tract', 'skymap'),
85 deferLoad=True,
86 multiple=True,
87 )
88 finalized_psf_ap_corr_cat = pipeBase.connectionTypes.Output(
89 doc=('Per-visit finalized psf models and aperture corrections. This '
90 'catalog uses detector id for the id and are sorted for fast '
91 'lookups of a detector.'),
92 name='finalized_psf_ap_corr_catalog',
93 storageClass='ExposureCatalog',
94 dimensions=('instrument', 'visit'),
95 )
96 finalized_src_table = pipeBase.connectionTypes.Output(
97 doc=('Per-visit catalog of measurements for psf/flag/etc.'),
98 name='finalized_src_table',
99 storageClass='DataFrame',
100 dimensions=('instrument', 'visit'),
101 )
102
103
104class FinalizeCharacterizationConfig(pipeBase.PipelineTaskConfig,
105 pipelineConnections=FinalizeCharacterizationConnections):
106 """Configuration for FinalizeCharacterizationTask."""
107 source_selector = sourceSelectorRegistry.makeField(
108 doc="How to select sources",
109 default="science"
110 )
111 id_column = pexConfig.Field(
112 doc='Name of column in isolated_star_sources with source id.',
113 dtype=str,
114 default='sourceId',
115 )
116 reserve_selection = pexConfig.ConfigurableField(
117 target=ReserveIsolatedStarsTask,
118 doc='Task to select reserved stars',
119 )
120 make_psf_candidates = pexConfig.ConfigurableField(
121 target=measAlg.MakePsfCandidatesTask,
122 doc='Task to make psf candidates from selected stars.',
123 )
124 psf_determiner = measAlg.psfDeterminerRegistry.makeField(
125 'PSF Determination algorithm',
126 default='piff'
127 )
128 measurement = pexConfig.ConfigurableField(
129 target=SingleFrameMeasurementTask,
130 doc='Measure sources for aperture corrections'
131 )
132 measure_ap_corr = pexConfig.ConfigurableField(
133 target=MeasureApCorrTask,
134 doc="Subtask to measure aperture corrections"
135 )
136 apply_ap_corr = pexConfig.ConfigurableField(
137 target=ApplyApCorrTask,
138 doc="Subtask to apply aperture corrections"
139 )
140
141 def setDefaults(self):
142 super().setDefaults()
143
144 source_selector = self.source_selector['science']
145 source_selector.setDefaults()
146
147 # We use the source selector only to select out flagged objects
148 # and signal-to-noise. Isolated, unresolved sources are handled
149 # by the isolated star catalog.
150
151 source_selector.doFlags = True
152 source_selector.doSignalToNoise = True
153 source_selector.doFluxLimit = False
154 source_selector.doUnresolved = False
155 source_selector.doIsolated = False
156
157 source_selector.signalToNoise.minimum = 20.0
158 source_selector.signalToNoise.maximum = 1000.0
159
160 source_selector.signalToNoise.fluxField = 'base_GaussianFlux_instFlux'
161 source_selector.signalToNoise.errField = 'base_GaussianFlux_instFluxErr'
162
163 source_selector.flags.bad = ['base_PixelFlags_flag_edge',
164 'base_PixelFlags_flag_interpolatedCenter',
165 'base_PixelFlags_flag_saturatedCenter',
166 'base_PixelFlags_flag_crCenter',
167 'base_PixelFlags_flag_bad',
168 'base_PixelFlags_flag_interpolated',
169 'base_PixelFlags_flag_saturated',
170 'slot_Centroid_flag',
171 'base_GaussianFlux_flag']
172
173 # Configure aperture correction to select only high s/n sources (that
174 # were used in the psf modeling) to avoid background problems when
175 # computing the aperture correction map.
176 self.measure_ap_corr.sourceSelector = 'science'
177
178 ap_selector = self.measure_ap_corr.sourceSelector['science']
179 # We do not need to filter flags or unresolved because we have used
180 # the filtered isolated stars as an input
181 ap_selector.doFlags = False
182 ap_selector.doUnresolved = False
183
184 import lsst.meas.modelfit # noqa: F401
185 import lsst.meas.extensions.photometryKron # noqa: F401
186 import lsst.meas.extensions.convolved # noqa: F401
187 import lsst.meas.extensions.gaap # noqa: F401
188 import lsst.meas.extensions.shapeHSM # noqa: F401
189
190 # Set up measurement defaults
191 self.measurement.plugins.names = [
192 'base_PsfFlux',
193 'base_GaussianFlux',
194 'modelfit_DoubleShapeletPsfApprox',
195 'modelfit_CModel',
196 'ext_photometryKron_KronFlux',
197 'ext_convolved_ConvolvedFlux',
198 'ext_gaap_GaapFlux',
199 'ext_shapeHSM_HsmShapeRegauss',
200 'ext_shapeHSM_HsmSourceMoments',
201 'ext_shapeHSM_HsmPsfMoments',
202 'ext_shapeHSM_HsmSourceMomentsRound',
203 ]
204 self.measurement.slots.modelFlux = 'modelfit_CModel'
205 self.measurement.plugins['ext_convolved_ConvolvedFlux'].seeing.append(8.0)
206 self.measurement.plugins['ext_gaap_GaapFlux'].sigmas = [
207 0.5,
208 0.7,
209 1.0,
210 1.5,
211 2.5,
212 3.0
213 ]
214 self.measurement.plugins['ext_gaap_GaapFlux'].doPsfPhotometry = True
215 self.measurement.slots.shape = 'ext_shapeHSM_HsmSourceMoments'
216 self.measurement.slots.psfShape = 'ext_shapeHSM_HsmPsfMoments'
217 self.measurement.plugins['ext_shapeHSM_HsmShapeRegauss'].deblendNChild = ""
218 # Turn off slot setting for measurement for centroid and shape
219 # (for which we use the input src catalog measurements)
220 self.measurement.slots.centroid = None
221 self.measurement.slots.apFlux = None
222 self.measurement.slots.calibFlux = None
223
224 names = self.measurement.plugins['ext_convolved_ConvolvedFlux'].getAllResultNames()
225 self.measure_ap_corr.allowFailure += names
226 names = self.measurement.plugins["ext_gaap_GaapFlux"].getAllGaapResultNames()
227 self.measure_ap_corr.allowFailure += names
228
229
230class FinalizeCharacterizationTask(pipeBase.PipelineTask):
231 """Run final characterization on exposures."""
232 ConfigClass = FinalizeCharacterizationConfig
233 _DefaultName = 'finalize_characterization'
234
235 def __init__(self, initInputs=None, **kwargs):
236 super().__init__(initInputs=initInputs, **kwargs)
237
239 initInputs['src_schema'].schema
240 )
241
242 self.makeSubtask('reserve_selection')
243 self.makeSubtask('source_selector')
244 self.makeSubtask('make_psf_candidates')
245 self.makeSubtask('psf_determiner')
246 self.makeSubtask('measurement', schema=self.schema)
247 self.makeSubtask('measure_ap_corr', schema=self.schema)
248 self.makeSubtask('apply_ap_corr', schema=self.schema)
249
250 # Only log warning and fatal errors from the source_selector
251 self.source_selector.log.setLevel(self.source_selector.log.WARN)
252
253 def runQuantum(self, butlerQC, inputRefs, outputRefs):
254 input_handle_dict = butlerQC.get(inputRefs)
255
256 band = butlerQC.quantum.dataId['band']
257 visit = butlerQC.quantum.dataId['visit']
258
259 src_dict_temp = {handle.dataId['detector']: handle
260 for handle in input_handle_dict['srcs']}
261 calexp_dict_temp = {handle.dataId['detector']: handle
262 for handle in input_handle_dict['calexps']}
263 isolated_star_cat_dict_temp = {handle.dataId['tract']: handle
264 for handle in input_handle_dict['isolated_star_cats']}
265 isolated_star_source_dict_temp = {handle.dataId['tract']: handle
266 for handle in input_handle_dict['isolated_star_sources']}
267 # TODO: Sort until DM-31701 is done and we have deterministic
268 # dataset ordering.
269 src_dict = {detector: src_dict_temp[detector] for
270 detector in sorted(src_dict_temp.keys())}
271 calexp_dict = {detector: calexp_dict_temp[detector] for
272 detector in sorted(calexp_dict_temp.keys())}
273 isolated_star_cat_dict = {tract: isolated_star_cat_dict_temp[tract] for
274 tract in sorted(isolated_star_cat_dict_temp.keys())}
275 isolated_star_source_dict = {tract: isolated_star_source_dict_temp[tract] for
276 tract in sorted(isolated_star_source_dict_temp.keys())}
277
278 struct = self.run(visit,
279 band,
280 isolated_star_cat_dict,
281 isolated_star_source_dict,
282 src_dict,
283 calexp_dict)
284
285 butlerQC.put(struct.psf_ap_corr_cat,
286 outputRefs.finalized_psf_ap_corr_cat)
287 butlerQC.put(pd.DataFrame(struct.output_table),
288 outputRefs.finalized_src_table)
289
290 def run(self, visit, band, isolated_star_cat_dict, isolated_star_source_dict, src_dict, calexp_dict):
291 """
292 Run the FinalizeCharacterizationTask.
293
294 Parameters
295 ----------
296 visit : `int`
297 Visit number. Used in the output catalogs.
298 band : `str`
299 Band name. Used to select reserved stars.
300 isolated_star_cat_dict : `dict`
301 Per-tract dict of isolated star catalog handles.
302 isolated_star_source_dict : `dict`
303 Per-tract dict of isolated star source catalog handles.
304 src_dict : `dict`
305 Per-detector dict of src catalog handles.
306 calexp_dict : `dict`
307 Per-detector dict of calibrated exposure handles.
308
309 Returns
310 -------
311 struct : `lsst.pipe.base.struct`
312 Struct with outputs for persistence.
313 """
314 # We do not need the isolated star table in this task.
315 # However, it is used in tests to confirm consistency of indexes.
316 _, isolated_source_table = self.concat_isolated_star_cats(
317 band,
318 isolated_star_cat_dict,
319 isolated_star_source_dict
320 )
321
322 exposure_cat_schema = afwTable.ExposureTable.makeMinimalSchema()
323 exposure_cat_schema.addField('visit', type='L', doc='Visit number')
324
325 metadata = dafBase.PropertyList()
326 metadata.add("COMMENT", "Catalog id is detector id, sorted.")
327 metadata.add("COMMENT", "Only detectors with data have entries.")
328
329 psf_ap_corr_cat = afwTable.ExposureCatalog(exposure_cat_schema)
330 psf_ap_corr_cat.setMetadata(metadata)
331
332 measured_src_tables = []
333
334 for detector in src_dict:
335 src = src_dict[detector].get()
336 exposure = calexp_dict[detector].get()
337
338 psf, ap_corr_map, measured_src = self.compute_psf_and_ap_corr_map(
339 visit,
340 detector,
341 exposure,
342 src,
343 isolated_source_table
344 )
345
346 # And now we package it together...
347 record = psf_ap_corr_cat.addNew()
348 record['id'] = int(detector)
349 record['visit'] = visit
350 if psf is not None:
351 record.setPsf(psf)
352 if ap_corr_map is not None:
353 record.setApCorrMap(ap_corr_map)
354
355 measured_src['visit'][:] = visit
356 measured_src['detector'][:] = detector
357
358 measured_src_tables.append(measured_src.asAstropy().as_array())
359
360 measured_src_table = np.concatenate(measured_src_tables)
361
362 return pipeBase.Struct(psf_ap_corr_cat=psf_ap_corr_cat,
363 output_table=measured_src_table)
364
365 def _make_output_schema_mapper(self, input_schema):
366 """Make the schema mapper from the input schema to the output schema.
367
368 Parameters
369 ----------
370 input_schema : `lsst.afw.table.Schema`
371 Input schema.
372
373 Returns
374 -------
376 Schema mapper
377 output_schema : `lsst.afw.table.Schema`
378 Output schema (with alias map)
379 """
380 mapper = afwTable.SchemaMapper(input_schema)
381 mapper.addMinimalSchema(afwTable.SourceTable.makeMinimalSchema())
382 mapper.addMapping(input_schema['slot_Centroid_x'].asKey())
383 mapper.addMapping(input_schema['slot_Centroid_y'].asKey())
384
385 # The aperture fields may be used by the psf determiner.
386 aper_fields = input_schema.extract('base_CircularApertureFlux_*')
387 for field, item in aper_fields.items():
388 mapper.addMapping(item.key)
389
390 # The following two may be redundant, but then the mapping is a no-op.
391 apflux_fields = input_schema.extract('slot_ApFlux_*')
392 for field, item in apflux_fields.items():
393 mapper.addMapping(item.key)
394
395 calibflux_fields = input_schema.extract('slot_CalibFlux_*')
396 for field, item in calibflux_fields.items():
397 mapper.addMapping(item.key)
398
399 mapper.addMapping(
400 input_schema[self.config.source_selector.active.signalToNoise.fluxField].asKey(),
401 'calib_psf_selection_flux')
402 mapper.addMapping(
403 input_schema[self.config.source_selector.active.signalToNoise.errField].asKey(),
404 'calib_psf_selection_flux_err')
405
406 output_schema = mapper.getOutputSchema()
407
408 output_schema.addField(
409 'calib_psf_candidate',
410 type='Flag',
411 doc=('set if the source was a candidate for PSF determination, '
412 'as determined from FinalizeCharacterizationTask.'),
413 )
414 output_schema.addField(
415 'calib_psf_reserved',
416 type='Flag',
417 doc=('set if source was reserved from PSF determination by '
418 'FinalizeCharacterizationTask.'),
419 )
420 output_schema.addField(
421 'calib_psf_used',
422 type='Flag',
423 doc=('set if source was used in the PSF determination by '
424 'FinalizeCharacterizationTask.'),
425 )
426 output_schema.addField(
427 'visit',
428 type=np.int64,
429 doc='Visit number for the sources.',
430 )
431 output_schema.addField(
432 'detector',
433 type=np.int32,
434 doc='Detector number for the sources.',
435 )
436
437 alias_map = input_schema.getAliasMap()
438 alias_map_output = afwTable.AliasMap()
439 alias_map_output.set('slot_Centroid', alias_map.get('slot_Centroid'))
440 alias_map_output.set('slot_ApFlux', alias_map.get('slot_ApFlux'))
441 alias_map_output.set('slot_CalibFlux', alias_map.get('slot_CalibFlux'))
442
443 output_schema.setAliasMap(alias_map_output)
444
445 return mapper, output_schema
446
447 def _make_selection_schema_mapper(self, input_schema):
448 """Make the schema mapper from the input schema to the selection schema.
449
450 Parameters
451 ----------
452 input_schema : `lsst.afw.table.Schema`
453 Input schema.
454
455 Returns
456 -------
458 Schema mapper
459 selection_schema : `lsst.afw.table.Schema`
460 Selection schema (with alias map)
461 """
462 mapper = afwTable.SchemaMapper(input_schema)
463 mapper.addMinimalSchema(input_schema)
464
465 selection_schema = mapper.getOutputSchema()
466
467 selection_schema.setAliasMap(input_schema.getAliasMap())
468
469 return mapper, selection_schema
470
471 def concat_isolated_star_cats(self, band, isolated_star_cat_dict, isolated_star_source_dict):
472 """
473 Concatenate isolated star catalogs and make reserve selection.
474
475 Parameters
476 ----------
477 band : `str`
478 Band name. Used to select reserved stars.
479 isolated_star_cat_dict : `dict`
480 Per-tract dict of isolated star catalog handles.
481 isolated_star_source_dict : `dict`
482 Per-tract dict of isolated star source catalog handles.
483
484 Returns
485 -------
486 isolated_table : `np.ndarray` (N,)
487 Table of isolated stars, with indexes to isolated sources.
488 isolated_source_table : `np.ndarray` (M,)
489 Table of isolated sources, with indexes to isolated stars.
490 """
491 isolated_tables = []
492 isolated_sources = []
493 merge_cat_counter = 0
494 merge_source_counter = 0
495
496 for tract in isolated_star_cat_dict:
497 df_cat = isolated_star_cat_dict[tract].get()
498 table_cat = df_cat.to_records()
499
500 df_source = isolated_star_source_dict[tract].get(
501 parameters={'columns': [self.config.id_column,
502 'obj_index']}
503 )
504 table_source = df_source.to_records()
505
506 # Cut isolated star table to those observed in this band, and adjust indexes
507 (use_band,) = (table_cat[f'nsource_{band}'] > 0).nonzero()
508
509 if len(use_band) == 0:
510 # There are no sources in this band in this tract.
511 self.log.info("No sources found in %s band in tract %d.", band, tract)
512 continue
513
514 # With the following matching:
515 # table_source[b] <-> table_cat[use_band[a]]
516 obj_index = table_source['obj_index'][:]
517 a, b = esutil.numpy_util.match(use_band, obj_index)
518
519 # Update indexes and cut to band-selected stars/sources
520 table_source['obj_index'][b] = a
521 _, index_new = np.unique(a, return_index=True)
522 table_cat[f'source_cat_index_{band}'][use_band] = index_new
523
524 # After the following cuts, the catalogs have the following properties:
525 # - table_cat only contains isolated stars that have at least one source
526 # in ``band``.
527 # - table_source only contains ``band`` sources.
528 # - The slice table_cat["source_cat_index_{band}"]: table_cat["source_cat_index_{band}"]
529 # + table_cat["nsource_{band}]
530 # applied to table_source will give all the sources associated with the star.
531 # - For each source, table_source["obj_index"] points to the index of the associated
532 # isolated star.
533 table_source = table_source[b]
534 table_cat = table_cat[use_band]
535
536 # Add reserved flag column to tables
537 table_cat = np.lib.recfunctions.append_fields(
538 table_cat,
539 'reserved',
540 np.zeros(table_cat.size, dtype=bool),
541 usemask=False
542 )
543 table_source = np.lib.recfunctions.append_fields(
544 table_source,
545 'reserved',
546 np.zeros(table_source.size, dtype=bool),
547 usemask=False
548 )
549
550 # Get reserve star flags
551 table_cat['reserved'][:] = self.reserve_selection.run(
552 len(table_cat),
553 extra=f'{band}_{tract}',
554 )
555 table_source['reserved'][:] = table_cat['reserved'][table_source['obj_index']]
556
557 # Offset indexes to account for tract merging
558 table_cat[f'source_cat_index_{band}'] += merge_source_counter
559 table_source['obj_index'] += merge_cat_counter
560
561 isolated_tables.append(table_cat)
562 isolated_sources.append(table_source)
563
564 merge_cat_counter += len(table_cat)
565 merge_source_counter += len(table_source)
566
567 isolated_table = np.concatenate(isolated_tables)
568 isolated_source_table = np.concatenate(isolated_sources)
569
570 return isolated_table, isolated_source_table
571
572 def compute_psf_and_ap_corr_map(self, visit, detector, exposure, src, isolated_source_table):
573 """Compute psf model and aperture correction map for a single exposure.
574
575 Parameters
576 ----------
577 visit : `int`
578 Visit number (for logging).
579 detector : `int`
580 Detector number (for logging).
581 exposure : `lsst.afw.image.ExposureF`
583 isolated_source_table : `np.ndarray`
584
585 Returns
586 -------
588 PSF Model
589 ap_corr_map : `lsst.afw.image.ApCorrMap`
590 Aperture correction map.
591 measured_src : `lsst.afw.table.SourceCatalog`
592 Updated source catalog with measurements, flags and aperture corrections.
593 """
594 # Apply source selector (s/n, flags, etc.)
595 good_src = self.source_selector.selectSources(src)
596
597 # Cut down input src to the selected sources
598 # We use a separate schema/mapper here than for the output/measurement catalog because of
599 # clashes between fields that were previously run and those that need to be rerun with
600 # the new psf model. This may be slightly inefficient but keeps input
601 # and output values cleanly separated.
602 selection_mapper, selection_schema = self._make_selection_schema_mapper(src.schema)
603
604 selected_src = afwTable.SourceCatalog(selection_schema)
605 selected_src.reserve(good_src.selected.sum())
606 selected_src.extend(src[good_src.selected], mapper=selection_mapper)
607
608 # The calib flags have been copied from the input table,
609 # and we reset them here just to ensure they aren't propagated.
610 selected_src['calib_psf_candidate'] = np.zeros(len(selected_src), dtype=bool)
611 selected_src['calib_psf_used'] = np.zeros(len(selected_src), dtype=bool)
612 selected_src['calib_psf_reserved'] = np.zeros(len(selected_src), dtype=bool)
613
614 # Find the isolated sources and set flags
615 matched_src, matched_iso = esutil.numpy_util.match(
616 selected_src['id'],
617 isolated_source_table[self.config.id_column]
618 )
619
620 matched_arr = np.zeros(len(selected_src), dtype=bool)
621 matched_arr[matched_src] = True
622 selected_src['calib_psf_candidate'] = matched_arr
623
624 reserved_arr = np.zeros(len(selected_src), dtype=bool)
625 reserved_arr[matched_src] = isolated_source_table['reserved'][matched_iso]
626 selected_src['calib_psf_reserved'] = reserved_arr
627
628 selected_src = selected_src[selected_src['calib_psf_candidate']].copy(deep=True)
629
630 # Make the measured source catalog as well, based on the selected catalog.
631 measured_src = afwTable.SourceCatalog(self.schema)
632 measured_src.reserve(len(selected_src))
633 measured_src.extend(selected_src, mapper=self.schema_mapper)
634
635 # We need to copy over the calib_psf flags because they were not in the mapper
636 measured_src['calib_psf_candidate'] = selected_src['calib_psf_candidate']
637 measured_src['calib_psf_reserved'] = selected_src['calib_psf_reserved']
638
639 # Select the psf candidates from the selection catalog
640 try:
641 psf_selection_result = self.make_psf_candidates.run(selected_src, exposure=exposure)
642 except Exception as e:
643 self.log.warning('Failed to make psf candidates for visit %d, detector %d: %s',
644 visit, detector, e)
645 return None, None, measured_src
646
647 psf_cand_cat = psf_selection_result.goodStarCat
648
649 # Make list of psf candidates to send to the determiner
650 # (omitting those marked as reserved)
651 psf_determiner_list = [cand for cand, use
652 in zip(psf_selection_result.psfCandidates,
653 ~psf_cand_cat['calib_psf_reserved']) if use]
654 flag_key = psf_cand_cat.schema['calib_psf_used'].asKey()
655 try:
656 psf, cell_set = self.psf_determiner.determinePsf(exposure,
657 psf_determiner_list,
659 flagKey=flag_key)
660 except Exception as e:
661 self.log.warning('Failed to determine psf for visit %d, detector %d: %s',
662 visit, detector, e)
663 return None, None, measured_src
664
665 # Set the psf in the exposure for measurement/aperture corrections.
666 exposure.setPsf(psf)
667
668 # At this point, we need to transfer the psf used flag from the selection
669 # catalog to the measurement catalog.
670 matched_selected, matched_measured = esutil.numpy_util.match(
671 selected_src['id'],
672 measured_src['id']
673 )
674 measured_used = np.zeros(len(measured_src), dtype=bool)
675 measured_used[matched_measured] = selected_src['calib_psf_used'][matched_selected]
676 measured_src['calib_psf_used'] = measured_used
677
678 # Next, we do the measurement on all the psf candidate, used, and reserved stars.
679 try:
680 self.measurement.run(measCat=measured_src, exposure=exposure)
681 except Exception as e:
682 self.log.warning('Failed to make measurements for visit %d, detector %d: %s',
683 visit, detector, e)
684 return psf, None, measured_src
685
686 # And finally the ap corr map.
687 try:
688 ap_corr_map = self.measure_ap_corr.run(exposure=exposure,
689 catalog=measured_src).apCorrMap
690 except Exception as e:
691 self.log.warning('Failed to compute aperture corrections for visit %d, detector %d: %s',
692 visit, detector, e)
693 return psf, None, measured_src
694
695 self.apply_ap_corr.run(catalog=measured_src, apCorrMap=ap_corr_map)
696
697 return psf, ap_corr_map, measured_src
A thin wrapper around std::map to allow aperture corrections to be attached to Exposures.
Definition ApCorrMap.h:45
Mapping class that holds aliases for a Schema.
Definition AliasMap.h:36
Custom catalog class for ExposureRecord/Table.
Definition Exposure.h:311
Defines the fields and offsets for a table.
Definition Schema.h:51
A mapping between the keys of two Schemas, used to copy data between them.
Class for storing ordered metadata with comments.
An intermediate base class for Psfs that use an image representation.
Definition ImagePsf.h:40
run(self, visit, band, isolated_star_cat_dict, isolated_star_source_dict, src_dict, calexp_dict)
compute_psf_and_ap_corr_map(self, visit, detector, exposure, src, isolated_source_table)
concat_isolated_star_cats(self, band, isolated_star_cat_dict, isolated_star_source_dict)