lsst.jointcal  16.0-21-g96865c2+1
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.pipe.base
32 
33 import lsst.jointcal.star
34 
35 
36 def createTwoFakeCcdImages(num1=4, num2=4, seed=100, fakeCcdId=12,
37  photoCalibMean1=1e-2, photoCalibMean2=1.2e-2):
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  fakeCcdId : `int`, optional
54  Sensor identifier to use for both CcdImages. The wcs, bbox, calib, etc.
55  will still be drawn from the CFHT ccd=12 files, as that is the only
56  testdata that is included in this simple test dataset.
57  photoCalibMean1, photoCalibMean2: `float`, optional
58  The mean photometric calibration to pass to each ccdImage construction.
59  Note: this value is 1/instFluxMag0, so it should be less than 1.
60 
61  Returns
62  -------
63  struct : `lsst.pipe.base.Struct`
64  Result struct with components:
65 
66  - `camera` : Camera representing these catalogs
67  (`lsst.afw.cameraGeom.Camera`).
68  - `catalogs` : Catalogs containing fake sources
69  (`list` of `lsst.afw.table.SourceCatalog`).
70  - `ccdImageList` : CcdImages containing the metadata and fake sources
71  (`list` of `lsst.jointcal.CcdImage`).
72  - `bbox` : Bounding Box of the image (`lsst.afw.geom.Box2I`).
73  - 'fluxFieldName' : name of the instFlux field in the catalogs ('str').
74  """
75  np.random.seed(seed)
76 
77  visit1 = 849375
78  visit2 = 850587
79  fluxFieldName = "SomeFlux"
80 
81  # Load or fake the necessary metadata for each CcdImage
82  dataDir = lsst.utils.getPackageDir('jointcal')
83  inputDir = os.path.join(dataDir, 'tests/data/cfht_minimal')
84  butler = lsst.daf.persistence.Butler(inputDir)
85 
86  # so we can access parts of the camera later (e.g. focal plane)
87  camera = butler.get('camera', visit=visit1)
88 
89  struct1 = createFakeCcdImage(butler, visit1, num1, fluxFieldName,
90  photoCalibMean=photoCalibMean1, photoCalibErr=1.0, fakeCcdId=fakeCcdId)
91  struct2 = createFakeCcdImage(butler, visit2, num2, fluxFieldName,
92  photoCalibMean=photoCalibMean2, photoCalibErr=5.0, fakeCcdId=fakeCcdId)
93 
94  return lsst.pipe.base.Struct(camera=camera,
95  catalogs=[struct1.catalog, struct2.catalog],
96  ccdImageList=[struct1.ccdImage, struct2.ccdImage],
97  bbox=struct1.bbox,
98  fluxFieldName=fluxFieldName)
99 
100 
101 def createFakeCcdImage(butler, visit, num, fluxFieldName,
102  photoCalibMean=1e-2, photoCalibErr=1.0, fakeCcdId=12):
103  """Create a fake CcdImage by making a fake catalog.
104 
105  Parameters
106  ----------
107  butler : `lsst.daf.persistence.Butler`
108  Butler to load metadata from.
109  visit : `int`
110  Visit identifier to build a butler dataId.
111  num : `int`
112  Number of sources to put in the catalogs. Should be
113  a square, to have sqrt(num) centroids on a grid.
114  fluxFieldName : `str`
115  Name of the flux field to populate in the catalog, without `_instFlux`
116  (e.g. "slot_CalibFlux").
117  photoCalibMean : `float`, optional
118  Value to set for calibrationMean in the created PhotoCalib.
119  Note: this value is 1/instFluxMag0, so it should be less than 1.
120  photoCalibErr : `float`, optional
121  Value to set for calibrationErr in the created PhotoCalib.
122  fakeCcdId : `int`, optional
123  Use this as the ccdId in the returned CcdImage.
124 
125  Returns
126  -------
127  struct : `lsst.pipe.base.Struct`
128  Result struct with components:
129 
130  - `catalog` : Catalogs containing fake sources
131  (`lsst.afw.table.SourceCatalog`).
132  - `ccdImage` : CcdImage containing the metadata and fake sources
133  (`lsst.jointcal.CcdImage`).
134  - `bbox` : Bounding Box of the image (`lsst.afw.geom.Box2I`).
135  """
136  ccdId = 12 # we only have data for ccd=12
137 
138  dataId = dict(visit=visit, ccd=ccdId)
139  skyWcs = butler.get('calexp_wcs', dataId=dataId)
140  visitInfo = butler.get('calexp_visitInfo', dataId=dataId)
141  bbox = butler.get('calexp_bbox', dataId=dataId)
142  detector = butler.get('calexp_detector', dataId=dataId)
143  filt = butler.get("calexp_filter", dataId=dataId).getName()
144  photoCalib = lsst.afw.image.PhotoCalib(photoCalibMean, photoCalibErr)
145 
146  catalog = createFakeCatalog(num, bbox, fluxFieldName, skyWcs=skyWcs)
147  ccdImage = lsst.jointcal.ccdImage.CcdImage(catalog, skyWcs, visitInfo, bbox, filt, photoCalib,
148  detector, visit, fakeCcdId, fluxFieldName)
149 
150  return lsst.pipe.base.Struct(catalog=catalog, ccdImage=ccdImage, bbox=bbox)
151 
152 
153 def createFakeCatalog(num, bbox, fluxFieldName, skyWcs=None, refCat=False):
154  """Return a fake minimally-useful catalog for jointcal.
155 
156  Parameters
157  ----------
158  num : `int`
159  Number of sources to put in the catalogs. Should be
160  a square, to have sqrt(num) centroids on a grid.
161  bbox : `lsst.afw.geom.Box2I`
162  Bounding Box of the detector to populate.
163  fluxFieldName : `str`
164  Name of the flux field to populate in the catalog, without `_instFlux`
165  (e.g. "slot_CalibFlux").
166  skyWcs : `lsst.afw.geom.SkyWcs` or None, optional
167  If supplied, use this to fill in coordinates from centroids.
168  refCat : `bool`, optional
169  Return a ``SimpleCatalog`` so that it behaves like a reference catalog?
170 
171  Returns
172  -------
173  catalog : `lsst.afw.table.SourceCatalog`
174  A populated source catalog.
175  """
177  # centroid
178  centroidKey = lsst.afw.table.Point2DKey.addFields(schema, "centroid", "centroid", "pixels")
179  xErrKey = schema.addField("centroid_xErr", type="F")
180  yErrKey = schema.addField("centroid_yErr", type="F")
181  # shape
182  shapeKey = lsst.afw.table.QuadrupoleKey.addFields(schema, "shape", "",
183  lsst.afw.table.CoordinateType.PIXEL)
184  # Put the fake sources in the minimal catalog.
185  schema.addField(fluxFieldName+"_instFlux", type="D", doc="post-ISR instFlux")
186  schema.addField(fluxFieldName+"_instFluxErr", type="D", doc="post-ISR instFlux stddev")
187  schema.addField(fluxFieldName+"_flux", type="D", doc="maggies")
188  schema.addField(fluxFieldName+"_fluxErr", type="D", doc="maggies stddev")
189  schema.addField(fluxFieldName+"_mag", type="D", doc="magnitude")
190  schema.addField(fluxFieldName+"_magErr", type="D", doc="magnitude stddev")
191  return fillCatalog(schema, num, bbox,
192  centroidKey, xErrKey, yErrKey, shapeKey, fluxFieldName,
193  skyWcs=skyWcs, refCat=refCat)
194 
195 
196 def fillCatalog(schema, num, bbox,
197  centroidKey, xErrKey, yErrKey, shapeKey, fluxFieldName,
198  skyWcs=None, fluxErrFraction=0.05, refCat=False):
199  """Return a catalog populated with fake, but reasonable, sources.
200 
201  Centroids are placed on a uniform grid, errors are normally distributed.
202 
203  Parameters
204  ----------
205  schema : `lsst.afw.table.Schema`
206  Pre-built schema to make the catalog from.
207  num : `int`
208  Number of sources to put in the catalog.
209  bbox : `lsst.afw.geom.Box2I`
210  Bounding box of the ccd to put sources in.
211  centroidKey : `lsst.afw.table.Key`
212  Key for the centroid field to populate.
213  xErrKey : `lsst.afw.table.Key`
214  Key for the xErr field to populate.
215  yErrKey : `lsst.afw.table.Key`
216  Key for the yErr field to populate.
217  shapeKey : `lsst.afw.table.Key`
218  Key for the shape field to populate.
219  fluxFieldName : `str`
220  Name of the flux field to populate in the catalog, without `_instFlux`
221  (e.g. "slot_CalibFlux").
222  skyWcs : `lsst.afw.geom.SkyWcs` or None, optional
223  If supplied, use this to fill in coordinates from centroids.
224  fluxErrFraction : `float`, optional
225  Fraction of instFlux to use for the instFluxErr.
226  refCat : `bool`, optional
227  Return a ``SimpleCatalog`` so that it behaves like a reference catalog?
228 
229  Returns
230  -------
231  catalog : `lsst.afw.table.SourceCatalog`
232  The filled catalog.
233  """
234  table = lsst.afw.table.SourceTable.make(schema)
235  table.defineCentroid('centroid')
236  table.defineShape('shape')
237  table.defineCalibFlux(fluxFieldName)
238  if refCat:
239  catalog = lsst.afw.table.SimpleCatalog(table)
240  else:
241  catalog = lsst.afw.table.SourceCatalog(table)
242 
243  instFlux = np.random.random(num)*10000
244  instFluxErr = np.abs(instFlux * np.random.normal(fluxErrFraction, scale=0.1, size=num))
245  xx = np.linspace(bbox.getMinX(), bbox.getMaxX(), int(np.sqrt(num)))
246  yy = np.linspace(bbox.getMinY(), bbox.getMaxY(), int(np.sqrt(num)))
247  xv, yv = np.meshgrid(xx, yy)
248  vx = np.random.normal(scale=0.1, size=num)
249  vy = np.random.normal(scale=0.1, size=num)
250 
251  # make all the sources perfectly spherical, for simplicity.
252  mxx = 1
253  myy = 1
254  mxy = 0
255 
256  for i, (x, y) in enumerate(zip(xv.ravel(), yv.ravel())):
257  record = catalog.addNew()
258  record.set('id', i)
259  record.set(centroidKey, lsst.afw.geom.Point2D(x, y))
260  record.set(shapeKey, lsst.afw.geom.ellipses.Quadrupole(mxx, myy, mxy))
261 
262  if skyWcs is not None:
263  lsst.afw.table.updateSourceCoords(skyWcs, catalog)
264 
265  catalog[xErrKey] = vx
266  catalog[yErrKey] = vy
267  catalog[fluxFieldName + '_instFlux'] = instFlux
268  catalog[fluxFieldName + '_instFluxErr'] = instFluxErr
269 
270  return catalog
271 
272 
273 def getMeasuredStarsFromCatalog(catalog, pixToFocal):
274  """Return a list of measuredStars built from a catalog.
275 
276  Parameters
277  ----------
278  catalog : `lsst.afw.table.SourceCatalog`
279  The table to get sources from.
280  pixToFocal : `lsst.afw.geom.TransformPoint2ToPoint2`
281  Transform that goes from pixel to focal plane coordinates, to set the
282  MeasuredStar x/y focal points.
283 
284  Returns
285  -------
286  stars : `list` of `lsst.jointcal.MeasuredStar`
287  MeasuredStars built from the catalog sources.
288  """
289  stars = []
290  for record in catalog:
291  star = lsst.jointcal.star.MeasuredStar()
292  star.x = record.getX()
293  star.y = record.getY()
294  star.setInstFluxAndErr(record.getCalibInstFlux(), record.getCalibInstFluxErr())
295  # TODO: cleanup after DM-4044
296  point = lsst.afw.geom.Point2D(star.x, star.y)
297  pointFocal = pixToFocal.applyForward(point)
298  star.setXFocal(pointFocal.getX())
299  star.setYFocal(pointFocal.getY())
300  stars.append(star)
301 
302  return stars
def createFakeCcdImage(butler, visit, num, fluxFieldName, photoCalibMean=1e-2, photoCalibErr=1.0, fakeCcdId=12)
Definition: testUtils.py:102
def createTwoFakeCcdImages(num1=4, num2=4, seed=100, fakeCcdId=12, photoCalibMean1=1e-2, photoCalibMean2=1.2e-2)
Definition: testUtils.py:37
def createFakeCatalog(num, bbox, fluxFieldName, skyWcs=None, refCat=False)
Definition: testUtils.py:153
void updateSourceCoords(geom::SkyWcs const &wcs, SourceCollection &sourceList)
std::string getPackageDir(std::string const &packageName)
def fillCatalog(schema, num, bbox, centroidKey, xErrKey, yErrKey, shapeKey, fluxFieldName, skyWcs=None, fluxErrFraction=0.05, refCat=False)
Definition: testUtils.py:198
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:273