Hide keyboard shortcuts

Hot-keys 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

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__ = ["Key", "Field", "SchemaItem"] 

23 

24import numpy as np 

25import fnmatch 

26import re 

27import collections 

28import astropy.units 

29 

30import lsst.geom 

31from lsst.utils import continueClass, TemplateMeta 

32 

33from ._table import _Key, _Field, _SchemaItem, Schema 

34 

35# Objects we prefer to use over the C++ string name for 

36# Key/Field/SchemaItem types. 

37_dtypes = { 

38 "String": str, 

39 "B": np.uint8, 

40 "U": np.uint16, 

41 "I": np.int32, 

42 "L": np.int64, 

43 "F": np.float32, 

44 "D": np.float64, 

45 "Angle": lsst.geom.Angle, 

46} 

47 

48 

49class Key(metaclass=TemplateMeta): 

50 pass 

51 

52 

53class Field(metaclass=TemplateMeta): 

54 pass 

55 

56 

57class SchemaItem(metaclass=TemplateMeta): 

58 pass 

59 

60 

61def _registerInstantiations(abc, types): 

62 """Iterate over a private dict (filled by template instantiations in C++) 

63 to register template instantiations a TemplateMeta ABCs. 

64 

65 If an entry for the type string exists in _dtypes, we use that instead of 

66 the string as the key, and use the string as an alias. 

67 """ 

68 for k, v in types.items(): 

69 dtype = _dtypes.get(k, None) 

70 if dtype is not None: 

71 abc.register(dtype, v) 

72 abc.alias(k, v) 

73 else: 

74 abc.register(k, v) 

75 

76 

77# _Key, _Field, and _SchemaItem are {str: class} dicts populated by the C++ 

78# wrappers. The keys are a superset of those in _dtypes. 

79_registerInstantiations(Key, _Key) 

80_registerInstantiations(Field, _Field) 

81_registerInstantiations(SchemaItem, _SchemaItem) 

82 

83# Also register `float->D` as an alias; can't include 

84# in _dtypes because we have (and prefer) np.float64 there. 

85Key.alias(float, _Key["D"]) 

86Field.alias(float, _Field["D"]) 

87SchemaItem.alias(float, _SchemaItem["D"]) 

88 

89 

90@continueClass # noqa: F811 

91class Schema: 

92 

93 def getOrderedNames(self): 

94 """Return a list of field names in the order the fields were added to the Schema. 

95 

96 Returns 

97 ------- 

98 names : `List` 

99 Field names in order they were added to the Schema. 

100 """ 

101 names = [] 

102 

103 def func(item): 

104 names.append(item.field.getName()) 

105 self.forEach(func) 

106 return names 

107 

108 def __iter__(self): 

109 """Iterate over the items in the Schema. 

110 """ 

111 items = [] 

112 self.forEach(items.append) 

113 return iter(items) 

114 

115 def checkUnits(self, parse_strict='raise'): 

116 """Check that all units in the Schema are valid Astropy unit strings. 

117 

118 Parameters 

119 ---------- 

120 parse_strict : `str`, optional 

121 One of 'raise' (default), 'warn', or 'strict', indicating how to 

122 handle unrecognized unit strings. See also astropy.units.Unit. 

123 """ 

124 def func(item): 

125 astropy.units.Unit(item.field.getUnits(), 

126 parse_strict=parse_strict) 

127 self.forEach(func) 

128 

129 def addField(self, field, type=None, doc="", units="", size=None, 

130 doReplace=False, parse_strict="raise"): 

131 """Add a field to the Schema. 

132 

133 Parameters 

134 ---------- 

135 field : `str` or `Field` 

136 The string name of the Field, or a fully-constructed Field object. 

137 If the latter, all other arguments besides doReplace are ignored. 

138 type : `str`, optional 

139 The type of field to create. Valid types are the keys of the 

140 afw.table.Field dictionary. 

141 doc : `str` 

142 Documentation for the field. 

143 unit : `str` 

144 Units for the field, or an empty string if unitless. 

145 size : `int` 

146 Size of the field; valid for string and array fields only. 

147 doReplace : `bool` 

148 If a field with this name already exists, replace it instead of 

149 raising pex.exceptions.InvalidParameterError. 

150 parse_strict : `str` 

151 One of 'raise' (default), 'warn', or 'strict', indicating how to 

152 handle unrecognized unit strings. See also astropy.units.Unit. 

153 

154 Returns 

155 ------- 

156 result : 

157 Result of the `Field` addition. 

158 """ 

159 if isinstance(field, str): 

160 field = Field[type](field, doc=doc, units=units, 

161 size=size, parse_strict=parse_strict) 

162 return field._addTo(self, doReplace) 

163 

164 def extract(self, *patterns, **kwargs): 

165 """Extract a dictionary of {<name>: <schema-item>} in which the field names 

166 match the given shell-style glob pattern(s). 

167 

168 Any number of glob patterns may be passed; the result will be the union of all 

169 the result of each glob considered separately. 

170 

171 Parameters 

172 ---------- 

173 patterns : Array of `str` 

174 List of glob patterns to use to select field names. 

175 kwargs : `dict` 

176 Dictionary of additional keyword arguments. May contain: 

177 - ``regex`` : `str` or `re` pattern 

178 A regular expression to be used in addition to any 

179 glob patterns passed as positional arguments. Note 

180 that this will be compared with re.match, not 

181 re.search. 

182 - ``sub`` : `str` 

183 A replacement string (see re.MatchObject.expand) used 

184 to set the dictionary keys of any fields matched by 

185 regex. 

186 - ``ordered`` : `bool`, optional 

187 If True, a collections.OrderedDict will be returned 

188 instead of a standard dict, with the order 

189 corresponding to the definition order of the 

190 Schema. Default is False. 

191 

192 Returns 

193 ------- 

194 d : `dict` 

195 Dictionary of extracted name-schema item sets. 

196 

197 Raises 

198 ------ 

199 ValueError 

200 Raised if the `sub` keyword argument is invalid without 

201 the `regex` argument. 

202 

203 Also raised if an unknown keyword argument is supplied. 

204 """ 

205 if kwargs.pop("ordered", False): 

206 d = collections.OrderedDict() 

207 else: 

208 d = dict() 

209 regex = kwargs.pop("regex", None) 

210 sub = kwargs.pop("sub", None) 

211 if sub is not None and regex is None: 

212 raise ValueError( 

213 "'sub' keyword argument to extract is invalid without 'regex' argument") 

214 if kwargs: 

215 kwargsStr = ", ".join(kwargs.keys()) 

216 raise ValueError(f"Unrecognized keyword arguments for extract: {kwargsStr}") 

217 for item in self: 

218 trueName = item.field.getName() 

219 names = [trueName] 

220 for alias, target in self.getAliasMap().items(): 

221 if trueName.startswith(target): 

222 names.append(trueName.replace(target, alias, 1)) 

223 for name in names: 

224 if regex is not None: 

225 m = re.match(regex, name) 

226 if m is not None: 

227 if sub is not None: 

228 name = m.expand(sub) 

229 d[name] = item 

230 continue # continue middle loop so we don't match the same name twice 

231 for pattern in patterns: 

232 if fnmatch.fnmatchcase(name, pattern): 

233 d[name] = item 

234 break # break inner loop so we don't match the same name twice 

235 return d 

236 

237 def __reduce__(self): 

238 """For pickle support.""" 

239 fields = [] 

240 for item in self: 

241 fields.append(item.field) 

242 return (makeSchemaFromFields, (fields,)) 

243 

244 

245def makeSchemaFromFields(fields): 

246 """Create a Schema from a sequence of Fields. For pickle support. 

247 

248 Parameters 

249 ---------- 

250 fields : `tuple` ['lsst.afw.table.Field'] 

251 The fields to construct the new Schema from. 

252 

253 Returns 

254 ------- 

255 schema : `lsst.afw.table.Schema` 

256 The constructed Schema. 

257 """ 

258 schema = Schema() 

259 for field in fields: 

260 schema.addField(field) 

261 return schema