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