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"),
168 cheb_size = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_size, ctrl)
169 psf_array[
"psf_size"] = cheb_size.evaluate(pixel_x, pixel_y)
170 cheb_e1 = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_e1, ctrl)
171 psf_array[
"psf_e1"] = cheb_e1.evaluate(pixel_x, pixel_y)
172 cheb_e2 = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_e2, ctrl)
173 psf_array[
"psf_e2"] = cheb_e2.evaluate(pixel_x, pixel_y)
174 cheb_area = ChebyshevBoundedField.fit(
lsst.geom.Box2I(bbox), x, y, psf_area, ctrl)
175 psf_array[
"psf_area"] = cheb_area.evaluate(pixel_x, pixel_y)
181 """Map of property maps to be run for a given task.
185 Property maps are classes derived from `BasePropertyMap`
188 for property_map
in self.values():
189 if (property_map.config.do_min
or property_map.config.do_max
or property_map.config.do_mean
190 or property_map.config.do_weighted_mean
or property_map.config.do_sum):
195 """Base class for property maps.
199 config : `BasePropertyMapConfig`
200 Property map configuration.
207 ConfigClass = BasePropertyMapConfig
212 object.__init__(self)
218 """Initialize the tract maps.
222 nside_coverage : `int`
223 Healpix nside of the healsparse coverage map.
225 Healpix nside of the property map.
228 self.
min_map = hsp.HealSparseMap.make_empty(nside_coverage,
232 self.
max_map = hsp.HealSparseMap.make_empty(nside_coverage,
236 self.
mean_map = hsp.HealSparseMap.make_empty(nside_coverage,
239 if self.
config.do_weighted_mean:
244 self.
sum_map = hsp.HealSparseMap.make_empty(nside_coverage,
249 """Initialize the value arrays for accumulation.
254 Number of pixels in the map.
265 if self.
config.do_weighted_mean:
272 """Accumulate values from a row of a visitSummary table.
276 indices : `np.ndarray`
277 Indices of values that should be accumulated.
279 Array of right ascension for indices
281 Array of declination
for indices
282 weights : `float`
or `np.ndarray`
283 Weight(s)
for indices to be accumulated.
284 scalings : `float`
or `np.ndarray`
285 Scaling values to coadd zeropoint.
287 Row of a visitSummary ExposureCatalog.
288 psf_array : `np.ndarray`, optional
289 Array of approximate psf values matched to ra/dec.
293 ValueError : Raised
if requires_psf
is True and psf_array
is None.
296 name = self.__class__.__name__
297 raise ValueError(f
"Cannot compute {name} without psf_array.")
299 values = self.
_compute(row, ra, dec, scalings, psf_array=psf_array)
306 if self.
config.do_weighted_mean:
312 """Finalize the accumulation of the mean and weighted mean.
316 total_weights : `np.ndarray`
317 Total accumulated weights, for each value index.
318 total_inputs : `np.ndarray`
319 Total number of inputs,
for each value index.
322 use, = np.where(total_inputs > 0)
324 if self.
config.do_weighted_mean:
325 use, = np.where(total_weights > 0.0)
332 """Assign accumulated values to the maps.
336 pixels : `np.ndarray`
337 Array of healpix pixels (nest scheme) to set in the map.
345 if self.
config.do_weighted_mean:
350 def _compute(self, row, ra, dec, scalings, psf_array=None):
351 """Compute map value from a row in the visitSummary catalog.
356 Row of a visitSummary ExposureCatalog.
358 Array of right ascensions
360 Array of declinations
361 scalings : `float` or `np.ndarray`
362 Scaling values to coadd zeropoint.
363 psf_array : `np.ndarray`, optional
364 Array of approximate psf values matched to ra/dec.
366 raise NotImplementedError(
"All property maps must implement _compute()")
368 def _post_process(self, total_weights, total_inputs):
369 """Perform post-processing on values.
373 total_weights : `np.ndarray`
374 Total accumulated weights, for each value index.
375 total_inputs : `np.ndarray`
376 Total number of inputs,
for each value index.
382@register_property_map("exposure_time")
384 """Exposure time property map."""
386 def _compute(self, row, ra, dec, scalings, psf_array=None):
387 return row.getVisitInfo().getExposureTime()
390@register_property_map("psf_size")
392 """PSF size property map."""
395 def _compute(self, row, ra, dec, scalings, psf_array=None):
396 return psf_array[
"psf_size"]
399@register_property_map("psf_e1")
401 """PSF shape e1 property map."""
404 def _compute(self, row, ra, dec, scalings, psf_array=None):
405 return psf_array[
"psf_e1"]
408@register_property_map("psf_e2")
410 """PSF shape e2 property map."""
413 def _compute(self, row, ra, dec, scalings, psf_array=None):
414 return psf_array[
"psf_e2"]
417@register_property_map("n_exposure")
419 """Number of exposures property map."""
422 def _compute(self, row, ra, dec, scalings, psf_array=None):
427 """Configuration for the PsfMaglim property map."""
428 maglim_nsigma = pexConfig.Field(dtype=float, default=5.0,
429 doc=
"Number of sigma to compute magnitude limit.")
434 raise ValueError(
"Can only use do_weighted_mean with PsfMaglimPropertyMap")
437@register_property_map("psf_maglim")
439 """PSF magnitude limit property map."""
442 ConfigClass = PsfMaglimPropertyMapConfig
444 def _compute(self, row, ra, dec, scalings, psf_array=None):
446 return psf_array[
"psf_area"]
448 def _post_process(self, total_weights, total_inputs):
451 - 2.5*np.log10(self.
config.maglim_nsigma*np.sqrt(psf_area/total_weights)))
455@register_property_map("sky_background")
457 """Sky background property map."""
458 def _compute(self, row, ra, dec, scalings, psf_array=None):
459 return scalings*row[
"skyBg"]
462@register_property_map("sky_noise")
464 """Sky noise property map."""
465 def _compute(self, row, ra, dec, scalings, psf_array=None):
466 return scalings*row[
"skyNoise"]
469@register_property_map("dcr_dra")
471 """Effect of DCR on delta-RA property map."""
472 def _compute(self, row, ra, dec, scalings, psf_array=None):
473 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
474 return np.tan(np.deg2rad(row[
"zenithDistance"]))*np.sin(par_angle)
477@register_property_map("dcr_ddec")
479 """Effect of DCR on delta-Dec property map."""
480 def _compute(self, row, ra, dec, scalings, psf_array=None):
481 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
482 return np.tan(np.deg2rad(row[
"zenithDistance"]))*np.cos(par_angle)
485@register_property_map("dcr_e1")
487 """Effect of DCR on psf shape e1 property map."""
488 def _compute(self, row, ra, dec, scalings, psf_array=None):
489 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
490 return (np.tan(np.deg2rad(row[
"zenithDistance"]))**2.)*np.cos(2.*par_angle)
493@register_property_map("dcr_e2")
495 """Effect of DCR on psf shape e2 property map."""
496 def _compute(self, row, ra, dec, scalings, psf_array=None):
497 par_angle = row.getVisitInfo().getBoresightParAngle().asRadians()
498 return (np.tan(np.deg2rad(row[
"zenithDistance"]))**2.)*np.sin(2.*par_angle)
501@register_property_map("epoch")
503 """Observation epoch (mjd) property map."""
504 def _compute(self, row, ra, dec, scalings, psf_array=None):
505 date = row.getVisitInfo().getDate()
506 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.
def _compute(self, row, ra, dec, scalings, psf_array=None)
def accumulate_values(self, indices, ra, dec, weights, scalings, row, psf_array=None)
def set_map_values(self, pixels)
def __init__(self, config, name)
def initialize_values(self, n_pixels)
def initialize_tract_maps(self, nside_coverage, nside)
def finalize_mean_values(self, total_weights, total_inputs)
def _post_process(self, total_weights, total_inputs)
def __call__(self, config)
def __init__(self, name, PropertyMapClass)
def register_property_map(name)