LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
objectMasks.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://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 <http://www.gnu.org/licenses/>.
21
22import re
23import os.path
24import logging
25import lsst.daf.base as dafBase
26import lsst.geom as geom
27import lsst.afw.table as afwTable
28from lsst.daf.butler.formatters.file import FileFormatter
29
30
32 """Class to support bright object masks
33
34 N.b. I/O is done by providing a readFits method which fools the butler.
35 """
36
37 def __init__(self):
38 schema = afwTable.SimpleTable.makeMinimalSchema()
39 schema.addField("type", str, "type of region (e.g. box, circle)", size=10)
40 schema.addField("radius", "Angle", "radius of mask (if type == circle")
41 schema.addField("height", "Angle", "height of mask (if type == box)")
42 schema.addField("width", "Angle", "width of mask (if type == box)")
43 schema.addField("angle", "Angle", "rotation of mask (if type == box)")
44 schema.addField("mag", float, "object's magnitude")
45
46 self._catalog_catalog = afwTable.SimpleCatalog(schema)
47 self._catalog_catalog.table.setMetadata(dafBase.PropertyList())
48
49 self.tabletable = self._catalog_catalog.table
50 self.addNewaddNew = self._catalog_catalog.addNew
51
52 def __len__(self):
53 return len(self._catalog_catalog)
54
55 def __iter__(self):
56 return iter(self._catalog_catalog)
57
58 def __getitem__(self, i):
59 return self._catalog_catalog.__getitem__(i)
60
61 def __setitem__(self, i, v):
62 return self._catalog_catalog.__setitem__(i, v)
63
64 @classmethod
65 def readFits(cls, fileName, hdu=0, flags=0):
66 """FitsCatalogStorage facade for `read`.
67
68 This method is intended for use by the Gen2 Butler only.
69
70 Parameters
71 ----------
72 fileName : `str`
73 Name of the file to read.
74 hdu : `int`
75 Provided for compatibility with the "FitsCatalogStorage" read API
76 defined in `lsst.daf.persistence`, and ignored here.
77 flags : `int`
78 Provided for compatibility with the "FitsCatalogStorage" read API
79 defined in `lsst.daf.persistence`, and ignored here.
80
81 Notes
82 -----
83 Having a `readFits` method makes the `ObjectCatalogMask` class
84 duck-type compatible with `lsst.afw.table` catalogs, to the extent
85 needed to support reading by the Gen2 Butler with no specialized code
86 in `lsst.daf.persistence`. The on-disk type should actually be an
87 ASCII ds9 region file, typically with a ".reg" suffix.
88 """
89 return cls.readread(fileName)
90
91 @staticmethod
92 def read(fileName):
93 """Read a ds9 region file, returning a ObjectMaskCatalog object
94
95 The files should be structured as follows:
96
97 # Description of catalogue as a comment
98 # CATALOG: catalog-id-string
99 # TRACT: 0
100 # PATCH: 5,4
101 # FILTER: HSC-I
102
103 wcs; fk5
104
105 circle(RA, DEC, RADIUS) # ID: 1, mag: 12.34
106 box(RA, DEC, XSIZE, YSIZE, THETA) # ID: 2, mag: 23.45
107 ...
108
109 The ", mag: XX.YY" is optional
110
111 The commented lines must be present, with the relevant fields such as
112 tract patch and filter filled in. The coordinate system must be listed
113 as above. Each patch is specified as a box or circle, with RA, DEC,
114 and dimensions specified in decimal degrees (with or without an
115 explicit "d").
116
117 Only (axis-aligned) boxes and circles are currently supported as
118 region definitions.
119 """
120
121 log = logging.getLogger("lsst.ObjectMaskCatalog")
122
123 brightObjects = ObjectMaskCatalog()
124 checkedWcsIsFk5 = False
125 NaN = float("NaN")*geom.degrees
126
127 nFormatError = 0 # number of format errors seen
128 with open(fileName) as fd:
129 for lineNo, line in enumerate(fd.readlines(), 1):
130 line = line.rstrip()
131
132 if re.search(r"^\s*#", line):
133 #
134 # Parse any line of the form "# key : value" and put them into the metadata.
135 #
136 # The medatdata values must be defined as outlined in the above docstring
137 #
138 # The value of these three keys will be checked,
139 # so get them right!
140 #
141 mat = re.search(r"^\s*#\s*([a-zA-Z][a-zA-Z0-9_]+)\s*:\s*(.*)", line)
142 if mat:
143 key, value = mat.group(1).lower(), mat.group(2)
144 if key == "tract":
145 value = int(value)
146
147 brightObjects.table.getMetadata().set(key, value)
148
149 line = re.sub(r"^\s*#.*", "", line)
150 if not line:
151 continue
152
153 if re.search(r"^\s*wcs\s*;\s*fk5\s*$", line, re.IGNORECASE):
154 checkedWcsIsFk5 = True
155 continue
156
157 # This regular expression parses the regions file for each region to be masked,
158 # with the format as specified in the above docstring.
159 mat = re.search(r"^\s*(box|circle)"
160 r"(?:\s+|\s*\‍(\s*)" # open paren or space
161 r"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)([d]*)" # ra + units
162 r"(?:\s+|\s*,\s*)" # sep
163 r"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)([d]*)" # dec + units
164 r"(?:\s+|\s*,\s*)" # sep
165 r"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)([d]*)" # param1 + units
166 r"(?:" # start optional 1
167 r"(?:\s+|\s*,\s*)" # sep
168 r"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)([d]*)" # param2 + units
169 r"(?:" # start optional 2
170 r"(?:\s+|\s*,\s*)" # sep
171 r"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)([d]*)" # param3 + units
172 ")?" # end optional 2
173 ")?" # end optional 1
174 r"(?:\s*|\s*\‍)\s*)" # close paren or space
175 r"#\s*ID:[\w\s]*(\d+)" # start comment, ID
176 r"(?:\s*,?\s*mag:\s*([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?))?"
177 r"\s*$", line)
178 if mat:
179 _type, ra, raUnit, dec, decUnit, \
180 param1, param1Unit, param2, param2Unit, param3, param3Unit, \
181 _id, mag = mat.groups()
182
183 _id = int(_id)
184 if mag is None:
185 mag = NaN
186 else:
187 mag = float(mag)
188
189 ra = convertToAngle(ra, raUnit, "ra", fileName, lineNo)
190 dec = convertToAngle(dec, decUnit, "dec", fileName, lineNo)
191
192 radius = NaN
193 width = NaN
194 height = NaN
195 angle = 0.0*geom.degrees
196
197 if _type == "box":
198 width = convertToAngle(param1, param1Unit, "width", fileName, lineNo)
199 height = convertToAngle(param2, param2Unit, "height", fileName, lineNo)
200 if param3 is not None:
201 angle = convertToAngle(param3, param3Unit, "angle", fileName, lineNo)
202
203 if angle != 0.0:
204 log.warning("Rotated boxes are not supported: \"%s\" at %s:%d",
205 line, fileName, lineNo)
206 nFormatError += 1
207 elif _type == "circle":
208 radius = convertToAngle(param1, param1Unit, "radius", fileName, lineNo)
209
210 if not (param2 is None and param3 is None):
211 log.warning("Extra parameters for circle: \"%s\" at %s:%d",
212 line, fileName, lineNo)
213 nFormatError += 1
214
215 rec = brightObjects.addNew()
216 # N.b. rec["coord"] = Coord is not supported, so we have to use the setter
217 rec["type"] = _type
218 rec["id"] = _id
219 rec["mag"] = mag
220 rec.setCoord(geom.SpherePoint(ra, dec))
221
222 rec["angle"] = angle
223 rec["height"] = height
224 rec["width"] = width
225 rec["radius"] = radius
226 else:
227 log.warning("Unexpected line \"%s\" at %s:%d", line, fileName, lineNo)
228 nFormatError += 1
229
230 if nFormatError > 0:
231 raise RuntimeError("Saw %d formatting errors in %s" % (nFormatError, fileName))
232
233 if not checkedWcsIsFk5:
234 raise RuntimeError("Expected to see a line specifying an fk5 wcs in %s" % fileName)
235
236 # This makes the deep copy contiguous in memory so that a ColumnView can be exposed to Numpy
237 brightObjects._catalog = brightObjects._catalog.copy(True)
238
239 return brightObjects
240
241
242def convertToAngle(var, varUnit, what, fileName, lineNo):
243 """Given a variable and its units, return an geom.Angle
244
245 what, fileName, and lineNo are used to generate helpful error messages
246 """
247 var = float(var)
248
249 if varUnit in ("d", "", None):
250 pass
251 elif varUnit == "'":
252 var /= 60.0
253 elif varUnit == '"':
254 var /= 3600.0
255 else:
256 raise RuntimeError("unsupported unit \"%s\" for %s at %s:%d" %
257 (varUnit, what, fileName, lineNo))
258
259 return var*geom.degrees
260
261
262class RegionFileFormatter(FileFormatter):
263 """Plugin for reading DS9 region file catalogs with Gen3 Butler.
264 """
265 extension = ".reg"
266
267 def _readFile(self, path, pytype):
268 # Docstring inherited from FileFormatter._readFile
269 if not os.path.exists(path):
270 return None
271
272 return pytype.read(path)
273
274 def _writeFile(self, inMemoryDataset, fileDescriptor):
275 # Docstring inherited from FileFormatter._writeFile
276 raise NotImplementedError("Write not implemented.")
Custom catalog class for record/table subclasses that are guaranteed to have an ID,...
Definition: SortedCatalog.h:42
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
Point in an unspecified spherical coordinate system.
Definition: SpherePoint.h:57
def readFits(cls, fileName, hdu=0, flags=0)
Definition: objectMasks.py:65
daf::base::PropertySet * set
Definition: fits.cc:912
def convertToAngle(var, varUnit, what, fileName, lineNo)
Definition: objectMasks.py:242