LSST Applications 27.0.0,g0265f82a02+469cd937ee,g02d81e74bb+21ad69e7e1,g1470d8bcf6+cbe83ee85a,g2079a07aa2+e67c6346a6,g212a7c68fe+04a9158687,g2305ad1205+94392ce272,g295015adf3+81dd352a9d,g2bbee38e9b+469cd937ee,g337abbeb29+469cd937ee,g3939d97d7f+72a9f7b576,g487adcacf7+71499e7cba,g50ff169b8f+5929b3527e,g52b1c1532d+a6fc98d2e7,g591dd9f2cf+df404f777f,g5a732f18d5+be83d3ecdb,g64a986408d+21ad69e7e1,g858d7b2824+21ad69e7e1,g8a8a8dda67+a6fc98d2e7,g99cad8db69+f62e5b0af5,g9ddcbc5298+d4bad12328,ga1e77700b3+9c366c4306,ga8c6da7877+71e4819109,gb0e22166c9+25ba2f69a1,gb6a65358fc+469cd937ee,gbb8dafda3b+69d3c0e320,gc07e1c2157+a98bf949bb,gc120e1dc64+615ec43309,gc28159a63d+469cd937ee,gcf0d15dbbd+72a9f7b576,gdaeeff99f8+a38ce5ea23,ge6526c86ff+3a7c1ac5f1,ge79ae78c31+469cd937ee,gee10cc3b42+a6fc98d2e7,gf1cff7945b+21ad69e7e1,gfbcc870c63+9a11dc8c8f
LSST Data Management Base Package
Loading...
Searching...
No Matches
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# (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__ = ["ObjectMaskCatalog", "RegionFileFormatter"]
23
24import re
25import os.path
26import logging
27import lsst.daf.base as dafBase
28import lsst.geom as geom
29import lsst.afw.table as afwTable
30from lsst.daf.butler.formatters.file import FileFormatter
31
32
34 """Class to support bright object masks
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
47 self._catalog.table.setMetadata(dafBase.PropertyList())
48
49 self.table = self._catalog.table
50 self.addNew = self._catalog.addNew
51
52 def __len__(self):
53 return len(self._catalog)
54
55 def __iter__(self):
56 return iter(self._catalog)
57
58 def __getitem__(self, i):
59 return self._catalog.__getitem__(i)
60
61 def __setitem__(self, i, v):
62 return self._catalog.__setitem__(i, v)
63
64 @classmethod
65 def read(cls, fileName):
66 """Read a ds9 region file, returning a ObjectMaskCatalog object
67
68 The files should be structured as follows:
69
70 # Description of catalogue as a comment
71 # CATALOG: catalog-id-string
72 # TRACT: 0
73 # PATCH: 5,4
74 # FILTER: HSC-I
75
76 wcs; fk5
77
78 circle(RA, DEC, RADIUS) # ID: 1, mag: 12.34
79 box(RA, DEC, XSIZE, YSIZE, THETA) # ID: 2, mag: 23.45
80 ...
81
82 The ", mag: XX.YY" is optional
83
84 The commented lines must be present, with the relevant fields such as
85 tract patch and filter filled in. The coordinate system must be listed
86 as above. Each patch is specified as a box or circle, with RA, DEC,
87 and dimensions specified in decimal degrees (with or without an
88 explicit "d").
89
90 Only (axis-aligned) boxes and circles are currently supported as
91 region definitions.
92 """
93
94 log = logging.getLogger("lsst.ObjectMaskCatalog")
95
96 brightObjects = cls()
97 checkedWcsIsFk5 = False
98 NaN = float("NaN")*geom.degrees
99
100 nFormatError = 0 # number of format errors seen
101 with open(fileName) as fd:
102 for lineNo, line in enumerate(fd.readlines(), 1):
103 line = line.rstrip()
104
105 if re.search(r"^\s*#", line):
106 #
107 # Parse any line of the form "# key : value" and put them into the metadata.
108 #
109 # The medatdata values must be defined as outlined in the above docstring
110 #
111 # The value of these three keys will be checked,
112 # so get them right!
113 #
114 mat = re.search(r"^\s*#\s*([a-zA-Z][a-zA-Z0-9_]+)\s*:\s*(.*)", line)
115 if mat:
116 key, value = mat.group(1).lower(), mat.group(2)
117 if key == "tract":
118 value = int(value)
119
120 brightObjects.table.getMetadata().set(key, value)
121
122 line = re.sub(r"^\s*#.*", "", line)
123 if not line:
124 continue
125
126 if re.search(r"^\s*wcs\s*;\s*fk5\s*$", line, re.IGNORECASE):
127 checkedWcsIsFk5 = True
128 continue
129
130 # This regular expression parses the regions file for each region to be masked,
131 # with the format as specified in the above docstring.
132 mat = re.search(r"^\s*(box|circle)"
133 r"(?:\s+|\s*\‍(\s*)" # open paren or space
134 r"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)([d]*)" # ra + units
135 r"(?:\s+|\s*,\s*)" # sep
136 r"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)([d]*)" # dec + units
137 r"(?:\s+|\s*,\s*)" # sep
138 r"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)([d]*)" # param1 + units
139 r"(?:" # start optional 1
140 r"(?:\s+|\s*,\s*)" # sep
141 r"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)([d]*)" # param2 + units
142 r"(?:" # start optional 2
143 r"(?:\s+|\s*,\s*)" # sep
144 r"([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)([d]*)" # param3 + units
145 ")?" # end optional 2
146 ")?" # end optional 1
147 r"(?:\s*|\s*\‍)\s*)" # close paren or space
148 r"#\s*ID:[\w\s]*(\d+)" # start comment, ID
149 r"(?:\s*,?\s*mag:\s*([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?))?"
150 r"\s*$", line)
151 if mat:
152 _type, ra, raUnit, dec, decUnit, \
153 param1, param1Unit, param2, param2Unit, param3, param3Unit, \
154 _id, mag = mat.groups()
155
156 _id = int(_id)
157 if mag is None:
158 mag = NaN
159 else:
160 mag = float(mag)
161
162 ra = convertToAngle(ra, raUnit, "ra", fileName, lineNo)
163 dec = convertToAngle(dec, decUnit, "dec", fileName, lineNo)
164
165 radius = NaN
166 width = NaN
167 height = NaN
168 angle = 0.0*geom.degrees
169
170 if _type == "box":
171 width = convertToAngle(param1, param1Unit, "width", fileName, lineNo)
172 height = convertToAngle(param2, param2Unit, "height", fileName, lineNo)
173 if param3 is not None:
174 angle = convertToAngle(param3, param3Unit, "angle", fileName, lineNo)
175
176 if angle != 0.0:
177 log.warning("Rotated boxes are not supported: \"%s\" at %s:%d",
178 line, fileName, lineNo)
179 nFormatError += 1
180 elif _type == "circle":
181 radius = convertToAngle(param1, param1Unit, "radius", fileName, lineNo)
182
183 if not (param2 is None and param3 is None):
184 log.warning("Extra parameters for circle: \"%s\" at %s:%d",
185 line, fileName, lineNo)
186 nFormatError += 1
187
188 rec = brightObjects.addNew()
189 # N.b. rec["coord"] = Coord is not supported, so we have to use the setter
190 rec["type"] = _type
191 rec["id"] = _id
192 rec["mag"] = mag
193 rec.setCoord(geom.SpherePoint(ra, dec))
194
195 rec["angle"] = angle
196 rec["height"] = height
197 rec["width"] = width
198 rec["radius"] = radius
199 else:
200 log.warning("Unexpected line \"%s\" at %s:%d", line, fileName, lineNo)
201 nFormatError += 1
202
203 if nFormatError > 0:
204 raise RuntimeError("Saw %d formatting errors in %s" % (nFormatError, fileName))
205
206 if not checkedWcsIsFk5:
207 raise RuntimeError("Expected to see a line specifying an fk5 wcs in %s" % fileName)
208
209 # This makes the deep copy contiguous in memory so that a ColumnView can be exposed to Numpy
210 brightObjects._catalog = brightObjects._catalog.copy(True)
211
212 return brightObjects
213
214
215def convertToAngle(var, varUnit, what, fileName, lineNo):
216 """Given a variable and its units, return an geom.Angle
217
218 what, fileName, and lineNo are used to generate helpful error messages
219 """
220 var = float(var)
221
222 if varUnit in ("d", "", None):
223 pass
224 elif varUnit == "'":
225 var /= 60.0
226 elif varUnit == '"':
227 var /= 3600.0
228 else:
229 raise RuntimeError("unsupported unit \"%s\" for %s at %s:%d" %
230 (varUnit, what, fileName, lineNo))
231
232 return var*geom.degrees
233
234
235class RegionFileFormatter(FileFormatter):
236 """Plugin for reading DS9 region file catalogs with Gen3 Butler.
237 """
238 extension = ".reg"
239
240 def _readFile(self, path, pytype):
241 # Docstring inherited from FileFormatter._readFile
242 if not os.path.exists(path):
243 return None
244
245 return pytype.read(path)
246
247 def _writeFile(self, inMemoryDataset, fileDescriptor):
248 # Docstring inherited from FileFormatter._writeFile
249 raise NotImplementedError("Write not implemented.")
Custom catalog class for record/table subclasses that are guaranteed to have an ID,...
Class for storing ordered metadata with comments.
Point in an unspecified spherical coordinate system.
Definition SpherePoint.h:57
_writeFile(self, inMemoryDataset, fileDescriptor)
daf::base::PropertySet * set
Definition fits.cc:931
convertToAngle(var, varUnit, what, fileName, lineNo)