Coverage for python/lsst/meas/base/compensatedGaussian/_compensatedGaussian.py: 34%
64 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-18 02:22 -0700
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-18 02:22 -0700
1# This file is part of meas_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22from __future__ import annotations
24__all__ = (
25 "SingleFrameCompensatedGaussianFluxConfig",
26 "SingleFrameCompensatedGaussianFluxPlugin",
27)
29import math
30import numpy as np
31import scipy.stats as sps
33from lsst.pex.config import Field, ListField
34from lsst.geom import Point2I
36from ..sfm import SingleFramePlugin, SingleFramePluginConfig
37from ..pluginRegistry import register
39from .._measBaseLib import _compensatedGaussianFiltInnerProduct
42class OutOfBoundsError(Exception):
43 pass
46class SingleFrameCompensatedGaussianFluxConfig(SingleFramePluginConfig):
47 kernel_widths = ListField(
48 doc="The widths (in pixels) of the kernels for which to measure compensated apertures.",
49 dtype=int,
50 minLength=1,
51 default=[3, 5]
52 )
54 t = Field(
55 doc="Scale parameter of outer Gaussian compared to inner Gaussian.",
56 dtype=float,
57 default=2.0,
58 )
61@register("base_CompensatedGaussianFlux")
62class SingleFrameCompensatedGaussianFluxPlugin(SingleFramePlugin):
63 ConfigClass = SingleFrameCompensatedGaussianFluxConfig
65 @classmethod
66 def getExecutionOrder(cls):
67 return cls.FLUX_ORDER
69 def __init__(
70 self,
71 config: SingleFrameCompensatedGaussianFluxConfig,
72 name: str,
73 schema,
74 metadata,
75 logName=None,
76 **kwds,
77 ):
78 super().__init__(config, name, schema, metadata, logName, **kwds)
80 # create generic failure key
81 self.fatalFailKey = schema.addField(
82 f"{name}_flag", type="Flag", doc="Set to 1 for any fatal failure."
83 )
85 # Out of bounds failure key
86 self.ooBoundsKey = schema.addField(
87 f"{name}_bounds_flag",
88 type="Flag",
89 doc="Flag set to 1 if not all filters fit within exposure.",
90 )
92 self.width_keys = {}
93 self._rads = {}
94 self._flux_corrections = {}
95 self._variance_corrections = {}
96 self._t = config.t
97 for width in config.kernel_widths:
98 base_key = f"{name}_{width}"
100 # flux
101 flux_str = f"{base_key}_instFlux"
102 flux_key = schema.addField(flux_str, type="D", doc="Compensated Gaussian flux measurement.")
104 # flux error
105 err_str = f"{base_key}_instFluxErr"
106 err_key = schema.addField(err_str, type="D", doc="Compensated Gaussian flux error.")
108 # mask bits
109 mask_str = f"{base_key}_mask_bits"
110 mask_key = schema.addField(mask_str, type=np.int32, doc="Mask bits set within aperture.")
112 self.width_keys[width] = (flux_key, err_key, mask_key)
113 self._rads[width] = math.ceil(sps.norm.ppf((0.995,), scale=width * config.t)[0])
115 self._max_rad = max(self._rads)
117 def fail(self, measRecord, error=None):
118 if isinstance(error, OutOfBoundsError):
119 measRecord.set(self.ooBoundsKey, True)
120 measRecord.set(self.fatalFailKey, True)
122 def measure(self, measRecord, exposure):
123 center = measRecord.getCentroid()
124 bbox = exposure.getBBox()
126 if Point2I(center) not in exposure.getBBox().erodedBy(self._max_rad):
127 raise OutOfBoundsError("Not all the kernels for this source fit inside the exposure.")
129 y = center.getY() - bbox.beginY
130 x = center.getX() - bbox.beginX
132 y_floor = math.floor(y)
133 x_floor = math.floor(x)
135 for width, (flux_key, err_key, mask_key) in self.width_keys.items():
136 rad = self._rads[width]
137 y_slice = slice(y_floor - rad, y_floor + rad + 1, 1)
138 x_slice = slice(x_floor - rad, x_floor + rad + 1, 1)
139 y_mean = y - y_floor + rad
140 x_mean = x - x_floor + rad
142 flux, var = _compensatedGaussianFiltInnerProduct(
143 exposure.image.array[y_slice, x_slice],
144 exposure.variance.array[y_slice, x_slice],
145 x_mean,
146 y_mean,
147 width,
148 self._t,
149 )
150 measRecord.set(flux_key, flux)
151 measRecord.set(err_key, np.sqrt(var))
152 measRecord.set(mask_key, np.bitwise_or.reduce(exposure.mask.array[y_slice, x_slice], axis=None))