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