22__all__ = [
"BasePropertyMapConfig",
"PropertyMapRegistry",
"register_property_map",
23 "PropertyMapMap",
"BasePropertyMap",
"ExposureTimePropertyMap",
24 "PsfSizePropertyMap",
"PsfE1PropertyMap",
"PsfE2PropertyMap",
25 "NExposurePropertyMap",
"PsfMaglimPropertyMapConfig",
26 "PsfMaglimPropertyMap",
"SkyBackgroundPropertyMap",
"SkyNoisePropertyMap",
27 "DcrDraPropertyMap",
"DcrDdecPropertyMap",
"DcrE1PropertyMap",
28 "DcrE2PropertyMap",
"EpochPropertyMap",
"compute_approx_psf_size_and_shape"]
31import healsparse
as hsp
36from lsst.afw.math import ChebyshevBoundedField, ChebyshevBoundedFieldControl
40 do_min = pexConfig.Field(dtype=bool, default=
False,
41 doc=
"Compute map of property minima.")
42 do_max = pexConfig.Field(dtype=bool, default=
False,
43 doc=
"Compute map of property maxima.")
44 do_mean = pexConfig.Field(dtype=bool, default=
False,
45 doc=
"Compute map of property means.")
46 do_weighted_mean = pexConfig.Field(dtype=bool, default=
False,
47 doc=
"Compute map of weighted property means.")
48 do_sum = pexConfig.Field(dtype=bool, default=
False,
49 doc=
"Compute map of property sums.")
53 """Class for property map registry.
57 This code is based on `lsst.meas.base.PluginRegistry`.
60 """Class used as the element in the property map registry.
65 Name under which the property map is registered.
66 PropertyMapClass : subclass of `BasePropertyMap`
80 """Register a property map class with the given name.
85 The name of the property map.
86 PropertyMapClass : subclass of `BasePropertyMap`
88 pexConfig.Registry.register(self, name, self.
Configurable(name, PropertyMapClass))
92 """A decorator to register a property map class in its base class's registry."""
93 def decorate(PropertyMapClass):
94 PropertyMapClass.registry.register(name, PropertyMapClass)
95 return PropertyMapClass
100 """Compute the approximate psf size and shape.
102 This routine fits how the psf size and shape varies over a field by approximating
103 with a Chebyshev bounded field.
107 ccd_row : `lsst.afw.table.ExposureRecord`
108 Exposure metadata for a given detector exposure.
110 Right ascension of points to compute size and shape (degrees).
112 Declination of points to compute size and shape (degrees).
114 Number of sampling points in the x direction.
116 Number of sampling points in the y direction.
117 orderx : `int`, optional
118 Chebyshev polynomial order for fit in x direction.
119 ordery : `int`, optional
120 Chebyshev polynomial order for fit in y direction.
124 psf_array : `np.ndarray`
125 Record array with "psf_size", "psf_e1", "psf_e2".
128 r, d
in zip(ra, dec)]
129 pixels = ccd_row.getWcs().skyToPixel(pts)
134 ctrl.triangular =
False
136 bbox = ccd_row.getBBox()
137 xSteps = np.linspace(bbox.getMinX(), bbox.getMaxX(), nx)
138 ySteps = np.linspace(bbox.getMinY(), bbox.getMaxY(), ny)
139 x = np.tile(xSteps, nx)
140 y = np.repeat(ySteps, ny)
142 psf_size = np.zeros(x.size)
143 psf_e1 = np.zeros(x.size)
144 psf_e2 = np.zeros(x.size)
145 psf_area = np.zeros(x.size)
147 psf = ccd_row.getPsf()
148 for i
in range(x.size):
150 psf_size[i] = shape.getDeterminantRadius()
155 psf_e1[i] = (ixx - iyy)/(ixx + iyy + 2.*psf_size[i]**2.)
156 psf_e2[i] = (2.*ixy)/(ixx + iyy + 2.*psf_size[i]**2.)
159 psf_area[i] = np.sum(im.array)/np.sum(im.array**2.)
161 pixel_x = np.array([pix.getX()
for pix
in pixels])
162 pixel_y = np.array([pix.getY()
for pix
in pixels])
164 psf_array = np.zeros(pixel_x.size, dtype=[(
"psf_size",
"f8"),
170 good = np.isfinite(psf_size)
174 cheb_size = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_size[good], ctrl)
175 psf_array[
"psf_size"] = cheb_size.evaluate(pixel_x, pixel_y)
176 cheb_e1 = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_e1[good], ctrl)
177 psf_array[
"psf_e1"] = cheb_e1.evaluate(pixel_x, pixel_y)
178 cheb_e2 = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_e2[good], ctrl)
179 psf_array[
"psf_e2"] = cheb_e2.evaluate(pixel_x, pixel_y)
180 cheb_area = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_area[good], ctrl)
181 psf_array[
"psf_area"] = cheb_area.evaluate(pixel_x, pixel_y)
187 """Map of property maps to be run for a given task.
191 Property maps are classes derived from `BasePropertyMap`
194 for property_map
in self.values():
195 if (property_map.config.do_min
or property_map.config.do_max
or property_map.config.do_mean
196 or property_map.config.do_weighted_mean
or property_map.config.do_sum):
201 """Base class for property maps.
205 config : `BasePropertyMapConfig`
206 Property map configuration.
216 ConfigClass = BasePropertyMapConfig
221 object.__init__(self)
227 """Initialize the tract maps.
231 nside_coverage : `int`
232 Healpix nside of the healsparse coverage map.
234 Healpix nside of the property map.
241 metadata = copy.copy(base_metadata)
242 metadata[
"OPERATION"] =
"minimum"
243 self.
min_map = hsp.HealSparseMap.make_empty(nside_coverage,
248 metadata = copy.copy(base_metadata)
249 metadata[
"OPERATION"] =
"maximum"
250 self.
max_map = hsp.HealSparseMap.make_empty(nside_coverage,
255 metadata = copy.copy(base_metadata)
256 metadata[
"OPERATION"] =
"mean"
257 self.
mean_map = hsp.HealSparseMap.make_empty(nside_coverage,
261 if self.
config.do_weighted_mean:
262 metadata = copy.copy(base_metadata)
263 metadata[
"OPERATION"] =
"weighted mean"
269 metadata = copy.copy(base_metadata)
270 metadata[
"OPERATION"] =
"sum"
271 self.
sum_map = hsp.HealSparseMap.make_empty(nside_coverage,
277 """Initialize the value arrays for accumulation.
282 Number of pixels in the map.
293 if self.
config.do_weighted_mean:
300 """Accumulate values from a row of a visitSummary table.
304 indices : `np.ndarray`
305 Indices of values that should be accumulated.
307 Array of right ascension for indices
309 Array of declination for indices
310 weights : `float` or `np.ndarray`
311 Weight(s) for indices to be accumulated.
312 scalings : `float` or `np.ndarray`
313 Scaling values to coadd zeropoint.
314 row : `lsst.afw.table.ExposureRecord`
315 Row of a visitSummary ExposureCatalog.
316 psf_array : `np.ndarray`, optional
317 Array of approximate psf values matched to ra/dec.
321 ValueError : Raised if requires_psf is True and psf_array is None.
324 name = self.__class__.__name__
325 raise ValueError(f
"Cannot compute {name} without psf_array.")
327 values = self.
_compute(row, ra, dec, scalings, psf_array=psf_array)
334 if self.
config.do_weighted_mean:
340 """Finalize the accumulation of the mean and weighted mean.
344 total_weights : `np.ndarray`
345 Total accumulated weights, for each value index.
346 total_inputs : `np.ndarray`
347 Total number of inputs, for each value index.
350 use, = np.where(total_inputs > 0)
352 if self.
config.do_weighted_mean:
353 use, = np.where(total_weights > 0.0)
360 """Assign accumulated values to the maps.
364 pixels : `np.ndarray`
365 Array of healpix pixels (nest scheme) to set in the map.
373 if self.
config.do_weighted_mean:
378 def _compute(self, row, ra, dec, scalings, psf_array=None):
379 """Compute map value from a row in the visitSummary catalog.
383 row : `lsst.afw.table.ExposureRecord`
384 Row of a visitSummary ExposureCatalog.
386 Array of right ascensions
388 Array of declinations
389 scalings : `float` or `np.ndarray`
390 Scaling values to coadd zeropoint.
391 psf_array : `np.ndarray`, optional
392 Array of approximate psf values matched to ra/dec.
394 raise NotImplementedError(
"All property maps must implement _compute()")
397 """Perform post-processing on values.
401 total_weights : `np.ndarray`
402 Total accumulated weights, for each value index.
403 total_inputs : `np.ndarray`
404 Total number of inputs, for each value index.
412 """Exposure time property map."""
413 description =
"Exposure time"
416 def _compute(self, row, ra, dec, scalings, psf_array=None):
417 return row.getVisitInfo().getExposureTime()
420@register_property_map("psf_size")
422 """PSF size property map."""
423 description =
"PSF size"
427 def _compute(self, row, ra, dec, scalings, psf_array=None):
428 return psf_array[
"psf_size"]
431@register_property_map("psf_e1")
433 """PSF shape e1 property map."""
434 description =
"PSF e1"
437 def _compute(self, row, ra, dec, scalings, psf_array=None):
438 return psf_array[
"psf_e1"]
441@register_property_map("psf_e2")
443 """PSF shape e2 property map."""
444 description =
"PSF e2"
447 def _compute(self, row, ra, dec, scalings, psf_array=None):
448 return psf_array[
"psf_e2"]
451@register_property_map("n_exposure")
453 """Number of exposures property map."""
454 description =
"Number of exposures"
457 def _compute(self, row, ra, dec, scalings, psf_array=None):
462 """Configuration for the PsfMaglim property map."""
463 maglim_nsigma = pexConfig.Field(dtype=float, default=5.0,
464 doc=
"Number of sigma to compute magnitude limit.")
469 raise ValueError(
"Can only use do_weighted_mean with PsfMaglimPropertyMap")
472@register_property_map("psf_maglim")
474 """PSF magnitude limit property map."""
475 description =
"PSF magnitude limit"
479 ConfigClass = PsfMaglimPropertyMapConfig
481 def _compute(self, row, ra, dec, scalings, psf_array=None):
483 return psf_array[
"psf_area"]
488 - 2.5*np.log10(self.
config.maglim_nsigma*np.sqrt(psf_area/total_weights)))
492@register_property_map("sky_background")
494 """Sky background property map."""
495 description =
"Sky background"
498 def _compute(self, row, ra, dec, scalings, psf_array=None):
499 return scalings*row[
"skyBg"]
502@register_property_map("sky_noise")
504 """Sky noise property map."""
505 description =
"Sky noise"
508 def _compute(self, row, ra, dec, scalings, psf_array=None):
509 return scalings*row[
"skyNoise"]
512@register_property_map("dcr_dra")
514 """Effect of DCR on delta-RA property map."""
515 description =
"Effect of DCR on delta-RA"
517 def _compute(self, row, ra, dec, scalings, psf_array=None):
518 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
519 return np.tan(np.deg2rad(row[
"zenithDistance"]))*np.sin(par_angle)
522@register_property_map("dcr_ddec")
524 """Effect of DCR on delta-Dec property map."""
525 description =
"Effect of DCR on delta-Dec"
527 def _compute(self, row, ra, dec, scalings, psf_array=None):
528 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
529 return np.tan(np.deg2rad(row[
"zenithDistance"]))*np.cos(par_angle)
532@register_property_map("dcr_e1")
534 """Effect of DCR on psf shape e1 property map."""
535 description =
"Effect of DCR on PSF shape e1"
537 def _compute(self, row, ra, dec, scalings, psf_array=None):
538 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
539 return (np.tan(np.deg2rad(row[
"zenithDistance"]))**2.)*np.cos(2.*par_angle)
542@register_property_map("dcr_e2")
544 """Effect of DCR on psf shape e2 property map."""
545 description =
"Effect of DCR on PSF shape e2"
547 def _compute(self, row, ra, dec, scalings, psf_array=None):
548 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
549 return (np.tan(np.deg2rad(row[
"zenithDistance"]))**2.)*np.sin(2.*par_angle)
552@register_property_map("epoch")
554 """Observation epoch (mjd) property map."""
555 description =
"Observation epoch"
558 def _compute(self, row, ra, dec, scalings, psf_array=None):
559 date = row.getVisitInfo().getDate()
560 return date.get(date.MJD)
accumulate_values(self, indices, ra, dec, weights, scalings, row, psf_array=None)
set_map_values(self, pixels)
initialize_values(self, n_pixels)
_compute(self, row, ra, dec, scalings, psf_array=None)
_post_process(self, total_weights, total_inputs)
__init__(self, config, name)
initialize_tract_maps(self, nside_coverage, nside)
finalize_mean_values(self, total_weights, total_inputs)
_compute(self, row, ra, dec, scalings, psf_array=None)
_compute(self, row, ra, dec, scalings, psf_array=None)
_compute(self, row, ra, dec, scalings, psf_array=None)
_compute(self, row, ra, dec, scalings, psf_array=None)
_compute(self, row, ra, dec, scalings, psf_array=None)
_compute(self, row, ra, dec, scalings, psf_array=None)
_compute(self, row, ra, dec, scalings, psf_array=None)
__init__(self, name, PropertyMapClass)
register(self, name, PropertyMapClass)
_compute(self, row, ra, dec, scalings, psf_array=None)
_compute(self, row, ra, dec, scalings, psf_array=None)
_compute(self, row, ra, dec, scalings, psf_array=None)
_post_process(self, total_weights, total_inputs)
_compute(self, row, ra, dec, scalings, psf_array=None)
_compute(self, row, ra, dec, scalings, psf_array=None)
_compute(self, row, ra, dec, scalings, psf_array=None)
compute_approx_psf_size_and_shape(ccd_row, ra, dec, nx=20, ny=20, orderx=2, ordery=2)
register_property_map(name)