LSSTApplications  10.0-2-g4f67435,11.0.rc2+1,11.0.rc2+12,11.0.rc2+3,11.0.rc2+4,11.0.rc2+5,11.0.rc2+6,11.0.rc2+7,11.0.rc2+8
LSSTDataManagementBasePackage
debugger.py
Go to the documentation of this file.
1 """This is a useful module for debugging measurements.
2 
3 It relies on the presence of a catalog already produced by running
4 measurements in the regular context. You provide the image and
5 catalog on the command-line, a list of source identifiers and the
6 measurement configuration in the config; the module reads the inputs,
7 subsets the catalog to contain only the sources of interest, and measures
8 those. This reduces the frustration of waiting for image processing
9 and the measurements to run on many other sources, greatly increasing
10 debugging efficiency.
11 """
12 
13 import sys
14 from argparse import ArgumentParser, Namespace
15 import lsst.pex.logging as pexLog
16 from lsst.pex.config import Config, ConfigurableField, Field, ListField
17 from lsst.pipe.base import CmdLineTask, ConfigValueAction, ConfigFileAction, TaskRunner, Struct
18 import lsst.afw.image as afwImage
19 import lsst.afw.table as afwTable
20 
21 from lsst.meas.algorithms.measurement import SourceMeasurementTask
22 
24  sourceId = ListField(dtype=int, default=[], doc="List of source identifiers to measure")
25  outputName = Field(dtype=str, default="catalog.fits", doc="Output name for source catalog")
26  measurement = ConfigurableField(target=SourceMeasurementTask, doc="Measurements")
27 
28 class MeasurementDebuggerRunner(TaskRunner):
29  """Provide the image and catalog names to the Task
30 
31  We provide a dummy dataRef only to avoid further overrides
32  of this class.
33  """
34  @staticmethod
35  def getTargetList(parsedCmd, **kwargs):
36  kwargs["image"] = parsedCmd.image
37  kwargs["catalog"] = parsedCmd.catalog
38  return [(Struct(dataId="<none>"), kwargs)]
39 
40 class MeasurementDebuggerArgumentParser(ArgumentParser):
41  """A stripped down version of the pipe_base ArgumentParser
42 
43  We don't care about the butler, just the config, and log.
44  """
45  def __init__(self, *args, **kwargs):
46  super(MeasurementDebuggerArgumentParser, self).__init__(*args, **kwargs)
47  self.add_argument("image", help="Name of image to measure")
48  self.add_argument("catalog", help="Name of catalog to measure")
49  self.add_argument("-c", "--config", nargs="*", action=ConfigValueAction,
50  help="config override(s), e.g. -c foo=newfoo bar.baz=3", metavar="NAME=VALUE")
51  self.add_argument("-C", "--configfile", dest="configfile", nargs="*", action=ConfigFileAction,
52  help="config override file(s)")
53  self.add_argument("--doraise", action="store_true",
54  help="raise an exception on error (else log a message and continue)?")
55  self.add_argument("--logdest", help="logging destination")
56 
57  def parse_args(self, config, args=None, log=None, override=None):
58  if args == None:
59  args = sys.argv[1:]
60  namespace = Namespace()
61  namespace.config = config
62  namespace.clobberConfig = False
63  namespace.butler = None
64  namespace.log = log if log is not None else pexLog.Log.getDefaultLog()
65  namespace = super(MeasurementDebuggerArgumentParser, self).parse_args(args=args, namespace=namespace)
66  del namespace.configfile
67  return namespace
68 
69 
70 class MeasurementDebuggerTask(CmdLineTask):
71  _DefaultName = "debugger"
72  ConfigClass = MeasurementDebuggerConfig
73  RunnerClass = MeasurementDebuggerRunner
74 
75  def __init__(self, schema=None, **kwargs):
76  super(MeasurementDebuggerTask, self).__init__(**kwargs)
77  if schema is None:
78  schema = afwTable.SourceTable.makeMinimalSchema()
79  self.schema = schema
80  self.makeSubtask("measurement", schema=schema)
81 
82  @classmethod
85 
86  def run(self, dataRef, image, catalog):
87  exp = self.readImage(image)
88  sources = self.readSources(catalog)
89  sources = self.subsetSources(sources)
90  sources = self.mapSchemas(sources)
91  self.measurement.measure(exp, sources)
92  self.writeSources(sources)
93  return Struct(exp=exp, sources=sources)
94 
95  def readImage(self, image):
96  exp = afwImage.ExposureF(image)
97  self.log.info("Read %dx%d image" % (exp.getWidth(), exp.getHeight()))
98  return exp
99 
100  def readSources(self, catalog):
101  sources = afwTable.SourceCatalog.readFits(catalog)
102  self.log.info("Read %d sources" % len(sources))
103  return sources
104 
105  def mapSchemas(self, sources):
106  catalog = afwTable.SourceCatalog(self.schema)
107  for ss in sources:
108  new = catalog.addNew()
109  new.setFootprint(ss.getFootprint())
110  for name in self.schema.getNames():
111  if name in ss.schema:
112  new.set(name, ss.get(name))
113  return catalog
114 
115  def subsetSources(self, sources):
116  """Return a subset of the input catalog
117 
118  The full catalog is used if the 'sourceId' list is empty.
119 
120  Parent sources (in the deblending sense) are also added to the
121  subset so that they can be removed (via replaceWithNoise).
122  """
123  if not self.config.sourceId:
124  return sources
125 
126  identifiers = set(self.config.sourceId)
127  subset = afwTable.SourceCatalog(sources.table)
128  while len(identifiers) > 0:
129  ident = identifiers.pop()
130  ss = sources.find(ident)
131  if ss is None:
132  raise RuntimeError("Unable to find id=%d in catalog" % ident)
133  subset.append(ss)
134  parent = ss.getParent()
135  if parent:
136  identifiers.add(parent)
137  self.log.info("Subset to %d sources" % len(subset))
138  return subset
139 
140  def writeSources(self, sources):
141  sources.writeFits(self.config.outputName)
142  self.log.info("Wrote %s" % self.config.outputName)
143 
144  def writeConfig(self, *args, **kwargs):
145  pass
146  def writeMetadata(self, *args, **kwargs):
147  pass
148  def writeSchemas(self, *args, **kwargs):
149  pass
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:55