lsst.jointcal  15.0-12-gd965ca7+2
testUtils.py
Go to the documentation of this file.
1 # This file is part of jointcal.
2 
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (http://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
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 GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 """Functions to help create jointcal tests by generating fake data."""
22 
23 __all__ = ['createFakeCatalog', 'createTwoFakeCcdImages', 'getMeasuredStarsFromCatalog']
24 
25 import os
26 import numpy as np
27 
28 import lsst.afw.geom
29 import lsst.afw.table
31 import lsst.obs.lsstSim
32 import lsst.pipe.base
33 
34 import lsst.jointcal.star
35 
36 
37 def createTwoFakeCcdImages(num1=4, num2=4, seed=100):
38  """Return two fake ccdImages built on CFHT Megacam metadata.
39 
40  If ``num1 == num2``, the catalogs will align on-sky so each source will
41  have a match in the other catalog.
42 
43  This uses the butler dataset stored in `tests/data/cfht_minimal` to
44  bootstrap the metadata.
45 
46  Parameters
47  ----------
48  num1, num2 : `int`, optional
49  Number of sources to put in the first and second catalogs. Should be
50  a square, to have sqrt(num) centroids on a grid.
51  seed : `int`, optional
52  Seed value for np.random.
53 
54  Returns
55  -------
56  struct : `lsst.pipe.base.Struct`
57  Result struct with components:
58 
59  - `camera` : Camera representing these catalogs
60  (`lsst.afw.cameraGeom.Camera`).
61  - `catalogs` : Catalogs containing fake sources
62  (`list` of `lsst.afw.table.SourceCatalog`).
63  - `ccdImageList` : CcdImages containing the metadata and fake sources
64  (`list` of `lsst.jointcal.CcdImage`).
65  - `bbox` : Bounding Box of the image (`lsst.afw.geom.Box2I`).
66  """
67  np.random.seed(seed)
68 
69  visit1 = 849375
70  visit2 = 850587
71  ccdId = 12
72  instFluxKeyName = "SomeFlux"
73 
74  # Load or fake the necessary metadata for each CcdImage
75  dataDir = lsst.utils.getPackageDir('jointcal')
76  inputDir = os.path.join(dataDir, 'tests/data/cfht_minimal')
77  butler = lsst.daf.persistence.Butler(inputDir)
78 
79  # so we can access parts of the camera later (e.g. focal plane)
80  camera = butler.get('camera', visit=visit1, ccd=ccdId)
81 
82  struct1 = createFakeCcdImage(butler, visit1, ccdId, num1, instFluxKeyName,
83  photoCalibMean=100.0, photoCalibErr=1.0)
84  struct2 = createFakeCcdImage(butler, visit2, ccdId, num2, instFluxKeyName,
85  photoCalibMean=120.0, photoCalibErr=5.0)
86 
87  return lsst.pipe.base.Struct(camera=camera,
88  catalogs=[struct1.catalog, struct2.catalog],
89  ccdImageList=[struct1.ccdImage, struct2.ccdImage],
90  bbox=struct1.bbox)
91 
92 
93 def createFakeCcdImage(butler, visit, ccdId, num, instFluxKeyName,
94  photoCalibMean=100.0, photoCalibErr=1.0):
95  """Create a fake CcdImage by making a fake catalog.
96 
97  Parameters
98  ----------
99  butler : `lsst.daf.persistence.Butler`
100  Butler to load metadata from.
101  visit : `int`
102  Visit identifier to build a butler dataId.
103  ccdId : `int`
104  CCD identifier to build a butler dataId.
105  num : `int`
106  Number of sources to put in the catalogs. Should be
107  a square, to have sqrt(num) centroids on a grid.
108  instFluxKeyName : `str`
109  Name of the instFluxKey to populate in the catalog.
110  photoCalibMean : `float`, optional
111  Value to set for calibrationMean in the created PhotoCalib.
112  photoCalibErr : `float`, optional
113  Value to set for calibrationErr in the created PhotoCalib.
114 
115  Returns
116  -------
117  struct : `lsst.pipe.base.Struct`
118  Result struct with components:
119 
120  - `catalog` : Catalogs containing fake sources
121  (`lsst.afw.table.SourceCatalog`).
122  - `ccdImage` : CcdImage containing the metadata and fake sources
123  (`lsst.jointcal.CcdImage`).
124  - `bbox` : Bounding Box of the image (`lsst.afw.geom.Box2I`).
125  """
126  dataId = dict(visit=visit, ccd=ccdId)
127  skyWcs = butler.get('calexp_wcs', dataId=dataId)
128  visitInfo = butler.get('calexp_visitInfo', dataId=dataId)
129  bbox = butler.get('calexp_bbox', dataId=dataId)
130  detector = butler.get('calexp_detector', dataId=dataId)
131  filt = butler.get("calexp_filter", dataId=dataId).getName()
132  photoCalib = lsst.afw.image.PhotoCalib(photoCalibMean, photoCalibErr)
133 
134  catalog = createFakeCatalog(num, bbox, instFluxKeyName, skyWcs=skyWcs)
135  ccdImage = lsst.jointcal.ccdImage.CcdImage(catalog, skyWcs, visitInfo, bbox, filt, photoCalib,
136  detector, visit, ccdId, instFluxKeyName)
137 
138  return lsst.pipe.base.Struct(catalog=catalog, ccdImage=ccdImage, bbox=bbox)
139 
140 
141 def createFakeCatalog(num, bbox, instFluxKeyName, skyWcs=None, refCat=False):
142  """Return a fake minimally-useful catalog for jointcal.
143 
144  Parameters
145  ----------
146  num : `int`
147  Number of sources to put in the catalogs. Should be
148  a square, to have sqrt(num) centroids on a grid.
149  bbox : `lsst.afw.geom.Box2I`
150  Bounding Box of the detector to populate.
151  instFluxKeyName : `str`
152  Name of the instFluxKey to populate in the catalog.
153  skyWcs : `lsst.afw.geom.SkyWcs` or None, optional
154  If supplied, use this to fill in coordinates from centroids.
155  refCat : `bool`, optional
156  Return a ``SimpleCatalog`` so that it behaves like a reference catalog?
157 
158  Returns
159  -------
160  catalog : `lsst.afw.table.SourceCatalog`
161  A populated source catalog.
162  """
164  # centroid
165  centroidKey = lsst.afw.table.Point2DKey.addFields(schema, "centroid", "centroid", "pixels")
166  xErrKey = schema.addField("centroid_xSigma", type="F")
167  yErrKey = schema.addField("centroid_ySigma", type="F")
168  # shape
169  shapeKey = lsst.afw.table.QuadrupoleKey.addFields(schema, "shape", "",
170  lsst.afw.table.CoordinateType.PIXEL)
171  # Put the fake sources in the minimal catalog.
172  schema.addField(instFluxKeyName+"_flux", type="D", doc="post-ISR instFlux")
173  schema.addField(instFluxKeyName+"_fluxSigma", type="D", doc="post-ISR instFlux stddev")
174  schema.addField(instFluxKeyName+"_calFlux", type="D", doc="maggies")
175  schema.addField(instFluxKeyName+"_calFluxErr", type="D", doc="maggies stddev")
176  schema.addField(instFluxKeyName+"_mag", type="D", doc="magnitude")
177  schema.addField(instFluxKeyName+"_magErr", type="D", doc="magnitude stddev")
178  return fillCatalog(schema, num, bbox,
179  centroidKey, xErrKey, yErrKey, shapeKey, instFluxKeyName,
180  skyWcs=skyWcs, refCat=refCat)
181 
182 
183 def fillCatalog(schema, num, bbox,
184  centroidKey, xErrKey, yErrKey, shapeKey, instFluxKeyName,
185  skyWcs=None, fluxErrFraction=0.05, refCat=False):
186  """Return a catalog populated with fake, but reasonable, sources.
187 
188  Centroids are placed on a uniform grid, errors are normally distributed.
189 
190  Parameters
191  ----------
192  schema : `lsst.afw.table.Schema`
193  Pre-built schema to make the catalog from.
194  num : `int`
195  Number of sources to put in the catalog.
196  bbox : `lsst.afw.geom.Box2I`
197  Bounding box of the ccd to put sources in.
198  centroidKey : `lsst.afw.table.Key`
199  Key for the centroid field to populate.
200  xErrKey : `lsst.afw.table.Key`
201  Key for the xErr field to populate.
202  yErrKey : `lsst.afw.table.Key`
203  Key for the yErr field to populate.
204  shapeKey : `lsst.afw.table.Key`
205  Key for the shape field to populate.
206  instFluxKeyName : `str`
207  Name of instFlux field to populate (i.e. instFluxKeyName+'_flux')
208  skyWcs : `lsst.afw.geom.SkyWcs` or None, optional
209  If supplied, use this to fill in coordinates from centroids.
210  fluxErrFraction : `float`, optional
211  Fraction of instFlux to use for the instFluxErr.
212  refCat : `bool`, optional
213  Return a ``SimpleCatalog`` so that it behaves like a reference catalog?
214 
215  Returns
216  -------
217  catalog : `lsst.afw.table.SourceCatalog`
218  The filled catalog.
219  """
220  table = lsst.afw.table.SourceTable.make(schema)
221  table.defineCentroid('centroid')
222  table.defineShape('shape')
223  table.defineInstFlux(instFluxKeyName)
224  if refCat:
225  catalog = lsst.afw.table.SimpleCatalog(table)
226  else:
227  catalog = lsst.afw.table.SourceCatalog(table)
228 
229  instFlux = np.random.random(num)
230  instFluxErr = instFlux * fluxErrFraction
231  xx = np.linspace(bbox.getMinX(), bbox.getMaxX(), int(np.sqrt(num)))
232  yy = np.linspace(bbox.getMinY(), bbox.getMaxY(), int(np.sqrt(num)))
233  xv, yv = np.meshgrid(xx, yy)
234  vx = np.random.normal(scale=0.1, size=num)
235  vy = np.random.normal(scale=0.1, size=num)
236 
237  # make all the sources perfectly spherical, for simplicity.
238  mxx = 1
239  myy = 1
240  mxy = 0
241 
242  for i, (x, y) in enumerate(zip(xv.ravel(), yv.ravel())):
243  record = catalog.addNew()
244  record.set('id', i)
245  record.set(centroidKey, lsst.afw.geom.Point2D(x, y))
246  record.set(shapeKey, lsst.afw.geom.ellipses.Quadrupole(mxx, myy, mxy))
247 
248  if skyWcs is not None:
249  lsst.afw.table.updateSourceCoords(skyWcs, catalog)
250 
251  catalog[xErrKey] = vx
252  catalog[yErrKey] = vy
253  catalog[instFluxKeyName + '_flux'] = instFlux
254  catalog[instFluxKeyName + '_fluxSigma'] = instFluxErr
255 
256  return catalog
257 
258 
259 def getMeasuredStarsFromCatalog(catalog, pixToFocal):
260  """Return a list of measuredStars built from a catalog.
261 
262  Parameters
263  ----------
264  catalog : `lsst.afw.table.SourceCatalog`
265  The table to get sources from.
266  pixToFocal : `lsst.afw.geom.TransformPoint2ToPoint2`
267  Transform that goes from pixel to focal plane coordinates, to set the
268  MeasuredStar x/y focal points.
269 
270  Returns
271  -------
272  stars : `list` of `lsst.jointcal.MeasuredStar`
273  MeasuredStars built from the catalog sources.
274  """
275  stars = []
276  for record in catalog:
277  star = lsst.jointcal.star.MeasuredStar()
278  star.x = record.getX()
279  star.y = record.getY()
280  star.setInstFlux(record.getInstFlux())
281  star.setInstFluxErr(record.getInstFluxErr())
282  # TODO: cleanup after DM-4044
283  point = lsst.afw.geom.Point2D(star.x, star.y)
284  pointFocal = pixToFocal.applyForward(point)
285  star.setXFocal(pointFocal.getX())
286  star.setYFocal(pointFocal.getY())
287  stars.append(star)
288 
289  return stars
def createFakeCatalog(num, bbox, instFluxKeyName, skyWcs=None, refCat=False)
Definition: testUtils.py:141
def createFakeCcdImage(butler, visit, ccdId, num, instFluxKeyName, photoCalibMean=100.0, photoCalibErr=1.0)
Definition: testUtils.py:94
def fillCatalog(schema, num, bbox, centroidKey, xErrKey, yErrKey, shapeKey, instFluxKeyName, skyWcs=None, fluxErrFraction=0.05, refCat=False)
Definition: testUtils.py:185
void updateSourceCoords(geom::SkyWcs const &wcs, SourceCollection &sourceList)
std::string getPackageDir(std::string const &packageName)
static QuadrupoleKey addFields(Schema &schema, std::string const &name, std::string const &doc, CoordinateType coordType=CoordinateType::PIXEL)
static Schema makeMinimalSchema()
static std::shared_ptr< SourceTable > make(Schema const &schema, std::shared_ptr< IdFactory > const &idFactory)
def getMeasuredStarsFromCatalog(catalog, pixToFocal)
Definition: testUtils.py:259
def createTwoFakeCcdImages(num1=4, num2=4, seed=100)
Definition: testUtils.py:37