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
35from lsst.afw.math import ChebyshevBoundedField, ChebyshevBoundedFieldControl
39 do_min = pexConfig.Field(dtype=bool, default=
False,
40 doc=
"Compute map of property minima.")
41 do_max = pexConfig.Field(dtype=bool, default=
False,
42 doc=
"Compute map of property maxima.")
43 do_mean = pexConfig.Field(dtype=bool, default=
False,
44 doc=
"Compute map of property means.")
45 do_weighted_mean = pexConfig.Field(dtype=bool, default=
False,
46 doc=
"Compute map of weighted property means.")
47 do_sum = pexConfig.Field(dtype=bool, default=
False,
48 doc=
"Compute map of property sums.")
52 """Class for property map registry.
59 """Class used as the element in the property map registry.
64 Name under which the property map is registered.
65 PropertyMapClass : subclass of `BasePropertyMap`
78 def register(self, name, PropertyMapClass):
79 """Register a property map class with the given name.
84 The name of the property map.
85 PropertyMapClass : subclass of `BasePropertyMap`
87 pexConfig.Registry.register(self, name, self.Configurable(name, PropertyMapClass))
91 """A decorator to register a property map class in its base class's registry."""
92 def decorate(PropertyMapClass):
93 PropertyMapClass.registry.register(name, PropertyMapClass)
94 return PropertyMapClass
98def compute_approx_psf_size_and_shape(ccd_row, ra, dec, nx=20, ny=20, orderx=2, ordery=2):
99 """Compute the approximate psf size and shape.
101 This routine fits how the psf size and shape varies over a field by approximating
102 with a Chebyshev bounded field.
107 Exposure metadata
for a given detector exposure.
109 Right ascension of points to compute size
and shape (degrees).
111 Declination of points to compute size
and shape (degrees).
113 Number of sampling points
in the x direction.
115 Number of sampling points
in the y direction.
116 orderx : `int`, optional
117 Chebyshev polynomial order
for fit
in x direction.
118 ordery : `int`, optional
119 Chebyshev polynomial order
for fit
in y direction.
123 psf_array : `np.ndarray`
124 Record array
with "psf_size",
"psf_e1",
"psf_e2".
127 r, d
in zip(ra, dec)]
128 pixels = ccd_row.getWcs().skyToPixel(pts)
133 ctrl.triangular =
False
135 bbox = ccd_row.getBBox()
136 xSteps = np.linspace(bbox.getMinX(), bbox.getMaxX(), nx)
137 ySteps = np.linspace(bbox.getMinY(), bbox.getMaxY(), ny)
138 x = np.tile(xSteps, nx)
139 y = np.repeat(ySteps, ny)
141 psf_size = np.zeros(x.size)
142 psf_e1 = np.zeros(x.size)
143 psf_e2 = np.zeros(x.size)
144 psf_area = np.zeros(x.size)
146 psf = ccd_row.getPsf()
147 for i
in range(x.size):
149 psf_size[i] = shape.getDeterminantRadius()
154 psf_e1[i] = (ixx - iyy)/(ixx + iyy + 2.*psf_size[i]**2.)
155 psf_e2[i] = (2.*ixy)/(ixx + iyy + 2.*psf_size[i]**2.)
158 psf_area[i] = np.sum(im.array)/np.sum(im.array**2.)
160 pixel_x = np.array([pix.getX()
for pix
in pixels])
161 pixel_y = np.array([pix.getY()
for pix
in pixels])
163 psf_array = np.zeros(pixel_x.size, dtype=[(
"psf_size",
"f8"),
169 good = np.isfinite(psf_size)
173 cheb_size = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_size[good], ctrl)
174 psf_array[
"psf_size"] = cheb_size.evaluate(pixel_x, pixel_y)
175 cheb_e1 = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_e1[good], ctrl)
176 psf_array[
"psf_e1"] = cheb_e1.evaluate(pixel_x, pixel_y)
177 cheb_e2 = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_e2[good], ctrl)
178 psf_array[
"psf_e2"] = cheb_e2.evaluate(pixel_x, pixel_y)
179 cheb_area = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_area[good], ctrl)
180 psf_array[
"psf_area"] = cheb_area.evaluate(pixel_x, pixel_y)
186 """Map of property maps to be run for a given task.
190 Property maps are classes derived from `BasePropertyMap`
193 for property_map
in self.values():
194 if (property_map.config.do_min
or property_map.config.do_max
or property_map.config.do_mean
195 or property_map.config.do_weighted_mean
or property_map.config.do_sum):
200 """Base class for property maps.
204 config : `BasePropertyMapConfig`
205 Property map configuration.
212 ConfigClass = BasePropertyMapConfig
217 object.__init__(self)
223 """Initialize the tract maps.
227 nside_coverage : `int`
228 Healpix nside of the healsparse coverage map.
230 Healpix nside of the property map.
233 self.
min_map = hsp.HealSparseMap.make_empty(nside_coverage,
237 self.
max_map = hsp.HealSparseMap.make_empty(nside_coverage,
241 self.
mean_map = hsp.HealSparseMap.make_empty(nside_coverage,
244 if self.
config.do_weighted_mean:
249 self.
sum_map = hsp.HealSparseMap.make_empty(nside_coverage,
254 """Initialize the value arrays for accumulation.
259 Number of pixels in the map.
270 if self.
config.do_weighted_mean:
277 """Accumulate values from a row of a visitSummary table.
281 indices : `np.ndarray`
282 Indices of values that should be accumulated.
284 Array of right ascension for indices
286 Array of declination
for indices
287 weights : `float`
or `np.ndarray`
288 Weight(s)
for indices to be accumulated.
289 scalings : `float`
or `np.ndarray`
290 Scaling values to coadd zeropoint.
292 Row of a visitSummary ExposureCatalog.
293 psf_array : `np.ndarray`, optional
294 Array of approximate psf values matched to ra/dec.
298 ValueError : Raised
if requires_psf
is True and psf_array
is None.
301 name = self.__class__.__name__
302 raise ValueError(f
"Cannot compute {name} without psf_array.")
304 values = self.
_compute(row, ra, dec, scalings, psf_array=psf_array)
311 if self.
config.do_weighted_mean:
317 """Finalize the accumulation of the mean and weighted mean.
321 total_weights : `np.ndarray`
322 Total accumulated weights, for each value index.
323 total_inputs : `np.ndarray`
324 Total number of inputs,
for each value index.
327 use, = np.where(total_inputs > 0)
329 if self.
config.do_weighted_mean:
330 use, = np.where(total_weights > 0.0)
337 """Assign accumulated values to the maps.
341 pixels : `np.ndarray`
342 Array of healpix pixels (nest scheme) to set in the map.
350 if self.
config.do_weighted_mean:
355 def _compute(self, row, ra, dec, scalings, psf_array=None):
356 """Compute map value from a row in the visitSummary catalog.
361 Row of a visitSummary ExposureCatalog.
363 Array of right ascensions
365 Array of declinations
366 scalings : `float` or `np.ndarray`
367 Scaling values to coadd zeropoint.
368 psf_array : `np.ndarray`, optional
369 Array of approximate psf values matched to ra/dec.
371 raise NotImplementedError(
"All property maps must implement _compute()")
374 """Perform post-processing on values.
378 total_weights : `np.ndarray`
379 Total accumulated weights, for each value index.
380 total_inputs : `np.ndarray`
381 Total number of inputs,
for each value index.
387@register_property_map("exposure_time")
389 """Exposure time property map."""
391 def _compute(self, row, ra, dec, scalings, psf_array=None):
392 return row.getVisitInfo().getExposureTime()
395@register_property_map("psf_size")
397 """PSF size property map."""
400 def _compute(self, row, ra, dec, scalings, psf_array=None):
401 return psf_array[
"psf_size"]
404@register_property_map("psf_e1")
406 """PSF shape e1 property map."""
409 def _compute(self, row, ra, dec, scalings, psf_array=None):
410 return psf_array[
"psf_e1"]
413@register_property_map("psf_e2")
415 """PSF shape e2 property map."""
418 def _compute(self, row, ra, dec, scalings, psf_array=None):
419 return psf_array[
"psf_e2"]
422@register_property_map("n_exposure")
424 """Number of exposures property map."""
427 def _compute(self, row, ra, dec, scalings, psf_array=None):
432 """Configuration for the PsfMaglim property map."""
433 maglim_nsigma = pexConfig.Field(dtype=float, default=5.0,
434 doc=
"Number of sigma to compute magnitude limit.")
439 raise ValueError(
"Can only use do_weighted_mean with PsfMaglimPropertyMap")
442@register_property_map("psf_maglim")
444 """PSF magnitude limit property map."""
447 ConfigClass = PsfMaglimPropertyMapConfig
449 def _compute(self, row, ra, dec, scalings, psf_array=None):
451 return psf_array[
"psf_area"]
456 - 2.5*np.log10(self.
config.maglim_nsigma*np.sqrt(psf_area/total_weights)))
460@register_property_map("sky_background")
462 """Sky background property map."""
463 def _compute(self, row, ra, dec, scalings, psf_array=None):
464 return scalings*row[
"skyBg"]
467@register_property_map("sky_noise")
469 """Sky noise property map."""
470 def _compute(self, row, ra, dec, scalings, psf_array=None):
471 return scalings*row[
"skyNoise"]
474@register_property_map("dcr_dra")
476 """Effect of DCR on delta-RA property map."""
477 def _compute(self, row, ra, dec, scalings, psf_array=None):
478 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
479 return np.tan(np.deg2rad(row[
"zenithDistance"]))*np.sin(par_angle)
482@register_property_map("dcr_ddec")
484 """Effect of DCR on delta-Dec property map."""
485 def _compute(self, row, ra, dec, scalings, psf_array=None):
486 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
487 return np.tan(np.deg2rad(row[
"zenithDistance"]))*np.cos(par_angle)
490@register_property_map("dcr_e1")
492 """Effect of DCR on psf shape e1 property map."""
493 def _compute(self, row, ra, dec, scalings, psf_array=None):
494 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
495 return (np.tan(np.deg2rad(row[
"zenithDistance"]))**2.)*np.cos(2.*par_angle)
498@register_property_map("dcr_e2")
500 """Effect of DCR on psf shape e2 property map."""
501 def _compute(self, row, ra, dec, scalings, psf_array=None):
502 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
503 return (np.tan(np.deg2rad(row[
"zenithDistance"]))**2.)*np.sin(2.*par_angle)
506@register_property_map("epoch")
508 """Observation epoch (mjd) property map."""
509 def _compute(self, row, ra, dec, scalings, psf_array=None):
510 date = row.getVisitInfo().getDate()
511 return date.get(date.MJD)
A control object used when fitting ChebyshevBoundedField to data (see ChebyshevBoundedField::fit)
Record class used to store exposure metadata.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
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)
_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)
register_property_map(name)