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