LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
LSSTDataManagementBasePackage
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 
22 import re
23 import os.path
24 import lsst.daf.base as dafBase
25 import lsst.afw.geom as afwGeom
26 import lsst.afw.table as afwTable
27 from lsst.daf.butler.formatters.fileFormatter import FileFormatter
28 from lsst.log import Log
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 = afwTable.SimpleCatalog(schema)
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 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.read(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 = Log.getLogger("ObjectMaskCatalog")
122 
123  brightObjects = ObjectMaskCatalog()
124  checkedWcsIsFk5 = False
125  NaN = float("NaN")*afwGeom.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"(\d+(?:\.\d*)?([d]*))" r"(?:\s+|\s*,\s*)"
162  r"([+-]?\d+(?:\.\d*)?)([d]*)" r"(?:\s+|\s*,\s*)"
163  r"([+-]?\d+(?:\.\d*)?)([d]*)" r"(?:\s+|\s*,\s*)?"
164  r"(?:([+-]?\d+(?:\.\d*)?)([d]*)"
165  r"\s*,\s*"
166  r"([+-]?\d+(?:\.\d*)?)([d]*)"
167  ")?"
168  r"(?:\s*|\s*\)\s*)" # close paren or space
169  r"\s*#\s*ID:\s*(\d+)" # start comment
170  r"(?:\s*,\s*mag:\s*(\d+\.\d*))?"
171  r"\s*$", line)
172  if mat:
173  _type, ra, raUnit, dec, decUnit, \
174  param1, param1Unit, param2, param2Unit, param3, param3Unit, \
175  _id, mag = mat.groups()
176 
177  _id = int(_id)
178  if mag is None:
179  mag = NaN
180  else:
181  mag = float(mag)
182 
183  ra = convertToAngle(ra, raUnit, "ra", fileName, lineNo)
184  dec = convertToAngle(dec, decUnit, "dec", fileName, lineNo)
185 
186  radius = NaN
187  width = NaN
188  height = NaN
189  angle = NaN
190 
191  if _type == "box":
192  width = convertToAngle(param1, param1Unit, "width", fileName, lineNo)
193  height = convertToAngle(param2, param2Unit, "height", fileName, lineNo)
194  angle = convertToAngle(param3, param3Unit, "angle", fileName, lineNo)
195 
196  if angle != 0.0:
197  log.warn("Rotated boxes are not supported: \"%s\" at %s:%d" % (
198  line, fileName, lineNo))
199  nFormatError += 1
200  elif _type == "circle":
201  radius = convertToAngle(param1, param1Unit, "radius", fileName, lineNo)
202 
203  if not (param2 is None and param3 is None):
204  log.warn("Extra parameters for circle: \"%s\" at %s:%d" % (
205  line, fileName, lineNo))
206  nFormatError += 1
207 
208  rec = brightObjects.addNew()
209  # N.b. rec["coord"] = Coord is not supported, so we have to use the setter
210  rec["type"] = _type
211  rec["id"] = _id
212  rec["mag"] = mag
213  rec.setCoord(afwGeom.SpherePoint(ra, dec))
214 
215  rec["angle"] = angle
216  rec["height"] = height
217  rec["width"] = width
218  rec["radius"] = radius
219  else:
220  log.warn("Unexpected line \"%s\" at %s:%d" % (line, fileName, lineNo))
221  nFormatError += 1
222 
223  if nFormatError > 0:
224  raise RuntimeError("Saw %d formatting errors in %s" % (nFormatError, fileName))
225 
226  if not checkedWcsIsFk5:
227  raise RuntimeError("Expected to see a line specifying an fk5 wcs in %s" % fileName)
228 
229  # This makes the deep copy contiguous in memory so that a ColumnView can be exposed to Numpy
230  brightObjects._catalog = brightObjects._catalog.copy(True)
231 
232  return brightObjects
233 
234 
235 def convertToAngle(var, varUnit, what, fileName, lineNo):
236  """Given a variable and its units, return an afwGeom.Angle
237 
238  what, fileName, and lineNo are used to generate helpful error messages
239  """
240  var = float(var)
241 
242  if varUnit in ("d", "", None):
243  pass
244  elif varUnit == "'":
245  var /= 60.0
246  elif varUnit == '"':
247  var /= 3600.0
248  else:
249  raise RuntimeError("unsupported unit \"%s\" for %s at %s:%d" %
250  (varUnit, what, fileName, lineNo))
251 
252  return var*afwGeom.degrees
253 
254 
255 class RegionFileFormatter(FileFormatter):
256  """Plugin for reading DS9 region file catalogs with Gen3 Butler.
257  """
258  extension = ".reg"
259 
260  def _readFile(self, path, pytype):
261  # Docstring inherited from FileFormatter._readFile
262  if not os.path.exists(path):
263  return None
264 
265  return pytype.read(path)
266 
267  def _writeFile(self, inMemoryDataset, fileDescriptor):
268  # Docstring inherited from FileFormatter._writeFile
269  raise NotImplementedError("Write not implemented.")
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
def convertToAngle(var, varUnit, what, fileName, lineNo)
Definition: objectMasks.py:235
daf::base::PropertySet * set
Definition: fits.cc:832
Definition: Log.h:691
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:63
Point in an unspecified spherical coordinate system.
Definition: SpherePoint.h:57
def readFits(cls, fileName, hdu=0, flags=0)
Definition: objectMasks.py:65