Coverage for python/lsst/afw/geom/transformConfig.py: 62%

52 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-29 02:27 -0700

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 

22import functools 

23import numpy 

24 

25import lsst.geom 

26from lsst.pex.config import Config, ListField, makeRegistry, \ 

27 ConfigDictField, ConfigurableField 

28from ._geom import makeTransform, makeIdentityTransform, \ 

29 makeRadialTransform 

30 

31__all__ = ["transformRegistry", "OneTransformConfig", "TransformConfig", 

32 "IdentityTransformConfig", "AffineTransformConfig", "RadialTransformConfig", 

33 "MultiTransformConfig"] 

34 

35transformRegistry = makeRegistry( 

36 """"A registry of ``Transform`` factories 

37 

38 A ``Transform`` factory is a function that obeys these rules: 

39 - has an attribute ``ConfigClass`` 

40 - takes one argument, ``config`` (an instance of ``ConfigClass``) by name 

41 - returns a ``Transform`` 

42 """ 

43) 

44 

45 

46class IdentityTransformConfig(Config): 

47 """A Config representing a ``Transform`` that does nothing. 

48 

49 See Also 

50 -------- 

51 lsst.afw.geom.makeIdentityTransform 

52 """ 

53 pass 

54 

55 

56def identityFactory(config): 

57 """Make an identity ``Transform`` 

58 """ 

59 return makeIdentityTransform() 

60 

61 

62identityFactory.ConfigClass = IdentityTransformConfig 

63transformRegistry.register("identity", identityFactory) 

64 

65 

66class OneTransformConfig(Config): 

67 """A Config representing a single ``Transform`` in a compound ``Transform``. 

68 

69 See Also 

70 -------- 

71 lsst.afw.geom.MultiTransformConfig 

72 """ 

73 transform = ConfigurableField( 

74 doc="Transform factory", 

75 target=identityFactory, 

76 ) 

77 

78 

79def invertingFactory(config): 

80 """Invert a ``Transform`` specified by config. 

81 """ 

82 return config.transform.apply().inverted() 

83 

84 

85invertingFactory.ConfigClass = OneTransformConfig 

86transformRegistry.register("inverted", invertingFactory) 

87 

88 

89class AffineTransformConfig(Config): 

90 """A Config representing an affine ``Transform``. 

91 

92 See Also 

93 -------- 

94 lsst.afw.geom.makeTransform 

95 """ 

96 linear = ListField( 

97 doc="2x2 linear matrix in the usual numpy order; " 

98 "to rotate a vector by theta use: cos(theta), sin(theta), " 

99 "-sin(theta), cos(theta)", 

100 dtype=float, 

101 length=4, 

102 default=(1, 0, 0, 1), 

103 ) 

104 translation = ListField( 

105 doc="x, y translation vector", 

106 dtype=float, 

107 length=2, 

108 default=(0, 0), 

109 ) 

110 

111 

112def affineFactory(config): 

113 """Make an affine ``Transform`` 

114 """ 

115 linear = numpy.array(config.linear) 

116 linear.shape = (2, 2) 

117 translation = numpy.array(config.translation) 

118 return makeTransform(lsst.geom.AffineTransform(linear, translation)) 

119 

120 

121affineFactory.ConfigClass = AffineTransformConfig 

122transformRegistry.register("affine", affineFactory) 

123 

124 

125class RadialTransformConfig(Config): 

126 """A Config representing a radially symmetric ``Transform``. 

127 

128 See Also 

129 -------- 

130 lsst.afw.geom.makeRadialTransform 

131 """ 

132 coeffs = ListField( 

133 doc="Coefficients for the radial polynomial; coeff[0] must be 0", 

134 dtype=float, 

135 minLength=1, 

136 optional=False, 

137 ) 

138 

139 def validate(self): 

140 if len(self.coeffs) == 0: 

141 return 

142 if len(self.coeffs) == 1 or self.coeffs[0] != 0 or self.coeffs[1] == 0: 

143 raise RuntimeError( 

144 f"invalid radial transform coeffs {self.coeffs}: " 

145 "need len(coeffs)=0 or len(coeffs)>1, coeffs[0]==0, " 

146 "and coeffs[1]!=0") 

147 

148 

149def radialFactory(config): 

150 """Make a radial ``Transform`` 

151 """ 

152 return makeRadialTransform(config.coeffs._list) 

153 

154 

155radialFactory.ConfigClass = RadialTransformConfig 

156transformRegistry.register("radial", radialFactory) 

157 

158 

159class MultiTransformConfig(Config): 

160 """A Config representing a chain of consecutive ``Transforms``. 

161 """ 

162 transformDict = ConfigDictField( 

163 doc="Dict of index: OneTransformConfig (a transform wrapper); " 

164 "key order is transform order", 

165 keytype=int, 

166 itemtype=OneTransformConfig, 

167 ) 

168 

169 

170def multiFactory(config): 

171 """Concatenate multiple ``Transforms`` 

172 """ 

173 transformKeys = sorted(config.transformDict.keys()) 

174 transformList = [config.transformDict[key].transform.apply() 

175 for key in transformKeys] 

176 

177 # Can't use then(self, other) directly because no single Transform class 

178 def concat(transform1, transform2): 

179 return transform1.then(transform2) 

180 

181 return functools.reduce(concat, transformList) 

182 

183 

184multiFactory.ConfigClass = MultiTransformConfig 

185transformRegistry.register("multi", multiFactory) 

186 

187 

188class TransformConfig(Config): 

189 """Config that identifies ``Transforms`` by keyword. 

190 

191 Supported configs: 

192 

193 ``"identity"`` 

194 `IdentityTransformConfig` 

195 ``"inverted"`` 

196 `OneTransformConfig` 

197 ``"affine"`` 

198 `AffineTransformConfig` 

199 ``"radial"`` 

200 `RadialTransformConfig` 

201 ``"multi"`` 

202 `MultiTransformConfig` 

203 """ 

204 transform = transformRegistry.makeField( 

205 doc="a Transform from the registry" 

206 )