Coverage for python/lsst/scarlet/lite/source.py: 40%

57 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-16 02:46 -0700

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 

22__all__ = ["Source"] 

23 

24from typing import Callable 

25 

26from .bbox import Box 

27from .component import Component 

28from .image import Image 

29 

30 

31class Source: 

32 """A container for components associated with the same astrophysical object 

33 

34 A source can have a single component, or multiple components, 

35 and each can be contained in different bounding boxes. 

36 

37 Parameters 

38 ---------- 

39 components: 

40 The components contained in the source. 

41 """ 

42 

43 def __init__(self, components: list[Component]): 

44 self.components = components 

45 self.flux_weighted_image: Image | None = None 

46 

47 @property 

48 def n_components(self) -> int: 

49 """The number of components in this source""" 

50 return len(self.components) 

51 

52 @property 

53 def center(self) -> tuple[int, int] | None: 

54 """The center of the source in the full Blend.""" 

55 if not self.is_null and hasattr(self.components[0], "peak"): 

56 return self.components[0].peak # type: ignore 

57 return None 

58 

59 @property 

60 def source_center(self) -> tuple[int, int] | None: 

61 """The center of the source in its local bounding box.""" 

62 _center = self.center 

63 _origin = self.bbox.origin 

64 if _center is not None: 

65 center = ( 

66 _center[0] - _origin[0], 

67 _center[1] - _origin[1], 

68 ) 

69 return center 

70 return None 

71 

72 @property 

73 def is_null(self) -> bool: 

74 """True if the source does not have any components""" 

75 return self.n_components == 0 

76 

77 @property 

78 def bbox(self) -> Box: 

79 """The minimal bounding box to contain all of this sources components 

80 

81 Null sources have a bounding box with shape `(0,0,0)` 

82 """ 

83 if self.n_components == 0: 

84 return Box((0, 0)) 

85 bbox = self.components[0].bbox 

86 for component in self.components[1:]: 

87 bbox = bbox | component.bbox 

88 return bbox 

89 

90 @property 

91 def bands(self) -> tuple: 

92 """The bands in the full source model.""" 

93 if self.is_null: 

94 return () 

95 return self.components[0].bands 

96 

97 def get_model(self, use_flux: bool = False) -> Image: 

98 """Build the model for the source 

99 

100 This is never called during optimization and is only used 

101 to generate a model of the source for investigative purposes. 

102 

103 Parameters 

104 ---------- 

105 use_flux: 

106 Whether to use the re-distributed flux associated with the source 

107 instead of the component models. 

108 

109 Returns 

110 ------- 

111 model: 

112 The full-color model. 

113 """ 

114 if self.n_components == 0: 

115 return 0 # type: ignore 

116 

117 if use_flux: 

118 # Return the redistributed flux 

119 # (calculated by scarlet.lite.measure.weight_sources) 

120 return self.flux_weighted_image # type: ignore 

121 

122 model = self.components[0].get_model() 

123 for component in self.components[1:]: 

124 model = model + component.get_model() 

125 return model 

126 

127 def parameterize(self, parameterization: Callable): 

128 """Convert the component parameter arrays into Parameter instances 

129 

130 Parameters 

131 ---------- 

132 parameterization: 

133 A function to use to convert parameters of a given type into 

134 a `Parameter` in place. It should take a single argument that 

135 is the `Component` or `Source` that is to be parameterized. 

136 """ 

137 for component in self.components: 

138 component.parameterize(parameterization) 

139 

140 def __str__(self): 

141 return f"Source<{len(self.components)}>" 

142 

143 def __repr__(self): 

144 return f"Source(components={repr(self.components)})>"