Coverage for python/lsst/display/firefly/footprints.py: 6%

99 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-21 05:13 +0000

1# This file is part of {{ cookiecutter.package_name }}. 

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 

23import numpy as np 

24from astropy.io.votable.tree import Info 

25from astropy.io.votable import from_table 

26from astropy.table import Column 

27 

28import lsst.geom as geom 

29import lsst.afw.table as afwTable 

30 

31 

32def recordSelector(record, selection): 

33 """Select records from source catalog 

34 

35 Parameters: 

36 ----------- 

37 record : `lsst.afw.detect.SourceRecord` 

38 record to select 

39 selection : `str` 

40 'all' to select all records. 'blended parents' to select records with 

41 more than zero children. 'deblended children' to select records with 

42 non-zero parents. 'isolated' to select records that are not blended, 

43 meaning zero parents and zero children. 

44 Values to check for sel 

45 """ 

46 # Catalogs missing the 'deblend_nChild' column were not deblended 

47 if 'deblend_nChild' in record.schema.getNames(): 

48 nChildren = record.get('deblend_nChild') 

49 else: 

50 nChildren = 0 

51 parentId = record.getParent() 

52 if selection == 'all': 

53 return True 

54 elif selection == 'blended parents': 

55 return (nChildren > 0) 

56 elif selection == 'deblended children': 

57 return (parentId > 0) 

58 elif selection == 'isolated': 

59 return ((parentId == 0) and (nChildren == 0)) 

60 else: 

61 raise RuntimeError( 

62 f'invalid selection: {selection}\n' 

63 'Must be one of "all", "blended parents", "deblended children", "isolated"' 

64 ) 

65 

66 

67def createFootprintsTable(catalog, xy0=None, insertColumn=4): 

68 """make a VOTable of SourceData table and footprints 

69 

70 Parameters: 

71 ----------- 

72 catalog : `lsst.afw.table.SourceCatalog` 

73 Source catalog from which to display footprints. 

74 xy0 : tuple or list or None 

75 Pixel origin to subtract off from the footprint coordinates. 

76 If None, the value used is (0,0) 

77 insertColumn : `int` 

78 Column at which to insert the "family_id" and "category" columns 

79 

80 Returns: 

81 -------- 

82 `astropy.io.votable.voTableFile` 

83 VOTable object to upload to Firefly 

84 """ 

85 if xy0 is None: 

86 xy0 = geom.Point2I(0, 0) 

87 

88 _catalog = afwTable.SourceCatalog(catalog.table.clone()) 

89 _catalog.extend(catalog, deep=True) 

90 sourceTable = _catalog.asAstropy() 

91 

92 # Change int64 dtypes so they convert to VOTable 

93 for colName in sourceTable.colnames: 

94 if sourceTable[colName].dtype.num == 9: 

95 sourceTable[colName].dtype = np.dtype('long') 

96 

97 inputColumnNames = sourceTable.colnames 

98 

99 x0, y0 = xy0 

100 # The 'deblend_nChild' column is only present if the catalog was deblended 

101 isDeblended = 'deblend_nChild' in catalog.schema.getNames() 

102 spanList = [] 

103 peakList = [] 

104 familyList = [] 

105 categoryList = [] 

106 fpxll = [] 

107 fpyll = [] 

108 fpxur = [] 

109 fpyur = [] 

110 for record in catalog: 

111 footprint = record.getFootprint() 

112 recordId = record.getId() 

113 spans = footprint.getSpans() 

114 scoords = [(s.getY()-y0, s.getX0()-x0, s.getX1()-x0) for s in spans] 

115 scoords = np.array(scoords).flatten() 

116 scoords = np.ma.MaskedArray(scoords, mask=np.zeros(len(scoords), 

117 dtype=np.bool)) 

118 fpbbox = footprint.getBBox() 

119 corners = [(c.getX()-x0, c.getY()-y0) for c in fpbbox.getCorners()] 

120 fpxll.append(corners[0][0]) 

121 fpyll.append(corners[0][1]) 

122 fpxur.append(corners[2][0]) 

123 fpyur.append(corners[2][1]) 

124 peaks = footprint.getPeaks() 

125 pcoords = [(p.getFx()-x0, p.getFy()-y0) for p in peaks] 

126 pcoords = np.array(pcoords).flatten() 

127 pcoords = np.ma.MaskedArray(pcoords, mask=np.zeros(len(pcoords), 

128 dtype=np.bool)) 

129 fpbbox = footprint.getBBox() 

130 parentId = record.getParent() 

131 nChild = record.get('deblend_nChild') if isDeblended else 0 

132 if parentId == 0: 

133 familyList.append(recordId) 

134 if nChild > 0: 

135 # blended parent 

136 categoryList.append('blended parent') 

137 else: 

138 # isolated 

139 categoryList.append('isolated') 

140 else: 

141 # deblended child 

142 familyList.append(parentId) 

143 categoryList.append('deblended child') 

144 spanList.append(scoords) 

145 peakList.append(pcoords) 

146 

147 sourceTable.add_column(Column(np.array(familyList)), 

148 name='family_id', 

149 index=insertColumn) 

150 sourceTable.add_column(Column(np.array(categoryList)), 

151 name='category', 

152 index=insertColumn+1) 

153 spanColumn = np.fromiter(spanList, dtype=object, count=len(spanList)) 

154 peakColumn = np.fromiter(peakList, dtype=object, count=len(peakList)) 

155 sourceTable.add_column(Column(spanColumn), name='spans') 

156 sourceTable.add_column(Column(peakColumn), name='peaks') 

157 sourceTable.add_column(Column(np.array(fpxll)), name='footprint_corner1_x') 

158 sourceTable.add_column(Column(np.array(fpyll)), name='footprint_corner1_y') 

159 sourceTable.add_column(Column(np.array(fpxur)), name='footprint_corner2_x') 

160 sourceTable.add_column(Column(np.array(fpyur)), name='footprint_corner2_y') 

161 

162 outputVO = from_table(sourceTable) 

163 outTable = outputVO.get_first_table() 

164 

165 outTable.infos.append(Info(name='contains_lsst_footprints', value='true')) 

166 outTable.infos.append(Info(name='contains_lsst_measurements', value='true')) 

167 outTable.infos.append(Info(name='FootPrintColumnNames', 

168 value='id;footprint_corner1_x;footprint_corner1_y;' + 

169 'footprint_corner2_x;footprint_corner2_y;spans;peaks')) 

170 outTable.infos.append(Info(name='pixelsys', value='zero-based')) 

171 # Check whether the coordinates are included and are valid 

172 if (('slot_Centroid_x' in inputColumnNames) and 

173 ('slot_Centroid_y' in inputColumnNames) and 

174 np.isfinite(outTable.array['slot_Centroid_x']).any() and 

175 np.isfinite(outTable.array['slot_Centroid_y']).any()): 

176 coord_column_string = 'slot_Centroid_x;slot_Centroid_y;ZERO_BASED' 

177 elif (('coord_ra' in inputColumnNames) and 

178 ('coord_dec' in inputColumnNames) and 

179 np.isfinite(outTable.array['coord_ra']).any() and 

180 np.isfinite(outTable.array['coord_dec']).any()): 

181 coord_column_string = 'coord_ra;coord_dec;EQ_J2000' 

182 elif (('base_SdssCentroid_x' in inputColumnNames) and 

183 ('base_SdssCentroid_y' in inputColumnNames) and 

184 np.isfinite(outTable.array['base_SdssCentroid_x']).any() and 

185 np.isfinite(outTable.array['base_SdssCentroid_y']).any()): 

186 coord_column_string = 'base_SdssCentroid_x;base_SdssCentroid_y;ZERO_BASED' 

187 else: 

188 raise RuntimeError('No valid coordinate columns in catalog') 

189 outTable.infos.append(Info(name='CatalogCoordColumns', 

190 value=coord_column_string)) 

191 

192 for f in outTable.fields: 

193 if f.datatype == 'bit': 

194 f.datatype = 'boolean' 

195 

196 outTable._config['version_1_3_or_later'] = True 

197 outputVO.set_all_tables_format('binary2') 

198 

199 return outputVO