22"""Read preprocessed bright stars and stack them to build an extended
26__all__ = [
"FocalPlaneRegionExtendedPsf",
"ExtendedPsf",
"StackBrightStarsConfig",
27 "StackBrightStarsTask",
"MeasureExtendedPsfConfig",
"MeasureExtendedPsfTask"]
29from dataclasses
import dataclass
30from typing
import List
32from lsst.afw import image
as afwImage
44 """Single extended PSF over a focal plane region.
46 The focal plane region is defined through a list
51 extended_psf_image : `lsst.afw.image.MaskedImageF`
52 Image of the extended PSF model.
53 detector_list : `list` [`int`]
54 List of detector IDs that define the focal plane region over which this
55 extended PSF model has been built (
and can be used).
57 extended_psf_image: afwImage.MaskedImageF
58 detector_list: List[int]
62 """Extended PSF model.
64 Each instance may contain a default extended PSF, a set of extended PSFs
65 that correspond to different focal plane regions, or both. At this time,
66 focal plane regions are always defined
as a subset of detectors.
70 default_extended_psf : `lsst.afw.image.MaskedImageF`
71 Extended PSF model to be used
as default (
or only) extended PSF model.
79 """Add a new focal plane region, along wit hits extended PSF, to the
84 extended_psf_image : `lsst.afw.image.MaskedImageF`
85 Extended PSF model for the region.
87 Name of the focal plane region. Will be converted to all-uppercase.
88 detector_list : `list` [`int`]
89 List of IDs
for the detectors that define the focal plane region.
91 region_name = region_name.upper()
93 raise ValueError(f
"Region name {region_name} is already used by this ExtendedPsf instance.")
95 extended_psf_image=extended_psf_image, detector_list=detector_list)
96 for det
in detector_list:
100 """Return the appropriate extended PSF.
102 If the instance contains no extended PSF defined over focal plane
103 regions, the default extended PSF will be returned regardless of
104 whether a detector ID was passed as argument.
108 detector : `int`, optional
109 Detector ID. If focal plane region PSFs are defined,
is used to
110 determine which model to
return.
114 extendedPsfImage : `lsst.afw.image.MaskedImageF`
115 The extended PSF model. If this instance contains extended PSFs
116 defined over focal plane regions, the extended PSF model
for the
117 region that contains ``detector``
is returned. If
not, the default
118 extended PSF
is returned.
122 raise ValueError(
"No default extended PSF available; please provide detector number.")
129 """Returns the number of extended PSF models present in the instance.
131 Note that if the instance contains both a default model
and a set of
132 focal plane region models, the length of the instance will be the
133 number of regional models, plus one (the default). This
is true even
134 in the case where the default model
is one of the focal plane
135 region-specific models.
143 """Returns the extended PSF for a focal plane region.
145 The region can be identified either by name, or through a detector ID.
149 region_name : `str`
or `
None`, optional
150 Name of the region
for which the extended PSF should be retrieved.
151 Ignored
if ``detector``
is provided. Must be provided
if
152 ``detector``
is None.
153 detector : `int`
or `
None`, optional
154 If provided, returns the extended PSF
for the focal plane region
155 that includes this detector.
160 Raised
if neither ``detector`` nor ``regionName``
is provided.
163 if region_name
is None:
164 raise ValueError(
"One of either a regionName or a detector number must be provided.")
169 """Write this object to a file.
174 Name of file to write.
180 metadata[
"HAS_REGIONS"] =
True
183 metadata[region] = e_psf_region.detector_list
185 metadata[
"HAS_REGIONS"] =
False
186 fits_primary = afwFits.Fits(filename,
"w")
187 fits_primary.createEmpty()
188 fits_primary.writeMetadata(metadata)
189 fits_primary.closeFile()
193 default_hdu_metadata.update({
"REGION":
"DEFAULT",
"EXTNAME":
"IMAGE"})
195 default_hdu_metadata.update({
"REGION":
"DEFAULT",
"EXTNAME":
"MASK"})
200 metadata.update({
"REGION": region,
"EXTNAME":
"IMAGE"})
201 e_psf_region.extended_psf_image.image.writeFits(filename, metadata=metadata, mode=
"a")
202 metadata.update({
"REGION": region,
"EXTNAME":
"MASK"})
203 e_psf_region.extended_psf_image.mask.writeFits(filename, metadata=metadata, mode=
"a")
206 """Alias for ``write_fits``; exists for compatibility with the Butler.
212 """Build an instance of this class from a file.
217 Name of the file to read.
220 global_metadata = afwFits.readMetadata(filename, hdu=0)
221 has_default = global_metadata.getBool(
"HAS_DEFAULT")
222 if global_metadata.getBool(
"HAS_REGIONS"):
223 focal_plane_region_names = global_metadata.getArray(
"REGION_NAMES")
225 focal_plane_region_names = []
226 f = afwFits.Fits(filename,
"r")
227 n_extensions = f.countHdus()
228 extended_psf_parts = {}
229 for j
in range(1, n_extensions):
230 md = afwFits.readMetadata(filename, hdu=j)
231 if has_default
and md[
"REGION"] ==
"DEFAULT":
232 if md[
"EXTNAME"] ==
"IMAGE":
233 default_image = afwImage.ImageF(filename, hdu=j)
234 elif md[
"EXTNAME"] ==
"MASK":
235 default_mask = afwImage.MaskX(filename, hdu=j)
237 if md[
"EXTNAME"] ==
"IMAGE":
238 extended_psf_part = afwImage.ImageF(filename, hdu=j)
239 elif md[
"EXTNAME"] ==
"MASK":
240 extended_psf_part = afwImage.MaskX(filename, hdu=j)
241 extended_psf_parts.setdefault(md[
"REGION"], {})[md[
"EXTNAME"].lower()] = extended_psf_part
244 extended_psf = cls(afwImage.MaskedImageF(default_image, default_mask))
248 if len(extended_psf_parts) != len(focal_plane_region_names):
249 raise ValueError(f
"Number of per-region extended PSFs read ({len(extended_psf_parts)}) does not "
250 "match with the number of regions recorded in the metadata "
251 f
"({len(focal_plane_region_names)}).")
253 for r_name
in focal_plane_region_names:
254 extended_psf_image = afwImage.MaskedImageF(**extended_psf_parts[r_name])
255 detector_list = global_metadata.getArray(r_name)
256 extended_psf.add_regional_extended_psf(extended_psf_image, r_name, detector_list)
262 """Alias for ``readFits``; exists for compatibility with the Butler.
268 """Configuration parameters for StackBrightStarsTask.
270 subregion_size = pexConfig.ListField(
272 doc="Size, in pixels, of the subregions over which the stacking will be "
273 "iteratively performed.",
276 stacking_statistic = pexConfig.ChoiceField(
278 doc=
"Type of statistic to use for stacking.",
283 "MEANCLIP":
"clipped mean",
286 num_sigma_clip = pexConfig.Field(
288 doc=
"Sigma for outlier rejection; ignored if stacking_statistic != 'MEANCLIP'.",
291 num_iter = pexConfig.Field(
293 doc=
"Number of iterations of outlier rejection; ignored if stackingStatistic != 'MEANCLIP'.",
296 bad_mask_planes = pexConfig.ListField(
298 doc=
"Mask planes that, if set, lead to associated pixels not being included in the stacking of the "
299 "bright star stamps.",
300 default=(
'BAD',
'CR',
'CROSSTALK',
'EDGE',
'NO_DATA',
'SAT',
'SUSPECT',
'UNMASKEDNAN')
302 do_mag_cut = pexConfig.Field(
304 doc=
"Apply magnitude cut before stacking?",
307 mag_limit = pexConfig.Field(
309 doc=
"Magnitude limit, in Gaia G; all stars brighter than this value will be stacked",
315 """Stack bright stars together to build an extended PSF model.
317 ConfigClass = StackBrightStarsConfig
318 _DefaultName = "stack_bright_stars"
320 def _set_up_stacking(self, example_stamp):
321 """Configure stacking statistic and control from config fields.
324 stats_control.setNumSigmaClip(self.config.num_sigma_clip)
325 stats_control.setNumIter(self.config.num_iter)
326 if bad_masks := self.config.bad_mask_planes:
327 and_mask = example_stamp.mask.getPlaneBitMask(bad_masks[0])
328 for bm
in bad_masks[1:]:
329 and_mask = and_mask | example_stamp.mask.getPlaneBitMask(bm)
330 stats_control.setAndMask(and_mask)
332 return stats_control, stats_flags
334 def run(self, bss_ref_list, region_name=None):
335 """Read input bright star stamps and stack them together.
337 The stacking is done iteratively over smaller areas of the final model
338 image to allow
for a great number of bright star stamps to be used.
342 bss_ref_list : `list` of
343 `lsst.daf.butler._deferredDatasetHandle.DeferredDatasetHandle`
344 List of available bright star stamps data references.
345 region_name : `str`, optional
346 Name of the focal plane region,
if applicable. Only used
for
347 logging purposes, when running over multiple such regions
348 (typically
from `MeasureExtendedPsfTask`)
351 region_message = f
' for region "{region_name}".'
354 self.log.info(
'Building extended PSF from stamps extracted from %d detector images%s',
355 len(bss_ref_list), region_message)
357 example_bss = bss_ref_list[0].get(datasetType=
"brightStarStamps", immediate=
True)
358 example_stamp = example_bss[0].stamp_im
360 ext_psf = afwImage.MaskedImageF(example_stamp.getBBox())
362 subregion_size = Extent2I(*self.config.subregion_size)
363 sub_bboxes = AssembleCoaddTask._subBBoxIter(ext_psf.getBBox(), subregion_size)
365 n_subregions = int(ext_psf.getDimensions()[0]/subregion_size[0] + 1)*int(
366 ext_psf.getDimensions()[1]/subregion_size[1] + 1)
367 self.log.info(
"Stacking will performed iteratively over approximately %d "
368 "smaller areas of the final model image.", n_subregions)
372 for jbbox, bbox
in enumerate(sub_bboxes):
374 for bss_ref
in bss_ref_list:
375 read_stars = bss_ref.get(datasetType=
"brightStarStamps", parameters={
'bbox': bbox})
376 if self.config.do_mag_cut:
377 read_stars = read_stars.selectByMag(magMax=self.config.mag_limit)
379 all_stars.extend(read_stars)
381 all_stars = read_stars
384 ext_psf.assign(coadd_sub_bbox, bbox)
389 dimensions=(
"band",
"instrument")):
390 input_brightStarStamps = pipeBase.connectionTypes.Input(
391 doc=
"Input list of bright star collections to be stacked.",
392 name=
"brightStarStamps",
393 storageClass=
"BrightStarStamps",
394 dimensions=(
"visit",
"detector"),
398 extended_psf = pipeBase.connectionTypes.Output(
399 doc=
"Extended PSF model built by stacking bright stars.",
401 storageClass=
"ExtendedPsf",
402 dimensions=(
"band",),
407 pipelineConnections=MeasureExtendedPsfConnections):
408 """Configuration parameters for MeasureExtendedPsfTask.
410 stack_bright_stars = pexConfig.ConfigurableField(
411 target=StackBrightStarsTask,
412 doc="Stack selected bright stars",
414 detectors_focal_plane_regions = pexConfig.DictField(
417 doc=
"Mapping from detector IDs to focal plane region names. If empty, a constant "
418 "extended PSF model is built from all selected bright stars.",
424 """Build and save extended PSF model.
426 The model is built by stacking bright star stamps, extracted
and
429 If a mapping
from detector IDs to focal plane regions
is provided,
430 a different extended PSF model will be built
for each focal plane
431 region. If
not, a single, constant extended PSF model
is built using
434 ConfigClass = MeasureExtendedPsfConfig
435 _DefaultName = "measureExtendedPsf"
437 def __init__(self, initInputs=None, *args, **kwargs):
438 pipeBase.Task.__init__(self, *args, **kwargs)
439 self.makeSubtask(
"stack_bright_stars")
441 set(self.config.detectors_focal_plane_regions.values())}
442 for det, region
in self.config.detectors_focal_plane_regions.items():
451 """Split available sets of bright star stamps according to focal plane
457 `lsst.daf.butler._deferredDatasetHandle.DeferredDatasetHandle`
458 List of available bright star stamps data references.
461 for dataset_handle
in ref_list:
462 det_id = dataset_handle.ref.dataId[
"detector"]
466 region_name = self.config.detectors_focal_plane_regions[det_id]
468 self.log.warning(
'Bright stars were available for detector %d, but it was missing '
469 'from the "detectors_focal_plane_regions" config field, so they will not '
470 'be used to build any of the extended PSF models', det_id)
473 region_ref_list[region_name].append(dataset_handle)
474 return region_ref_list
477 input_data = butlerQC.get(inputRefs)
478 bss_ref_list = input_data[
'input_brightStarStamps']
480 if not self.config.detectors_focal_plane_regions:
481 self.log.info(
"No detector groups were provided to MeasureExtendedPsfTask; computing a single, "
482 "constant extended PSF model over all available observations.")
483 output_e_psf =
ExtendedPsf(self.stack_bright_stars.run(bss_ref_list))
487 for region_name, ref_list
in region_ref_list.items():
490 self.log.warning(
'No valid brightStarStamps reference found for region "%s"; '
491 'skipping it.', region_name)
493 ext_psf = self.stack_bright_stars.run(ref_list, region_name)
494 output_e_psf.add_regional_extended_psf(ext_psf, region_name,
496 output = pipeBase.Struct(extended_psf=output_e_psf)
497 butlerQC.put(output, outputRefs)
std::vector< SchemaItem< Flag > > * items
Pass parameters to a Statistics object.
Class for storing ordered metadata with comments.
def writeFits(self, filename)
def readFits(cls, filename)
detectors_focal_plane_regions
def __call__(self, detector=None)
def get_regional_extended_psf(self, region_name=None, detector=None)
def add_regional_extended_psf(self, extended_psf_image, region_name, detector_list)
def read_fits(cls, filename)
def write_fits(self, filename)
def __init__(self, default_extended_psf=None)
def select_detector_refs(self, ref_list)
def __init__(self, initInputs=None, *args, **kwargs)
def runQuantum(self, butlerQC, inputRefs, outputRefs)
def _set_up_stacking(self, example_stamp)
daf::base::PropertyList * list
daf::base::PropertySet * set
std::shared_ptr< lsst::afw::image::Image< PixelT > > statisticsStack(std::vector< std::shared_ptr< lsst::afw::image::Image< PixelT > > > &images, Property flags, StatisticsControl const &sctrl=StatisticsControl(), std::vector< lsst::afw::image::VariancePixel > const &wvector=std::vector< lsst::afw::image::VariancePixel >(0))
A function to compute some statistics of a stack of Images.
Property stringToStatisticsProperty(std::string const property)
Conversion function to switch a string to a Property (see Statistics.h)