LSST Applications g04e9c324dd+8c5ae1fdc5,g134cb467dc+b203dec576,g18429d2f64+358861cd2c,g199a45376c+0ba108daf9,g1fd858c14a+dd066899e3,g262e1987ae+ebfced1d55,g29ae962dfc+72fd90588e,g2cef7863aa+aef1011c0b,g35bb328faa+8c5ae1fdc5,g3fd5ace14f+b668f15bc5,g4595892280+3897dae354,g47891489e3+abcf9c3559,g4d44eb3520+fb4ddce128,g53246c7159+8c5ae1fdc5,g67b6fd64d1+abcf9c3559,g67fd3c3899+1f72b5a9f7,g74acd417e5+cb6b47f07b,g786e29fd12+668abc6043,g87389fa792+8856018cbb,g89139ef638+abcf9c3559,g8d7436a09f+bcf525d20c,g8ea07a8fe4+9f5ccc88ac,g90f42f885a+6054cc57f1,g97be763408+06f794da49,g9dd6db0277+1f72b5a9f7,ga681d05dcb+7e36ad54cd,gabf8522325+735880ea63,gac2eed3f23+abcf9c3559,gb89ab40317+abcf9c3559,gbf99507273+8c5ae1fdc5,gd8ff7fe66e+1f72b5a9f7,gdab6d2f7ff+cb6b47f07b,gdc713202bf+1f72b5a9f7,gdfd2d52018+8225f2b331,ge365c994fd+375fc21c71,ge410e46f29+abcf9c3559,geaed405ab2+562b3308c0,gf9a733ac38+8c5ae1fdc5,w.2025.35
LSST Data Management Base Package
Loading...
Searching...
No Matches
measure.py
Go to the documentation of this file.
1# This file is part of scarlet_lite.
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/>.
21
22from typing import cast
23
24import numpy as np
25
26from .bbox import Box
27from .image import Image
28
29
31 images: Image,
32 variance: Image,
33 psfs: np.ndarray,
34 center: tuple[int, int],
35) -> float:
36 """Calculate the signal to noise for a point source
37
38 This is done by weighting the image with the PSF in each band
39 and dividing by the PSF weighted variance.
40
41 Parameters
42 ----------
43 images:
44 The 3D (bands, y, x) image containing the data.
45 variance:
46 The variance of `images`.
47 psfs:
48 The PSF in each band.
49 center:
50 The center of the signal.
51
52 Returns
53 -------
54 snr:
55 The signal to noise of the source.
56 """
57 py = psfs.shape[1] // 2
58 px = psfs.shape[2] // 2
59 bbox = Box(psfs[0].shape, origin=(-py + center[0], -px + center[1]))
60 overlap = images.bbox & bbox
61 noise = variance[overlap].data
62 img = images[overlap].data
63 _psfs = Image(psfs, bands=images.bands, yx0=cast(tuple[int, int], bbox.origin))[overlap].data
64 numerator = img * _psfs
65 denominator = (_psfs * noise) * _psfs
66 return np.sum(numerator) / np.sqrt(np.sum(denominator))
67
68
69def conserve_flux(blend, mask_footprint: bool = True, images: Image | None = None) -> None:
70 """Use the source models as templates to re-distribute flux
71 from the data
72
73 The source models are used as approximations to the data,
74 which redistribute the flux in the data according to the
75 ratio of the models for each source.
76 There is no return value for this function,
77 instead it adds (or modifies) a ``flux_weighted_image``
78 attribute to each the sources with the flux attributed to
79 that source.
80
81 Parameters
82 ----------
83 blend:
84 The blend that is being fit
85 mask_footprint:
86 Whether or not to apply a mask for pixels with zero weight.
87 """
88 observation = blend.observation
89 py = observation.psfs.shape[-2] // 2
90 px = observation.psfs.shape[-1] // 2
91
92 if images is None:
93 images = observation.images.copy()
94 if mask_footprint:
95 images.data[observation.weights.data == 0] = 0
96 model = blend.get_model()
97 bands = None
98 else:
99 bands = images.bands
100 model = blend.get_model()[bands,]
101 # Always convolve in real space to avoid FFT artifacts
102 model = observation.convolve(model, mode="real")
103 model.data[model.data < 0] = 0
104
105 for src in blend.sources:
106 if src.is_null:
107 src.flux_weighted_image = Image.from_box(Box((0, 0)), bands=observation.bands) # type: ignore
108 continue
109 src_model = src.get_model()
110
111 # Grow the model to include the wings of the PSF
112 src_box = src.bbox.grow((py, px))
113 overlap = observation.bbox & src_box
114 src_model = src_model.project(bbox=overlap)
115 src_model = observation.convolve(src_model, mode="real")
116 if bands is not None:
117 src_model = src_model[bands,]
118 src_model.data[src_model.data < 0] = 0
119 numerator = src_model.data
120 denominator = model[overlap].data
121 cuts = denominator != 0
122 ratio = np.zeros(numerator.shape, dtype=numerator.dtype)
123 ratio[cuts] = numerator[cuts] / denominator[cuts]
124 ratio[denominator == 0] = 0
125 # sometimes numerical errors can cause a hot pixel to have a
126 # slightly higher ratio than 1
127 ratio[ratio > 1] = 1
128 src.flux_weighted_image = src_model.copy_with(data=ratio) * images[overlap]
A class to represent a 2-dimensional array of pixels.
Definition Image.h:51
None conserve_flux(blend, bool mask_footprint=True, Image|None images=None)
Definition measure.py:69
float calculate_snr(Image images, Image variance, np.ndarray psfs, tuple[int, int] center)
Definition measure.py:35