22"""Read preprocessed bright stars and stack to build an extended PSF model."""
25 "FocalPlaneRegionExtendedPsf",
27 "StackBrightStarsConfig",
28 "StackBrightStarsTask",
29 "MeasureExtendedPsfConfig",
30 "MeasureExtendedPsfTask",
33from dataclasses
import dataclass
34from typing
import List
38from lsst.afw.math import StatisticsControl, statisticsStack, stringToStatisticsProperty
41from lsst.pex.config import ChoiceField, Config, ConfigurableField, DictField, Field, ListField
42from lsst.pipe.base import PipelineTaskConfig, PipelineTaskConnections, Struct, Task
43from lsst.pipe.base.connectionTypes
import Input, Output
49 """Single extended PSF over a focal plane region.
51 The focal plane region is defined through a list of detectors.
55 extended_psf_image : `lsst.afw.image.MaskedImageF`
56 Image of the extended PSF model.
57 detector_list : `list` [`int`]
58 List of detector IDs that define the focal plane region over which this
59 extended PSF model has been built (
and can be used).
62 extended_psf_image: MaskedImageF
63 detector_list: List[int]
67 """Extended PSF model.
69 Each instance may contain a default extended PSF, a set of extended PSFs
70 that correspond to different focal plane regions, or both. At this time,
71 focal plane regions are always defined
as a subset of detectors.
75 default_extended_psf : `lsst.afw.image.MaskedImageF`
76 Extended PSF model to be used
as default (
or only) extended PSF model.
85 """Add a new focal plane region, along wit hits extended PSF, to the
90 extended_psf_image : `lsst.afw.image.MaskedImageF`
91 Extended PSF model for the region.
93 Name of the focal plane region. Will be converted to all-uppercase.
94 detector_list : `list` [`int`]
95 List of IDs
for the detectors that define the focal plane region.
97 region_name = region_name.upper()
99 raise ValueError(f
"Region name {region_name} is already used by this ExtendedPsf instance.")
101 extended_psf_image=extended_psf_image, detector_list=detector_list
103 for det
in detector_list:
107 """Return the appropriate extended PSF.
109 If the instance contains no extended PSF defined over focal plane
110 regions, the default extended PSF will be returned regardless of
111 whether a detector ID was passed as argument.
115 detector : `int`, optional
116 Detector ID. If focal plane region PSFs are defined,
is used to
117 determine which model to
return.
121 extendedPsfImage : `lsst.afw.image.MaskedImageF`
122 The extended PSF model. If this instance contains extended PSFs
123 defined over focal plane regions, the extended PSF model
for the
124 region that contains ``detector``
is returned. If
not, the default
125 extended PSF
is returned.
129 raise ValueError(
"No default extended PSF available; please provide detector number.")
136 """Returns the number of extended PSF models present in the instance.
138 Note that if the instance contains both a default model
and a set of
139 focal plane region models, the length of the instance will be the
140 number of regional models, plus one (the default). This
is true even
141 in the case where the default model
is one of the focal plane
142 region-specific models.
150 """Returns the extended PSF for a focal plane region.
152 The region can be identified either by name, or through a detector ID.
156 region_name : `str`
or `
None`, optional
157 Name of the region
for which the extended PSF should be retrieved.
158 Ignored
if ``detector``
is provided. Must be provided
if
159 ``detector``
is None.
160 detector : `int`
or `
None`, optional
161 If provided, returns the extended PSF
for the focal plane region
162 that includes this detector.
167 Raised
if neither ``detector`` nor ``regionName``
is provided.
170 if region_name
is None:
171 raise ValueError(
"One of either a regionName or a detector number must be provided.")
176 """Write this object to a file.
181 Name of file to write.
187 metadata[
"HAS_REGIONS"] =
True
190 metadata[region] = e_psf_region.detector_list
192 metadata[
"HAS_REGIONS"] =
False
193 fits_primary =
Fits(filename,
"w")
194 fits_primary.createEmpty()
195 fits_primary.writeMetadata(metadata)
196 fits_primary.closeFile()
200 default_hdu_metadata.update({
"REGION":
"DEFAULT",
"EXTNAME":
"IMAGE"})
202 default_hdu_metadata.update({
"REGION":
"DEFAULT",
"EXTNAME":
"MASK"})
207 metadata.update({
"REGION": region,
"EXTNAME":
"IMAGE"})
208 e_psf_region.extended_psf_image.image.writeFits(filename, metadata=metadata, mode=
"a")
209 metadata.update({
"REGION": region,
"EXTNAME":
"MASK"})
210 e_psf_region.extended_psf_image.mask.writeFits(filename, metadata=metadata, mode=
"a")
213 """Alias for ``write_fits``; for compatibility with the Butler."""
218 """Build an instance of this class from a file.
223 Name of the file to read.
226 global_metadata = readMetadata(filename, hdu=0)
227 has_default = global_metadata.getBool(
"HAS_DEFAULT")
228 if global_metadata.getBool(
"HAS_REGIONS"):
229 focal_plane_region_names = global_metadata.getArray(
"REGION_NAMES")
231 focal_plane_region_names = []
232 f =
Fits(filename,
"r")
233 n_extensions = f.countHdus()
234 extended_psf_parts = {}
235 for j
in range(1, n_extensions):
236 md = readMetadata(filename, hdu=j)
237 if has_default
and md[
"REGION"] ==
"DEFAULT":
238 if md[
"EXTNAME"] ==
"IMAGE":
239 default_image = ImageF(filename, hdu=j)
240 elif md[
"EXTNAME"] ==
"MASK":
241 default_mask = MaskX(filename, hdu=j)
243 if md[
"EXTNAME"] ==
"IMAGE":
244 extended_psf_part = ImageF(filename, hdu=j)
245 elif md[
"EXTNAME"] ==
"MASK":
246 extended_psf_part = MaskX(filename, hdu=j)
247 extended_psf_parts.setdefault(md[
"REGION"], {})[md[
"EXTNAME"].lower()] = extended_psf_part
250 extended_psf = cls(MaskedImageF(default_image, default_mask))
254 if len(extended_psf_parts) != len(focal_plane_region_names):
256 f
"Number of per-region extended PSFs read ({len(extended_psf_parts)}) does not "
257 "match with the number of regions recorded in the metadata "
258 f
"({len(focal_plane_region_names)})."
261 for r_name
in focal_plane_region_names:
262 extended_psf_image = MaskedImageF(**extended_psf_parts[r_name])
263 detector_list = global_metadata.getArray(r_name)
264 extended_psf.add_regional_extended_psf(extended_psf_image, r_name, detector_list)
270 """Alias for ``readFits``; exists for compatibility with the Butler."""
275 """Configuration parameters for StackBrightStarsTask."""
279 doc=
"Size, in pixels, of the subregions over which the stacking will be " "iteratively performed.",
284 doc=
"Type of statistic to use for stacking.",
289 "MEANCLIP":
"clipped mean",
294 doc=
"Sigma for outlier rejection; ignored if stacking_statistic != 'MEANCLIP'.",
299 doc=
"Number of iterations of outlier rejection; ignored if stackingStatistic != 'MEANCLIP'.",
304 doc=
"Mask planes that define pixels to be excluded from the stacking of the bright star stamps.",
305 default=(
"BAD",
"CR",
"CROSSTALK",
"EDGE",
"NO_DATA",
"SAT",
"SUSPECT",
"UNMASKEDNAN"),
309 doc=
"Apply magnitude cut before stacking?",
314 doc=
"Magnitude limit, in Gaia G; all stars brighter than this value will be stacked",
320 """Stack bright stars together to build an extended PSF model."""
322 ConfigClass = StackBrightStarsConfig
323 _DefaultName =
"stack_bright_stars"
326 """Configure stacking statistic and control from config fields."""
328 stats_control.setNumSigmaClip(self.config.num_sigma_clip)
329 stats_control.setNumIter(self.config.num_iter)
330 if bad_masks := self.config.bad_mask_planes:
331 and_mask = example_stamp.mask.getPlaneBitMask(bad_masks[0])
332 for bm
in bad_masks[1:]:
333 and_mask = and_mask | example_stamp.mask.getPlaneBitMask(bm)
334 stats_control.setAndMask(and_mask)
335 stats_flags = stringToStatisticsProperty(self.config.stacking_statistic)
336 return stats_control, stats_flags
338 def run(self, bss_ref_list, region_name=None):
339 """Read input bright star stamps and stack them together.
341 The stacking is done iteratively over smaller areas of the final model
342 image to allow
for a great number of bright star stamps to be used.
346 bss_ref_list : `list` of
347 `lsst.daf.butler._deferredDatasetHandle.DeferredDatasetHandle`
348 List of available bright star stamps data references.
349 region_name : `str`, optional
350 Name of the focal plane region,
if applicable. Only used
for
351 logging purposes, when running over multiple such regions
352 (typically
from `MeasureExtendedPsfTask`)
355 region_message = f
" for region '{region_name}'."
359 "Building extended PSF from stamps extracted from %d detector images%s",
364 example_bss = bss_ref_list[0].get()
365 example_stamp = example_bss[0].stamp_im
367 ext_psf = MaskedImageF(example_stamp.getBBox())
369 subregion_size = Extent2I(*self.config.subregion_size)
370 sub_bboxes = AssembleCoaddTask._subBBoxIter(ext_psf.getBBox(), subregion_size)
372 n_subregions = ((ext_psf.getDimensions()[0]) // (subregion_size[0] + 1)) * (
373 (ext_psf.getDimensions()[1]) // (subregion_size[1] + 1)
376 "Stacking performed iteratively over approximately %d smaller areas of the final model image.",
382 for jbbox, bbox
in enumerate(sub_bboxes):
384 for bss_ref
in bss_ref_list:
385 read_stars = bss_ref.get(parameters={
"bbox": bbox})
386 if self.config.do_mag_cut:
387 read_stars = read_stars.selectByMag(magMax=self.config.mag_limit)
389 all_stars.extend(read_stars)
391 all_stars = read_stars
393 coadd_sub_bbox = statisticsStack(all_stars.getMaskedImages(), stats_flags, stats_control)
394 ext_psf.assign(coadd_sub_bbox, bbox)
399 input_brightStarStamps = Input(
400 doc=
"Input list of bright star collections to be stacked.",
401 name=
"brightStarStamps",
402 storageClass=
"BrightStarStamps",
403 dimensions=(
"visit",
"detector"),
407 extended_psf = Output(
408 doc=
"Extended PSF model built by stacking bright stars.",
410 storageClass=
"ExtendedPsf",
411 dimensions=(
"band",),
416 """Configuration parameters for MeasureExtendedPsfTask."""
419 target=StackBrightStarsTask,
420 doc=
"Stack selected bright stars",
426 "Mapping from detector IDs to focal plane region names. If empty, a constant extended PSF model "
427 "is built from all selected bright stars."
434 """Build and save extended PSF model.
436 The model is built by stacking bright star stamps, extracted
and
440 If a mapping
from detector IDs to focal plane regions
is provided, a
441 different extended PSF model will be built
for each focal plane region. If
442 not, a single constant extended PSF model
is built
with all available data.
445 ConfigClass = MeasureExtendedPsfConfig
446 _DefaultName = "measureExtendedPsf"
448 def __init__(self, initInputs=None, *args, **kwargs):
450 self.makeSubtask(
"stack_bright_stars")
452 region: []
for region
in set(self.config.detectors_focal_plane_regions.values())
454 for det, region
in self.config.detectors_focal_plane_regions.items():
463 """Split available sets of bright star stamps according to focal plane
469 `lsst.daf.butler._deferredDatasetHandle.DeferredDatasetHandle`
470 List of available bright star stamps data references.
473 for dataset_handle
in ref_list:
474 det_id = dataset_handle.ref.dataId[
"detector"]
478 region_name = self.config.detectors_focal_plane_regions[det_id]
481 "Bright stars were available for detector %d, but it was missing from the %s config "
482 "field, so they will not be used to build any of the extended PSF models.",
484 "'detectors_focal_plane_regions'",
488 region_ref_list[region_name].append(dataset_handle)
489 return region_ref_list
492 input_data = butlerQC.get(inputRefs)
493 bss_ref_list = input_data[
"input_brightStarStamps"]
495 if not self.config.detectors_focal_plane_regions:
497 "No detector groups were provided to MeasureExtendedPsfTask; computing a single, "
498 "constant extended PSF model over all available observations."
500 output_e_psf =
ExtendedPsf(self.stack_bright_stars.run(bss_ref_list))
504 for region_name, ref_list
in region_ref_list.items():
508 "No valid brightStarStamps reference found for region '%s'; skipping it.",
512 ext_psf = self.stack_bright_stars.run(ref_list, region_name)
513 output_e_psf.add_regional_extended_psf(
516 output = Struct(extended_psf=output_e_psf)
517 butlerQC.put(output, outputRefs)
std::vector< SchemaItem< Flag > > * items
afw::table::PointKey< int > dimensions
A simple struct that combines the two arguments that must be passed to most cfitsio routines and cont...
Pass parameters to a Statistics object.
Class for storing ordered metadata with comments.
get_regional_extended_psf(self, region_name=None, detector=None)
add_regional_extended_psf(self, extended_psf_image, region_name, detector_list)
writeFits(self, filename)
__init__(self, default_extended_psf=None)
detectors_focal_plane_regions
write_fits(self, filename)
__call__(self, detector=None)
__init__(self, initInputs=None, *args, **kwargs)
runQuantum(self, butlerQC, inputRefs, outputRefs)
select_detector_refs(self, ref_list)
_set_up_stacking(self, example_stamp)
daf::base::PropertyList * list
daf::base::PropertySet * set