4 Matches fakes based on position stored in the calibrated exposure image header 20 NO_FOOTPRINT = lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS
24 """Combine the meas and forced_src catalogs.""" 25 if len(meas) != len(force):
26 raise Exception(
"# Meas and Forced_src catalogs should have " 29 mapper.addMinimalSchema(meas.schema)
30 newSchema = mapper.getOutputSchema()
32 newSchema.addField(
'force_deblend_nChild', type=np.int32)
33 newSchema.addField(
'force_base_ClassificationExtendedness_value', type=float)
34 newSchema.addField(
'force_ext_photometryKron_KronFlux_instFlux', type=float)
35 newSchema.addField(
'force_ext_photometryKron_KronFlux_instFluxErr', type=float)
36 newSchema.addField(
'force_base_PsfFlux_instFlux', type=float)
37 newSchema.addField(
'force_base_PsfFlux_instFluxErr', type=float)
38 newSchema.addField(
'force_ext_photometryKron_KronFlux_apCorr', type=float)
39 newSchema.addField(
'force_ext_photometryKron_KronFlux_apCorrErr', type=float)
40 newSchema.addField(
'force_base_PsfFlux_apCorr', type=float)
41 newSchema.addField(
'force_base_PsfFlux_apCorrErr', type=float)
42 newSchema.addField(
'force_modelfit_CModel_instFlux', type=float)
43 newSchema.addField(
'force_modelfit_CModel_instFluxErr', type=float)
44 newSchema.addField(
'force_modelfit_CModel_fracDev', type=float)
45 newSchema.addField(
'force_modelfit_CModel_exp_instFlux', type=float)
46 newSchema.addField(
'force_modelfit_CModel_exp_instFluxErr', type=float)
47 newSchema.addField(
'force_modelfit_CModel_dev_instFlux', type=float)
48 newSchema.addField(
'force_modelfit_CModel_dev_instFluxErr', type=float)
49 newSchema.addField(
'force_modelfit_CModel_apCorr', type=float)
50 newSchema.addField(
'force_modelfit_CModel_apCorrErr', type=float)
51 newSchema.addField(
'force_modelfit_CModel_exp_apCorr', type=float)
52 newSchema.addField(
'force_modelfit_CModel_exp_apCorrErr', type=float)
53 newSchema.addField(
'force_modelfit_CModel_dev_apCorr', type=float)
54 newSchema.addField(
'force_modelfit_CModel_dev_apCorrErr', type=float)
56 newCols = [
'deblend_nChild',
'base_ClassificationExtendedness_value',
57 'ext_photometryKron_KronFlux_instFlux',
'ext_photometryKron_KronFlux_instFluxErr',
58 'base_PsfFlux_instFlux',
'base_PsfFlux_instFluxErr',
59 'ext_photometryKron_KronFlux_apCorr',
'ext_photometryKron_KronFlux_apCorrErr',
60 'base_PsfFlux_apCorr',
'base_PsfFlux_apCorrErr',
61 'modelfit_CModel_instFlux',
'modelfit_CModel_instFluxErr',
62 'modelfit_CModel_exp_apCorr',
'modelfit_CModel_exp_apCorrErr',
63 'modelfit_CModel_exp_instFlux',
'modelfit_CModel_exp_instFlux',
64 'modelfit_CModel_exp_apCorr',
'modelfit_CModel_exp_apCorrErr',
65 'modelfit_CModel_dev_instFlux',
'modelfit_CModel_dev_instFluxErr',
66 'modelfit_CModel_dev_apCorr',
'modelfit_CModel_dev_apCorrErr',
67 'modelfit_CModel_fracDev']
68 measAlias = meas.schema.getAliasMap()
69 newAlias = newSchema.getAliasMap()
70 for aliasKey
in measAlias.keys():
71 newAlias.set(aliasKey, measAlias[aliasKey])
73 combSrc.extend(meas, mapper=mapper)
76 combSrc[
'force_' + key][:] = force[key][:]
81 def getMag(flux, fluxerr, zeropoint):
82 """Return the magnitude and error.""" 83 mag = -2.5 * np.log10(flux)
84 magerr = (2.5 / np.log(10.0) * fluxerr / flux)
85 return (mag.T + zeropoint).T, magerr
89 """Return the Re, b/a and PA for a given quadrupole moment.""" 93 b_a = (e.getB() / e.getA())
94 except ZeroDivisionError:
96 pa = (e.getTheta() * 180.0 / np.pi)
102 Match to the fake catalog and append those columns to the source table. 104 this assumes the sources are an astropy table 105 or it will throw a TypeError 107 if not isinstance(sources, astropy.table.Table):
108 raise TypeError(
"Expect an astropy table for sources" 109 " use getAstroTable to convert")
111 fakes = astropy.table.Table().read(fakeCatalog)
112 fakes.rename_column(
'ID',
'fakeId')
113 return astropy.table.join(sources, fakes, keys=
'fakeId', join_type=
'left')
118 Return the fake matches based on the information in the header. 120 returns a tuple with: 121 the positions in pixels of the fake sources added to the chip 122 the match is in a dictionary of the form: 123 {fakeid:[ind_of_match_in_sources,...],...} 125 look within a tolerance of 1 pixel in each direction 127 fakeXY = collections.defaultdict(tuple)
128 fakename = re.compile(
'FAKE([0-9]+)')
129 for card
in cal_md.names():
130 m = fakename.match(card)
132 x, y =
list(map(float, (cal_md.getScalar(card)).split(
',')))
133 fakeXY[
int(m.group(1))] = (x, y)
135 srcX, srcY = sources.getX(), sources.getY()
136 srcIndex = collections.defaultdict(list)
137 for fid, fcoord
in fakeXY.items():
138 distX = srcX - fcoord[0]
139 distY = srcY - fcoord[1]
140 matched = (np.abs(distX) < tol) & (np.abs(distY) < tol)
141 srcIndex[fid] = np.where(matched)[0]
143 return fakeXY, srcIndex
147 reffMatch=False, pix=0.168, minRad=None,
148 raCol='RA', decCol='Dec'):
150 Return the fake matches based on an radec match. 153 sources: source table object 154 radecCatFile: filename for fits file of fake object table, 156 bbox: Bounding Box of exposure (ccd or patch) within which 158 wcs: Wcs of source image 161 tol: tolerance within which to match sources, given in PIXELS 164 fakeXY: set of pixel positions of fake sources 165 srcIdx: matches to fake sources in a dictionary of the form 166 {fakeid:[ind_of_match_in_sources,...],...} 169 IOError: couldn't open radecCatFile 171 fakeXY = collections.defaultdict(tuple)
173 fakeCat = astropy.table.Table().read(radecCatFile, format=
'fits')
177 for fakeSrc
in fakeCat:
180 fakeCoord = wcs.skyToPixel(fakePix)
182 if bbox.contains(fakeCoord):
184 fakeXY[
int(fakeSrc[
'ID'])] = (fakeCoord.getX(),
186 (fakeSrc[
'reff'] / pix) * tol)
188 fakeXY[
int(fakeSrc[
'ID'])] = (fakeCoord.getX(),
192 srcX, srcY = sources.getX(), sources.getY()
193 srcIndex = collections.defaultdict(list)
194 srcClose = collections.defaultdict(list)
195 for fid, fcoord
in fakeXY.items():
196 distX = (srcX - fcoord[0])
197 distY = (srcY - fcoord[1])
198 distR = np.sqrt((np.abs(distX) ** 2.0) + (np.abs(distY) ** 2.0))
199 closest = np.nanargmin(distR)
201 if minRad
is not None:
202 if radMatch < minRad:
205 matched = (distR <= radMatch)
207 matched = (np.abs(distX) <= radMatch) & (np.abs(distY) <= radMatch)
209 srcIndex[fid] = np.where(matched)[0]
210 srcClose[fid] = closest
212 return fakeXY, srcIndex, srcClose
216 extraCols=(
'zeropoint',
'visit',
'ccd'),
217 includeMissing=
False, footprints=
False, radecMatch=
None,
218 multiband=
False, reffMatch=
False, pix=0.168, minRad=
None,
219 raCol=
'RA', decCol=
'Dec'):
221 Get list of sources which agree in pixel position with fake ones with tol. 223 This returns a sourceCatalog of all the matched fake objects, 224 note, there will be duplicates in this list, since I haven't 225 checked deblend.nchild, and I'm only doing a tolerance match, 226 which could include extra sources 228 The outputs can include extraCols as long as they are one of: 229 zeropoint, visit, ccd, thetaNorth, pixelScale 231 If includeMissing is true, then the pipeline looks at the fake sources 232 added in the header and includes an entry in the table for sources without 233 any measurements, specifically the 'id' column will be 0 235 radecMatch is the fakes table. if it's not None(default), then do an ra/dec 236 match with the input catalog instead of looking in the header for where the 239 coaddData =
"deepCoadd_calexp" 240 coaddMeta =
"deepCoadd_calexp_md" 242 availExtras = {
'zeropoint': {
'type': float,
'doc':
'zeropoint'},
243 'visit': {
'type': int,
'doc':
'visit id'},
244 'ccd': {
'type': int,
'doc':
'ccd id'},
246 'doc':
'angle to north'},
247 'pixelScale': {
'type': float,
248 'doc':
'pixelscale in arcsec/pixel'}}
250 if not np.in1d(extraCols,
list(availExtras.keys())).
all():
251 print(
"extraCols must be in ", availExtras)
254 if 'filter' not in dataId:
255 sources = butler.get(
'src', dataId,
256 flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS,
258 cal = butler.get(
'calexp', dataId, immediate=
True)
259 cal_md = butler.get(
'calexp_md', dataId, immediate=
True)
261 meas = butler.get(
'deepCoadd_meas', dataId,
264 force = butler.get(
'deepCoadd_forced_src', dataId,
268 cal = butler.get(coaddData, dataId, immediate=
True)
269 cal_md = butler.get(coaddMeta, dataId, immediate=
True)
271 print(
"skipping", dataId)
274 if (
'pixelScale' in extraCols)
or (
'thetaNorth' in extraCols):
276 availExtras[
'pixelScale'][
'value'] = wcs.getPixelScale().asArcseconds()
280 xMid = cal.getWidth() // 2
281 yMid = cal.getHeight() // 2
283 midCoord = wcs.pixelToSky(midPoint)
284 northSkyToPixelMatrix = wcs.linearizeSkyToPixel(midCoord, lsst.afw.geom.degrees)
285 northSkyToPixelMatrix = northSkyToPixelMatrix.getLinear()
286 availExtras[
'thetaNorth'][
'value'] = (np.arctan2(*tuple(northSkyToPixelMatrix
289 if 'visit' in extraCols:
290 availExtras[
'visit'][
'value'] = dataId[
'visit']
291 if 'ccd' in extraCols:
292 availExtras[
'ccd'][
'value'] = dataId[
'ccd']
293 if 'zeropoint' in extraCols:
294 zeropoint = 2.5 * np.log10(cal_md.getScalar(
'FLUXMAG0'))
295 availExtras[
'zeropoint'][
'value'] = zeropoint
297 if radecMatch
is None:
300 if minRad
is not None:
301 print(
"# The min matching radius is %4.1f pixel" % minRad)
315 mapper.addMinimalSchema(sources.schema)
316 newSchema = mapper.getOutputSchema()
317 newSchema.addField(
'fakeId', type=np.int32,
318 doc=
'id of fake source matched to position')
319 newSchema.addField(
'nMatched', type=np.int32,
320 doc=
'Number of matched objects')
321 newSchema.addField(
'nPrimary', type=np.int32,
322 doc=
'Number of unique matched objects')
323 newSchema.addField(
'nNoChild', type=np.int32,
324 doc=
'Number of matched objects with nchild==0')
325 newSchema.addField(
'rMatched', type=float,
326 doc=
'Radius used form atching obects, in pixel')
327 newSchema.addField(
'fakeOffX', type=float,
328 doc=
'offset from input fake position in X (pixels)')
329 newSchema.addField(
'fakeOffY', type=float,
330 doc=
'offset from input fake position in Y (pixels)')
331 newSchema.addField(
'fakeOffR', type=float,
332 doc=
'offset from input fake position in radius')
333 newSchema.addField(
'fakeClosest', type=
"Flag",
334 doc=
'Is this match the closest one?')
336 for extraName
in set(extraCols).intersection(availExtras):
337 newSchema.addField(extraName, type=availExtras[extraName][
'type'],
338 doc=availExtras[extraName][
'doc'])
341 srcList.reserve(sum([len(s)
for s
in srcIndex.values()]) +
342 (0
if not includeMissing
else list(srcIndex.values()).count([])))
344 centroidKey = sources.getCentroidKey()
345 isPrimary = sources.schema.find(
'detect_isPrimary').getKey()
346 nChild = sources.schema.find(
'force_deblend_nChild').getKey()
347 for ident, sindlist
in srcIndex.items():
348 rMatched = fakeXY[ident][2]
349 if minRad
is not None:
350 if rMatched < minRad:
352 nMatched = len(sindlist)
353 nPrimary = np.sum([sources[
int(obj)].get(isPrimary)
for obj
in sindlist])
354 nNoChild = np.sum([(sources[
int(obj)].get(nChild) == 0)
for 356 if includeMissing
and (nMatched == 0):
357 newRec = srcList.addNew()
358 newRec.set(
'fakeId', ident)
360 newRec.set(
'nMatched', 0)
361 newRec.set(
'rMatched', rMatched)
363 newRec = srcList.addNew()
364 newRec.assign(sources[
int(ss)], mapper)
365 newRec.set(
'fakeId', ident)
366 newRec.set(
'nMatched', nMatched)
367 newRec.set(
'nPrimary', nPrimary)
368 newRec.set(
'nNoChild', nNoChild)
369 newRec.set(
'rMatched', rMatched)
370 offsetX = (sources[
int(ss)].get(centroidKey).getX() -
372 newRec.set(
'fakeOffX', offsetX)
373 offsetY = (sources[
int(ss)].get(centroidKey).getY() -
375 newRec.set(
'fakeOffY', offsetY)
376 newRec.set(
'fakeOffR', np.sqrt(offsetX ** 2.0 + offsetY ** 2.0))
378 if int(ss) ==
int(srcClose[ident]):
379 newRec.set(
'fakeClosest',
True)
381 newRec.set(
'fakeClosest',
False)
384 srcList = srcList.copy(deep=
True)
386 for extraName
in set(extraCols).intersection(availExtras):
387 tempCol = srcList.get(extraName)
388 tempCol.fill(availExtras[extraName][
'value'])
395 Return an astropy table with all the src entries. 397 if the entries are complex objects, it breaks them down: 398 ellipse entries are broken into 399 ellipse_a = semi-major axis 400 ellipse_q = axis ratio (always < 1) 401 ellipse_theta = rotation of semi-major axis 402 from chip x-axis in degrees 403 if mags is True, returns the magnitudes for all the flux columns 405 tab = astropy.table.Table()
406 for name
in src.schema.getNames():
409 nameKey = src.schema.find(name).getKey()
411 tab.add_column(astropy.table.Column(name=name,
412 data=src.get(nameKey)))
413 except lsst.pex.exceptions.LsstException:
415 if type(src[0].get(nameKey))
is quadrupole:
416 """Check for shape measurements""" 419 tab.add_column(astropy.table.Column(name=name+
'_a',
421 tab.add_column(astropy.table.Column(name=name+
'_q',
423 tab.add_column(astropy.table.Column(name=name+
'_theta',
426 """Check for coordinate measurements""" 427 x, y =
list(zip(*[(s.get(nameKey).getRa().asDegrees(),
428 s.get(nameKey).getDec().asDegrees())
for s
in src]))
429 tab.add_column(astropy.table.Column(name=name+
'_ra', data=x))
430 tab.add_column(astropy.table.Column(name=name+
'_dec', data=y))
432 keyData = np.array([s.get(nameKey)
for s
in src])
433 tab.add_column(astropy.table.Column(name=name,
438 tab.remove_column(name)
439 newCol = astropy.table.Column(data=[s.get(nameKey).asDegrees()
441 dtype=float, name=name)
442 tab.add_column(newCol)
447 for col
in tab.colnames:
448 colMatch = (re.match(
r'^flux\.[a-z]+$', col)
or 449 re.match(
r'^flux\.[a-z]+.apcorr$', col)
or 450 re.match(
r'^force.flux\.[a-z]+$', col)
or 451 re.match(
r'^force.flux\.[a-z]+.apcorr$', col)
or 452 re.match(
'^force.cmodel.+flux$', col)
or 453 re.match(
'^force.cmodel.+flux.apcorr$', col)
or 454 re.match(
'^cmodel.+flux$', col)
or 455 re.match(
'^cmodel.+flux.apcorr$', col))
457 zp = tab[
'zeropoint']
if not re.search(
'apcorr', col)
else 0.0
458 mag, magerr =
getMag(tab[col], tab[col+
'.err'], zp)
459 tab.add_column(astropy.table.Column(name=re.sub(
'instFlux',
'mag',
462 tab.add_column(astropy.table.Column(name=re.sub(
'instFlux',
'mag',
470 filt=None, tol=1.0, pix=0.168,
471 fakeCat=None, pixMatch=False, multiband=False,
472 reffMatch=False, includeMissing=True, minRad=None,
473 raCol='RA', decCol='Dec'):
474 """Return matched catalog for each CCD or Patch.""" 476 print(
'Doing ccd %d' %
int(ccd))
478 {
'visit': visit,
'ccd':
int(ccd)},
479 includeMissing=includeMissing,
480 extraCols=(
'visit',
'ccd',
481 'zeropoint',
'pixelScale',
483 radecMatch=fakeCat
if not pixMatch
else None,
484 tol=tol, reffMatch=reffMatch, pix=pix,
485 minRad=minRad, raCol=raCol, decCol=decCol)
487 print(
'Doing patch %s' % ccd)
489 {
'tract': visit,
'patch': ccd,
491 includeMissing=includeMissing,
492 extraCols=(
'thetaNorth',
'pixelScale',
494 radecMatch=fakeCat
if not pixMatch
else None,
495 tol=tol, multiband=multiband,
496 reffMatch=reffMatch, pix=pix,
497 minRad=minRad, raCol=raCol, decCol=decCol)
500 print(
' No match returns!')
503 slist = mlis.copy(
True)
505 slist.extend(mlis,
True)
512 overwrite=False, filt=None, tol=1.0, pixMatch=False,
513 multiband=False, reffMatch=False, pix=0.168,
514 multijobs=1, includeMissing=True, minRad=None,
515 raCol='RA', decCol='Dec'):
517 Driver (main function) for return match to fakes. 519 INPUT: rootDir = rerun directory 520 visit = visit id (int) (or tracts) 521 ccdList = list of ccds to look at (or patches) 522 outdir = output directory for matched file, 523 None means no output written 524 fakeCat = fake catalog to match to, 525 None means the fake sources are just 526 extracted from the header of the CCDs based on 527 position but no matching is done 528 overwrite = whether to overwrite the existing output file, 530 pixMatch = do pixel matching instead of ra/dec matching 531 even if there is a catalog supplied 532 multiband = whether match to forced photometry catalogs 533 from multiband process 534 reffMatch = whether match fake sources in pixel radius 535 or using tol x Reff (Only for Ra, Dec match) 536 OUTPUT: returns an astropy.table.Table with all the entries 537 from the source catalog for objects which match in pixel 538 position to the fake sources 545 from joblib
import Parallel, delayed
546 mlist = Parallel(n_jobs=multijobs)(delayed(returnMatchSingle)(
547 butler,
None, visit, ccd,
550 includeMissing=includeMissing,
552 reffMatch=reffMatch, tol=tol,
557 raCol=raCol)
for ccd
in ccdList)
563 slist.extend(m,
True)
566 print(
"# Can not import joblib, stop multiprocessing!")
569 filt=filt, fakeCat=fakeCat,
570 includeMissing=includeMissing,
576 raCol=raCol, decCol=decCol)
580 filt=filt, fakeCat=fakeCat,
581 includeMissing=includeMissing,
587 raCol=raCol, decCol=decCol)
590 print(
"Returns no match....!")
596 if fakeCat
is not None:
599 if outfile
is not None:
601 astroTable.write(outfile+
'.fits', format=
'fits',
604 print(
"Try setting the option -w to overwrite the file.")
610 if __name__ ==
'__main__':
612 parser = argparse.ArgumentParser()
613 parser.add_argument(
'rootDir', help=
'root dir of data repo')
614 parser.add_argument(
'visit',
615 help=
'id of visit (or tract, if filter is specified)',
617 parser.add_argument(
'-f',
'--filter', dest=
'filt',
618 help=
'name of filter, if none assume single visit',
620 parser.add_argument(
'--ccd', nargs=
'+', help=
'id of ccd(s) or patches')
621 parser.add_argument(
'-o', help=
'outputfilename', default=
None,
623 parser.add_argument(
'-c', help=
'fake catalog', default=
None,
625 parser.add_argument(
'-w',
'--overwrite', help=
'overwrite output file',
626 dest=
'ow', default=
False, action=
'store_true')
627 parser.add_argument(
'-m',
'--multiband',
628 help=
'Match multiband measurements',
629 dest=
'multiband', default=
False, action=
'store_true')
630 parser.add_argument(
'-r',
'--reffMatch',
631 help=
'Match the fake sources using tol x Reff',
632 dest=
'reffMatch', default=
False, action=
'store_true')
633 parser.add_argument(
'--min',
'--minRad',
634 help=
'Minimum matching radius in unit of pixel',
635 dest=
'minRad', type=float, default=
None)
636 parser.add_argument(
'-t',
'--tolerance', type=float, dest=
'tol',
637 help=
'matching radius in PIXELS (default=1.0)')
638 parser.add_argument(
'-j',
'--multijobs', type=int,
639 help=
'Number of jobs run at the same time',
640 dest=
'multijobs', default=1)
641 parser.add_argument(
'--ra',
'--raCol', dest=
'raCol',
642 help=
'Name of the column for RA',
644 parser.add_argument(
'--dec',
'--decCol', dest=
'decCol',
645 help=
'Name of the column for Dec',
647 args = parser.parse_args()
650 args.fakeCat, overwrite=args.ow, filt=args.filt,
651 tol=args.tol, multiband=args.multiband,
652 reffMatch=args.reffMatch,
653 multijobs=args.multijobs,
655 raCol=args.raCol, decCol=args.decCol)
def getMag(flux, fluxerr, zeropoint)
A floating-point coordinate rectangle geometry.
A mapping between the keys of two Schemas, used to copy data between them.
daf::base::PropertySet * set
A class representing an angle.
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
def combineWithForce(meas, force)
def getFakeMatchesHeader(cal_md, sources, tol=1.0)
def returnMatchTable(rootDir, visit, ccdList, outfile=None, fakeCat=None, overwrite=False, filt=None, tol=1.0, pixMatch=False, multiband=False, reffMatch=False, pix=0.168, multijobs=1, includeMissing=True, minRad=None, raCol='RA', decCol='Dec')
def getFakeSources(butler, dataId, tol=1.0, extraCols=('zeropoint', 'visit', 'ccd'), includeMissing=False, footprints=False, radecMatch=None, multiband=False, reffMatch=False, pix=0.168, minRad=None, raCol='RA', decCol='Dec')
An ellipse core for the semimajor/semiminor axis and position angle parametrization (a...
def getAstroTable(src, mags=True)
def returnMatchSingle(butler, slist, visit, ccd, filt=None, tol=1.0, pix=0.168, fakeCat=None, pixMatch=False, multiband=False, reffMatch=False, includeMissing=True, minRad=None, raCol='RA', decCol='Dec')
Point in an unspecified spherical coordinate system.
def matchToFakeCatalog(sources, fakeCatalog)
def getFakeMatchesRaDec(sources, radecCatFile, bbox, wcs, tol=1.0, reffMatch=False, pix=0.168, minRad=None, raCol='RA', decCol='Dec')
daf::base::PropertyList * list