LSSTApplications  10.0+286,10.0+36,10.0+46,10.0-2-g4f67435,10.1+152,10.1+37,11.0,11.0+1,11.0-1-g47edd16,11.0-1-g60db491,11.0-1-g7418c06,11.0-2-g04d2804,11.0-2-g68503cd,11.0-2-g818369d,11.0-2-gb8b8ce7
LSSTDataManagementBasePackage
selectImages.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008, 2009, 2010 LSST Corporation.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
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 LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 import lsst.pex.config as pexConfig
24 import lsst.pex.exceptions as pexExceptions
25 import lsst.afw.geom as afwGeom
26 import lsst.pipe.base as pipeBase
27 
28 __all__ = ["BaseSelectImagesTask", "BaseExposureInfo", "WcsSelectImagesTask", "DatabaseSelectImagesConfig"]
29 
30 class DatabaseSelectImagesConfig(pexConfig.Config):
31  """Base configuration for subclasses of BaseSelectImagesTask that use a database"""
32  host = pexConfig.Field(
33  doc = "Database server host name",
34  dtype = str,
35  )
36  port = pexConfig.Field(
37  doc = "Database server port",
38  dtype = int,
39  )
40  database = pexConfig.Field(
41  doc = "Name of database",
42  dtype = str,
43  )
44  maxExposures = pexConfig.Field(
45  doc = "maximum exposures to select; intended for debugging; ignored if None",
46  dtype = int,
47  optional = True,
48  )
49 
50 class BaseExposureInfo(pipeBase.Struct):
51  """Data about a selected exposure
52  """
53  def __init__(self, dataId, coordList):
54  """Create exposure information that can be used to generate data references
55 
56  The object has the following fields:
57  - dataId: data ID of exposure (a dict)
58  - coordList: a list of corner coordinates of the exposure (list of afwCoord.IcrsCoord)
59  plus any others items that are desired
60  """
61  super(BaseExposureInfo, self).__init__(dataId=dataId, coordList=coordList)
62 
63 
64 class BaseSelectImagesTask(pipeBase.Task):
65  """Base task for selecting images suitable for coaddition
66  """
67  ConfigClass = pexConfig.Config
68  _DefaultName = "selectImages"
69 
70  @pipeBase.timeMethod
71  def run(self, coordList):
72  """Select images suitable for coaddition in a particular region
73 
74  @param[in] coordList: list of coordinates defining region of interest; if None then select all images
75  subclasses may add additional keyword arguments, as required
76 
77  @return a pipeBase Struct containing:
78  - exposureInfoList: a list of exposure information objects (subclasses of BaseExposureInfo),
79  which have at least the following fields:
80  - dataId: data ID dictionary
81  - coordList: coordinates of the corner of the exposure (list of afwCoord.IcrsCoord)
82  """
83  raise NotImplementedError()
84 
85  def _runArgDictFromDataId(self, dataId):
86  """Extract keyword arguments for run (other than coordList) from a data ID
87 
88  @return keyword arguments for run (other than coordList), as a dict
89  """
90  raise NotImplementedError()
91 
92  def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[]):
93  """Run based on a data reference
94 
95  This delegates to run() and _runArgDictFromDataId() to do the actual
96  selection. In the event that the selectDataList is non-empty, this will
97  be used to further restrict the selection, providing the user with
98  additional control over the selection.
99 
100  @param[in] dataRef: data reference; must contain any extra keys needed by the subclass
101  @param[in] coordList: list of coordinates defining region of interest; if None, search the whole sky
102  @param[in] makeDataRefList: if True, return dataRefList
103  @param[in] selectDataList: List of SelectStruct with dataRefs to consider for selection
104  @return a pipeBase Struct containing:
105  - exposureInfoList: a list of objects derived from ExposureInfo
106  - dataRefList: a list of data references (None if makeDataRefList False)
107  """
108  runArgDict = self._runArgDictFromDataId(dataRef.dataId)
109  exposureInfoList = self.run(coordList, **runArgDict).exposureInfoList
110 
111  if len(selectDataList) > 0 and len(exposureInfoList) > 0:
112  # Restrict the exposure selection further
113  ccdKeys, ccdValues = _extractKeyValue(exposureInfoList)
114  inKeys, inValues = _extractKeyValue([s.dataRef for s in selectDataList], keys=ccdKeys)
115  inValues = set(inValues)
116  newExposureInfoList = []
117  for info, ccdVal in zip(exposureInfoList, ccdValues):
118  if ccdVal in inValues:
119  newExposureInfoList.append(info)
120  else:
121  self.log.info("De-selecting exposure %s: not in selectDataList" % info.dataId)
122  exposureInfoList = newExposureInfoList
123 
124  if makeDataRefList:
125  butler = dataRef.butlerSubset.butler
126  dataRefList = [butler.dataRef(datasetType = "calexp",
127  dataId = expInfo.dataId,
128  ) for expInfo in exposureInfoList]
129  else:
130  dataRefList = None
131 
132  return pipeBase.Struct(
133  dataRefList = dataRefList,
134  exposureInfoList = exposureInfoList,
135  )
136 
137 
138 def _extractKeyValue(dataList, keys=None):
139  """Extract the keys and values from a list of dataIds
140 
141  The input dataList is a list of objects that have 'dataId' members.
142  This allows it to be used for both a list of data references and a
143  list of ExposureInfo
144  """
145  assert len(dataList) > 0
146  if keys is None:
147  keys = sorted(dataList[0].dataId.keys())
148  keySet = set(keys)
149  values = list()
150  for data in dataList:
151  thisKeys = set(data.dataId.keys())
152  if thisKeys != keySet:
153  raise RuntimeError("DataId keys inconsistent: %s vs %s" % (keySet, thisKeys))
154  values.append(tuple(data.dataId[k] for k in keys))
155  return keys, values
156 
157 
158 class SelectStruct(pipeBase.Struct):
159  """A container for data to be passed to the WcsSelectImagesTask"""
160  def __init__(self, dataRef, wcs, dims):
161  super(SelectStruct, self).__init__(dataRef=dataRef, wcs=wcs, dims=dims)
162 
163 
165  """Select images using their Wcs"""
166  def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[]):
167  """Select images in the selectDataList that overlap the patch
168 
169  We use the "convexHull" function in the geom package to define
170  polygons on the celestial sphere, and test the polygon of the
171  patch for overlap with the polygon of the image.
172 
173  We use "convexHull" instead of generating a SphericalConvexPolygon
174  directly because the standard for the inputs to SphericalConvexPolygon
175  are pretty high and we don't want to be responsible for reaching them.
176  If "convexHull" is found to be too slow, we can revise this.
177 
178  @param dataRef: Data reference for coadd/tempExp (with tract, patch)
179  @param coordList: List of Coord specifying boundary of patch
180  @param makeDataRefList: Construct a list of data references?
181  @param selectDataList: List of SelectStruct, to consider for selection
182  """
183  from lsst.geom import convexHull
184 
185  dataRefList = []
186  exposureInfoList = []
187 
188  patchVertices = [coord.getVector() for coord in coordList]
189  patchPoly = convexHull(patchVertices)
190 
191  for data in selectDataList:
192  dataRef = data.dataRef
193  imageWcs = data.wcs
194  nx,ny = data.dims
195 
196  imageBox = afwGeom.Box2D(afwGeom.Point2D(0,0), afwGeom.Extent2D(nx, ny))
197  try:
198  imageCorners = [imageWcs.pixelToSky(pix) for pix in imageBox.getCorners()]
199  except (pexExceptions.DomainError, pexExceptions.RuntimeError) as e:
200  # Protecting ourselves from awful Wcs solutions in input images
201  self.log.logdebug("WCS error in testing calexp %s (%s): deselecting" % (dataRef.dataId, e))
202  continue
203 
204  imagePoly = convexHull([coord.getVector() for coord in imageCorners])
205  if patchPoly.intersects(imagePoly): # "intersects" also covers "contains" or "is contained by"
206  self.log.info("Selecting calexp %s" % dataRef.dataId)
207  dataRefList.append(dataRef)
208  exposureInfoList.append(BaseExposureInfo(dataRef.dataId, imageCorners))
209 
210  return pipeBase.Struct(
211  dataRefList = dataRefList if makeDataRefList else None,
212  exposureInfoList = exposureInfoList,
213  )
A floating-point coordinate rectangle geometry.
Definition: Box.h:271