Coverage for python/lsst/afw/cameraGeom/_amplifier.py: 26%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

71 statements  

1# This file is part of afw. 

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__ = ["AmplifierGeometryComparison", "ReadoutCornerValNameDict", "ReadoutCornerNameValDict"] 

23 

24import enum 

25 

26from lsst.geom import Extent2I 

27from lsst.utils import continueClass, inClass 

28from ._cameraGeom import Amplifier, ReadoutCorner 

29 

30 

31ReadoutCornerValNameDict = { 

32 ReadoutCorner.LL: "LL", 

33 ReadoutCorner.LR: "LR", 

34 ReadoutCorner.UR: "UR", 

35 ReadoutCorner.UL: "UL", 

36} 

37ReadoutCornerNameValDict = {val: key for key, val in 

38 ReadoutCornerValNameDict.items()} 

39 

40 

41class AmplifierGeometryComparison(enum.Flag): 

42 """Flags used to report geometric differences between amplifier""" 

43 

44 EQUAL = 0 

45 """All tested properties of the two amplifiers are equal.""" 

46 

47 SHIFTED_X = enum.auto() 

48 """Amplifiers have different X offsets relative to assembled raw.""" 

49 

50 SHIFTED_Y = enum.auto() 

51 """Amplifiers have different Y offsets relative to assembled raw.""" 

52 

53 SHIFTED = SHIFTED_X | SHIFTED_Y 

54 """Amplifiers are different offsets relative to assembled raw.""" 

55 

56 FLIPPED_X = enum.auto() 

57 """Amplifiers differ by (at least) an X-coordinate flip.""" 

58 

59 FLIPPED_Y = enum.auto() 

60 """Amplifiers differ by (at least) a Y-coordinate flip.""" 

61 

62 FLIPPED = FLIPPED_X | FLIPPED_Y 

63 """Amplifiers differ by (at least) a coordinate flip.""" 

64 

65 ASSEMBLY_DIFFERS = SHIFTED | FLIPPED 

66 """Amplifiers differ in offsets relative to raw, indicating at least a 

67 difference in assembly state. 

68 """ 

69 

70 REGIONS_DIFFER = enum.auto() 

71 """Amplifiers have different full/data/overscan/prescan regions. 

72 

73 If ``assembly=True`` was passed to `Amplifier.compare`, this will only be 

74 set if regions differ even after applying flips and offsets to make the 

75 assembly states the same. If ``assembly=False`` was passed to 

76 `Amplifier.compare`, regions will be compared while assuming that assembly 

77 state is the same. 

78 """ 

79 

80 

81@continueClass 

82class Amplifier: # noqa: F811 

83 

84 def compareGeometry(self, other, *, assembly=True, regions=True): 

85 """Compare the geometry of this amplifier with another. 

86 

87 Parameters 

88 ---------- 

89 assembly : `bool`, optional 

90 If `True` (default) test whether flips and offsets relative to 

91 assembled raw are the same, and account for those when testing 

92 whether regions are the same. 

93 regions : `bool`, optional 

94 If `True` (default) test whether full/data/overscan/prescan regions 

95 are the same. 

96 

97 Returns 

98 ------- 

99 comparison : `AmplifierGeometryComparison` 

100 Flags representing the result of the comparison. 

101 """ 

102 result = AmplifierGeometryComparison.EQUAL 

103 if assembly: 

104 if self.getRawXYOffset().getX() != other.getRawXYOffset().getX(): 

105 result |= AmplifierGeometryComparison.SHIFTED_X 

106 if self.getRawXYOffset().getY() != other.getRawXYOffset().getY(): 

107 result |= AmplifierGeometryComparison.SHIFTED_Y 

108 if self.getRawFlipX() != other.getRawFlipX(): 

109 result |= AmplifierGeometryComparison.FLIPPED_X 

110 if self.getRawFlipY() != other.getRawFlipY(): 

111 result |= AmplifierGeometryComparison.FLIPPED_Y 

112 if regions: 

113 if result & AmplifierGeometryComparison.ASSEMBLY_DIFFERS: 

114 # Transform (a copy of) other to the same assembly state as 

115 # self. 

116 other = other.rebuild().transform( 

117 outOffset=self.getRawXYOffset(), 

118 outFlipX=self.getRawFlipX(), 

119 outFlipY=self.getRawFlipY(), 

120 ).finish() 

121 for bboxName in ("", 

122 "HorizontalOverscan", 

123 "Data", 

124 "VerticalOverscan", 

125 "Prescan"): 

126 if getattr(self, f"getRaw{bboxName}BBox")() != getattr(other, f"getRaw{bboxName}BBox")(): 

127 result |= AmplifierGeometryComparison.REGIONS_DIFFER 

128 return result 

129 

130 

131@inClass(Amplifier.Builder) 

132def transform(self, *, outOffset=None, outFlipX=False, outFlipY=False): 

133 """Transform an amplifier builder (in-place) by applying shifts and 

134 flips. 

135 

136 Parameters 

137 ---------- 

138 outOffset : `lsst.geom.Extent2I`, optional 

139 Post-transformation return value for ``self.getRawXYOffset()``. 

140 The default is ``(0, 0)``, which shifts the amplifier to its 

141 position in the assembled (but still untrimmed) raw image. 

142 outFlipX : `bool`, optional 

143 Post-transformation return value for ``self.getRawFlipX()``. The 

144 default is `False`, which flips the amplifier to its correct 

145 X orientation in the assembled raw image. 

146 outFlipX : `bool`, optional 

147 Post-transformation return value for ``self.getRawFlipY()``. The 

148 default is `False`, which flips the amplifier to its correct 

149 Y orientation in the assembled raw image. 

150 

151 Returns 

152 ------- 

153 self : `AmplifierBuilder` 

154 Returned to enable method chaining, e.g. 

155 ``amplifier.rebuild().transform().finish()``. 

156 """ 

157 if outOffset is None: 

158 outOffset = Extent2I(0, 0) 

159 bbox = self.getRawBBox() 

160 awidth, aheight = bbox.getDimensions() 

161 # 

162 # Figure out how far flipping the amp LR and/or TB offsets the bboxes 

163 # 

164 boxMin0 = bbox.getMin() # initial position of rawBBox's LLC corner 

165 if self.getRawFlipX() != outFlipX: 

166 bbox.flipLR(awidth) 

167 if self.getRawFlipY() != outFlipY: 

168 bbox.flipTB(aheight) 

169 shift = boxMin0 - bbox.getMin() 

170 

171 for bboxName in ("", 

172 "HorizontalOverscan", 

173 "Data", 

174 "VerticalOverscan", 

175 "Prescan"): 

176 bbox = getattr(self, f"getRaw{bboxName}BBox")() 

177 if self.getRawFlipX() != outFlipX: 

178 bbox.flipLR(awidth) 

179 if self.getRawFlipY() != outFlipY: 

180 bbox.flipTB(aheight) 

181 bbox.shift(self.getRawXYOffset() + shift - outOffset) 

182 

183 getattr(self, f"setRaw{bboxName}BBox")(bbox) 

184 

185 # Update the Readout Corner if we've flipped anything. 

186 outReadoutCorner = self.getReadoutCorner() 

187 if self.getRawFlipX() != outFlipX: 

188 xFlipMapping = {ReadoutCorner.LL: ReadoutCorner.LR, ReadoutCorner.LR: ReadoutCorner.LL, 

189 ReadoutCorner.UR: ReadoutCorner.UL, ReadoutCorner.UL: ReadoutCorner.UR} 

190 outReadoutCorner = xFlipMapping[outReadoutCorner] 

191 if self.getRawFlipY() != outFlipY: 

192 yFlipMapping = {ReadoutCorner.LL: ReadoutCorner.UL, ReadoutCorner.LR: ReadoutCorner.UR, 

193 ReadoutCorner.UR: ReadoutCorner.LR, ReadoutCorner.UL: ReadoutCorner.LL} 

194 outReadoutCorner = yFlipMapping[outReadoutCorner] 

195 if outReadoutCorner != self.getReadoutCorner(): 

196 self.setReadoutCorner(outReadoutCorner) 

197 

198 # 

199 # All of these have now been transferred to the amp geometry 

200 # 

201 self.setRawXYOffset(outOffset) 

202 self.setRawFlipX(outFlipX) 

203 self.setRawFlipY(outFlipY) 

204 return self