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