lsst.pipe.tasks  14.0-57-ga659d1f3+2
objectMasks.py
Go to the documentation of this file.
1 from __future__ import absolute_import, division, print_function
2 from builtins import object
3 import re
4 import lsst.daf.base as dafBase
5 import lsst.afw.geom as afwGeom
6 import lsst.afw.table as afwTable
7 from lsst.log import Log
8 
9 
10 class ObjectMaskCatalog(object):
11  """Class to support bright object masks
12 
13  N.b. I/O is done by providing a readFits method which fools the butler.
14  """
15 
16  def __init__(self):
17  schema = afwTable.SimpleTable.makeMinimalSchema()
18  schema.addField("type", str, "type of region (e.g. box, circle)", size=10)
19  schema.addField("radius", "Angle", "radius of mask (if type == circle")
20  schema.addField("height", "Angle", "height of mask (if type == box)")
21  schema.addField("width", "Angle", "width of mask (if type == box)")
22  schema.addField("angle", "Angle", "rotation of mask (if type == box)")
23  schema.addField("mag", float, "object's magnitude")
24 
25  self._catalog = afwTable.SimpleCatalog(schema)
26  self._catalog.table.setMetadata(dafBase.PropertyList())
27 
28  self.table = self._catalog.table
29  self.addNew = self._catalog.addNew
30 
31  def __len__(self):
32  return len(self._catalog)
33 
34  def __iter__(self):
35  return iter(self._catalog)
36 
37  def __getitem__(self, i):
38  return self._catalog.__getitem__(i)
39 
40  def __setitem__(self, i, v):
41  return self._catalog.__setitem__(i, v)
42 
43  @staticmethod
44  def readFits(fileName, hdu=0, flags=0):
45  """Read a ds9 region file, returning a ObjectMaskCatalog object
46 
47  This method is called "readFits" to fool the butler. The corresponding mapper entry looks like
48  brightObjectMask: {
49  template: "deepCoadd/BrightObjectMasks/%(tract)d/BrightObjectMask-%(tract)d-%(patch)s-%(filter)s.reg"
50  python: "lsst.obs.subaru.objectMasks.ObjectMaskCatalog"
51  persistable: "PurePythonClass"
52  storage: "FitsCatalogStorage"
53  }
54  and this is the only way I know to get it to read a random file type, in this case a ds9 region file.
55 
56  This method expects to find files named as BrightObjectMask-%(tract)d-%(patch)s-%(filter)s.reg
57  The files should be structured as follows:
58 
59  # Description of catalogue as a comment
60  # CATALOG: catalog-id-string
61  # TRACT: 0
62  # PATCH: 5,4
63  # FILTER: HSC-I
64 
65  wcs; fk5
66 
67  circle(RA, DEC, RADIUS) # ID: 1, mag: 12.34
68  box(RA, DEC, XSIZE, YSIZE, THETA) # ID: 2, mag: 23.45
69  ...
70 
71  The ", mag: XX.YY" is optional
72 
73  The commented lines must be present, with the relevant fields such as tract patch and filter filled
74  in. The coordinate system must be listed as above. Each patch is specified as a box or circle, with
75  RA, DEC, and dimensions specified in decimal degrees (with or without an explicit "d").
76 
77  Only (axis-aligned) boxes and circles are currently supported as region definitions.
78  """
79 
80  log = Log.getLogger("ObjectMaskCatalog")
81 
82  brightObjects = ObjectMaskCatalog()
83  checkedWcsIsFk5 = False
84  NaN = float("NaN")*afwGeom.degrees
85 
86  nFormatError = 0 # number of format errors seen
87  with open(fileName) as fd:
88  for lineNo, line in enumerate(fd.readlines(), 1):
89  line = line.rstrip()
90 
91  if re.search(r"^\s*#", line):
92  #
93  # Parse any line of the form "# key : value" and put them into the metadata.
94  #
95  # The medatdata values must be defined as outlined in the above docstring
96  #
97  # The value of these three keys will be checked,
98  # so get them right!
99  #
100  mat = re.search(r"^\s*#\s*([a-zA-Z][a-zA-Z0-9_]+)\s*:\s*(.*)", line)
101  if mat:
102  key, value = mat.group(1).lower(), mat.group(2)
103  if key == "tract":
104  value = int(value)
105 
106  brightObjects.table.getMetadata().set(key, value)
107 
108  line = re.sub(r"^\s*#.*", "", line)
109  if not line:
110  continue
111 
112  if re.search(r"^\s*wcs\s*;\s*fk5\s*$", line, re.IGNORECASE):
113  checkedWcsIsFk5 = True
114  continue
115 
116  # This regular expression parses the regions file for each region to be masked,
117  # with the format as specified in the above docstring.
118  mat = re.search(r"^\s*(box|circle)"
119  "(?:\s+|\s*\(\s*)" # open paren or space
120  "(\d+(?:\.\d*)?([d]*))" "(?:\s+|\s*,\s*)"
121  "([+-]?\d+(?:\.\d*)?)([d]*)" "(?:\s+|\s*,\s*)"
122  "([+-]?\d+(?:\.\d*)?)([d]*)" "(?:\s+|\s*,\s*)?"
123  "(?:([+-]?\d+(?:\.\d*)?)([d]*)"
124  "\s*,\s*"
125  "([+-]?\d+(?:\.\d*)?)([d]*)"
126  ")?"
127  "(?:\s*|\s*\)\s*)" # close paren or space
128  "\s*#\s*ID:\s*(\d+)" # start comment
129  "(?:\s*,\s*mag:\s*(\d+\.\d*))?"
130  "\s*$", line)
131  if mat:
132  _type, ra, raUnit, dec, decUnit, \
133  param1, param1Unit, param2, param2Unit, param3, param3Unit, \
134  _id, mag = mat.groups()
135 
136  _id = int(_id)
137  if mag is None:
138  mag = NaN
139  else:
140  mag = float(mag)
141 
142  ra = convertToAngle(ra, raUnit, "ra", fileName, lineNo)
143  dec = convertToAngle(dec, decUnit, "dec", fileName, lineNo)
144 
145  radius = NaN
146  width = NaN
147  height = NaN
148  angle = NaN
149 
150  if _type == "box":
151  width = convertToAngle(param1, param1Unit, "width", fileName, lineNo)
152  height = convertToAngle(param2, param2Unit, "height", fileName, lineNo)
153  angle = convertToAngle(param3, param3Unit, "angle", fileName, lineNo)
154 
155  if angle != 0.0:
156  log.warn("Rotated boxes are not supported: \"%s\" at %s:%d" % (
157  line, fileName, lineNo))
158  nFormatError += 1
159  elif _type == "circle":
160  radius = convertToAngle(param1, param1Unit, "radius", fileName, lineNo)
161 
162  if not (param2 is None and param3 is None):
163  log.warn("Extra parameters for circle: \"%s\" at %s:%d" % (
164  line, fileName, lineNo))
165  nFormatError += 1
166 
167  rec = brightObjects.addNew()
168  # N.b. rec["coord"] = Coord is not supported, so we have to use the setter
169  rec["type"] = _type
170  rec["id"] = _id
171  rec["mag"] = mag
172  rec.setCoord(afwGeom.SpherePoint(ra, dec))
173 
174  rec["angle"] = angle
175  rec["height"] = height
176  rec["width"] = width
177  rec["radius"] = radius
178  else:
179  log.warn("Unexpected line \"%s\" at %s:%d" % (line, fileName, lineNo))
180  nFormatError += 1
181 
182  if nFormatError > 0:
183  raise RuntimeError("Saw %d formatting errors in %s" % (nFormatError, fileName))
184 
185  if not checkedWcsIsFk5:
186  raise RuntimeError("Expected to see a line specifying an fk5 wcs in %s" % fileName)
187 
188  # This makes the deep copy contiguous in memory so that a ColumnView can be exposed to Numpy
189  brightObjects._catalog = brightObjects._catalog.copy(True)
190 
191  return brightObjects
192 
193 
194 def convertToAngle(var, varUnit, what, fileName, lineNo):
195  """Given a variable and its units, return an afwGeom.Angle
196 
197  what, fileName, and lineNo are used to generate helpful error messages
198  """
199  var = float(var)
200 
201  if varUnit in ("d", "", None):
202  pass
203  elif varUnit == "'":
204  var /= 60.0
205  elif varUnit == '"':
206  var /= 3600.0
207  else:
208  raise RuntimeError("unsupported unit \"%s\" for %s at %s:%d" %
209  (varUnit, what, fileName, lineNo))
210 
211  return var*afwGeom.degrees
def convertToAngle(var, varUnit, what, fileName, lineNo)
Definition: objectMasks.py:194
def readFits(fileName, hdu=0, flags=0)
Definition: objectMasks.py:44