243 ):
244 r"""Fit a PSF + smooth background model (linear) to a small region
245 around a peak.
246
247 See fitPsfs for a more thorough description, including all
248 parameters not described below.
249
250 Parameters
251 ----------
252 fp: `afw.detection.Footprint`
253 Footprint containing the Peaks to model.
254 fmask: `afw.image.Mask`
255 The Mask plane for pixels in the Footprint
256 pk: `afw.detection.PeakRecord`
257 The peak within the Footprint that we are going to fit with PSF model
258 pkF: `afw.geom.Point2D`
259 Floating point coordinates of the peak.
260 pkres: `meas.deblender.DeblendedPeak`
261 Peak results object that will hold the results.
262 fbb: `afw.geom.Box2I`
263 Bounding box of ``fp``
264 peaks: `afw.detection.PeakCatalog`
265 Catalog of peaks contained in the parent footprint.
266 peaksF: list of `afw.geom.Point2D`
267 List of floating point coordinates of all of the peaks.
268 psf: list of `afw.detection.Psf`\ s
269 Psf of the ``maskedImage`` for each band.
270 psffwhm: list pf `float`\ s
271 FWHM of the ``maskedImage``\ 's ``psf`` in each band.
272 img: `afw.image.ImageF`
273 The image that contains the footprint.
274 varimg: `afw.image.ImageF`
275 The variance of the image that contains the footprint.
276
277 Results
278 -------
279 ispsf: `bool`
280 Whether or not the peak matches a PSF model.
281 """
282 import lsstDebug
283
284
287
288
289
290 R0 = int(np.ceil(psffwhm*1.))
291
292 R1 = int(np.ceil(psffwhm*1.5))
293 cx, cy = pkF.getX(), pkF.getY()
294 psfimg = psf.computeImage(cx, cy)
295
296 R2 = R1 +
min(psfimg.getWidth(), psfimg.getHeight())/2.
297
298 pbb = psfimg.getBBox()
299 pbb.clip(fbb)
300 px0, py0 = psfimg.getX0(), psfimg.getY0()
301
302
303
305 pkres.setOutOfBounds()
306 return
307
308
309 xlo = int(np.floor(cx - R1))
310 ylo = int(np.floor(cy - R1))
311 xhi = int(np.ceil(cx + R1))
312 yhi = int(np.ceil(cy + R1))
314 stampbb.clip(fbb)
315 xlo, xhi = stampbb.getMinX(), stampbb.getMaxX()
316 ylo, yhi = stampbb.getMinY(), stampbb.getMaxY()
317 if xlo > xhi or ylo > yhi:
318 log.trace('Skipping this peak: out of bounds')
319 pkres.setOutOfBounds()
320 return
321
322
323 if min(stampbb.getWidth(), stampbb.getHeight()) <=
max(tinyFootprintSize, 2):
324
325
326 log.trace('Skipping this peak: tiny footprint / close to edge')
327 pkres.setTinyFootprint()
328 return
329
330
331 otherpeaks = []
332 for pk2, pkF2 in zip(peaks, peaksF):
333 if pk2 == pk:
334 continue
335 if pkF.distanceSquared(pkF2) > R2**2:
336 continue
337 opsfimg = psf.computeImage(pkF2.getX(), pkF2.getY())
338 if not opsfimg.getBBox().overlaps(stampbb):
339 continue
340 otherpeaks.append(opsfimg)
341 log.trace('%i other peaks within range', len(otherpeaks))
342
343
344
345
346
347 NT1 = 4 + len(otherpeaks)
348
349 NT2 = NT1 + 2
350
351 NP = (1 + yhi - ylo)*(1 + xhi - xlo)
352
353 I_psf = 0
354 I_sky = 1
355 I_sky_ramp_x = 2
356 I_sky_ramp_y = 3
357
358 I_opsf = 4
359 I_dx = NT1 + 0
360 I_dy = NT1 + 1
361
362
363 ix0, iy0 = img.getX0(), img.getY0()
364 fx0, fy0 = fbb.getMinX(), fbb.getMinY()
365 fslice = (slice(ylo-fy0, yhi-fy0+1), slice(xlo-fx0, xhi-fx0+1))
366 islice = (slice(ylo-iy0, yhi-iy0+1), slice(xlo-ix0, xhi-ix0+1))
367 fmask_sub = fmask .getArray()[fslice]
368 var_sub = varimg.getArray()[islice]
369 img_sub = img.getArray()[islice]
370
371
372 psfarr = psfimg.getArray()[pbb.getMinY()-py0: 1+pbb.getMaxY()-py0,
373 pbb.getMinX()-px0: 1+pbb.getMaxX()-px0]
374 px0, px1 = pbb.getMinX(), pbb.getMaxX()
375 py0, py1 = pbb.getMinY(), pbb.getMaxY()
376
377
378 valid = (fmask_sub > 0)
379 xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
380 RR = ((xx - cx)**2)[np.newaxis, :] + ((yy - cy)**2)[:, np.newaxis]
381 valid *= (RR <= R1**2)
382 valid *= (var_sub > 0)
383 NP = valid.sum()
384
385 if NP == 0:
386 log.warning('Skipping peak at (%.1f, %.1f): no unmasked pixels nearby', cx, cy)
387 pkres.setNoValidPixels()
388 return
389
390
391 XX, YY = np.meshgrid(xx, yy)
392 ipixes = np.vstack((XX[valid] - xlo, YY[valid] - ylo)).T
393
394 inpsfx = (xx >= px0)*(xx <= px1)
395 inpsfy = (yy >= py0)*(yy <= py1)
396 inpsf = np.outer(inpsfy, inpsfx)
397 indx = np.outer(inpsfy, (xx > px0)*(xx < px1))
398 indy = np.outer((yy > py0)*(yy < py1), inpsfx)
399
400 del inpsfx
401 del inpsfy
402
403 def _overlap(xlo, xhi, xmin, xmax):
404 assert (xlo <= xmax) and (xhi >= xmin) and (xlo <= xhi) and (xmin <= xmax)
405 xloclamp =
max(xlo, xmin)
406 Xlo = xloclamp - xlo
407 xhiclamp =
min(xhi, xmax)
408 Xhi = Xlo + (xhiclamp - xloclamp)
409 assert xloclamp >= 0
410 assert Xlo >= 0
411 return (xloclamp, xhiclamp+1, Xlo, Xhi+1)
412
413 A = np.zeros((NP, NT2))
414
415 A[:, I_sky] = 1.
416
417 A[:, I_sky_ramp_x] = ipixes[:, 0] + (xlo-cx)
418 A[:, I_sky_ramp_y] = ipixes[:, 1] + (ylo-cy)
419
420
421 px0, px1 = pbb.getMinX(), pbb.getMaxX()
422 py0, py1 = pbb.getMinY(), pbb.getMaxY()
423 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
424 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
425 dpx0, dpy0 = px0 - xlo, py0 - ylo
426 psf_y_slice = slice(sy3 - dpy0, sy4 - dpy0)
427 psf_x_slice = slice(sx3 - dpx0, sx4 - dpx0)
428 psfsub = psfarr[psf_y_slice, psf_x_slice]
429 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
430 A[inpsf[valid], I_psf] = psfsub[vsub]
431
432
433
434 oldsx = (sx1, sx2, sx3, sx4)
435 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0+1, px1-1)
436 psfsub = (psfarr[psf_y_slice, sx3 - dpx0 + 1: sx4 - dpx0 + 1]
437 - psfarr[psf_y_slice, sx3 - dpx0 - 1: sx4 - dpx0 - 1])/2.
438 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
439 A[indx[valid], I_dx] = psfsub[vsub]
440
441 (sx1, sx2, sx3, sx4) = oldsx
442
443
444 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0+1, py1-1)
445 psfsub = (psfarr[sy3 - dpy0 + 1: sy4 - dpy0 + 1, psf_x_slice]
446 - psfarr[sy3 - dpy0 - 1: sy4 - dpy0 - 1, psf_x_slice])/2.
447 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
448 A[indy[valid], I_dy] = psfsub[vsub]
449
450
451 for j, opsf in enumerate(otherpeaks):
452 obb = opsf.getBBox()
453 ino = np.outer((yy >= obb.getMinY())*(yy <= obb.getMaxY()),
454 (xx >= obb.getMinX())*(xx <= obb.getMaxX()))
455 dpx0, dpy0 = obb.getMinX() - xlo, obb.getMinY() - ylo
456 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, obb.getMinX(), obb.getMaxX())
457 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, obb.getMinY(), obb.getMaxY())
458 opsfarr = opsf.getArray()
459 psfsub = opsfarr[sy3 - dpy0: sy4 - dpy0, sx3 - dpx0: sx4 - dpx0]
460 vsub = valid[sy1-ylo: sy2-ylo, sx1-xlo: sx2-xlo]
461 A[ino[valid], I_opsf + j] = psfsub[vsub]
462
463 b = img_sub[valid]
464
465
466
467 rw = np.ones_like(RR)
468 ii = (RR > R0**2)
469 rr = np.sqrt(RR[ii])
470 rw[ii] = np.maximum(0, 1. - ((rr - R0)/(R1 - R0)))
471 w = np.sqrt(rw[valid]/var_sub[valid])
472
473 sumr = np.sum(rw[valid])
474 log.debug('sumr = %g', sumr)
475
476 del ii
477
478 Aw = A*w[:, np.newaxis]
479 bw = b*w
480
481 if debugPlots:
482 import pylab as plt
483 plt.clf()
484 N = NT2 + 2
485 R, C = 2, (N+1)/2
486 for i in range(NT2):
487 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
488 im1[ipixes[:, 1], ipixes[:, 0]] = A[:, i]
489 plt.subplot(R, C, i+1)
490 plt.imshow(im1, interpolation='nearest', origin='lower')
491 plt.subplot(R, C, NT2+1)
492 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
493 im1[ipixes[:, 1], ipixes[:, 0]] = b
494 plt.imshow(im1, interpolation='nearest', origin='lower')
495 plt.subplot(R, C, NT2+2)
496 im1 = np.zeros((1+yhi-ylo, 1+xhi-xlo))
497 im1[ipixes[:, 1], ipixes[:, 0]] = w
498 plt.imshow(im1, interpolation='nearest', origin='lower')
499 plt.savefig('A.png')
500
501
502
503
504
505
506
507 try:
508
509
510 X1, r1, rank1, s1 = np.linalg.lstsq(Aw[:, :NT1], bw, rcond=-1)
511
512 X2, r2, rank2, s2 = np.linalg.lstsq(Aw, bw, rcond=-1)
513 except np.linalg.LinAlgError as e:
514 log.warning("Failed to fit PSF to child: %s", e)
515 pkres.setPsfFitFailed()
516 return
517
518 log.debug('r1 r2 %s %s', r1, r2)
519
520
521
522 if len(r1) > 0:
523 chisq1 = r1[0]
524 else:
525 chisq1 = 1e30
526 if len(r2) > 0:
527 chisq2 = r2[0]
528 else:
529 chisq2 = 1e30
530 dof1 = sumr - len(X1)
531 dof2 = sumr - len(X2)
532 log.debug('dof1, dof2 %g %g', dof1, dof2)
533
534
535 if dof1 <= 0 or dof2 <= 0:
536 log.trace('Skipping this peak: bad DOF %g, %g', dof1, dof2)
537 pkres.setBadPsfDof()
538 return
539
540 q1 = chisq1/dof1
541 q2 = chisq2/dof2
542 log.trace('PSF fits: chisq/dof = %g, %g', q1, q2)
543 ispsf1 = (q1 < psfChisqCut1)
544 ispsf2 = (q2 < psfChisqCut2)
545
546 pkres.psfFit1 = (chisq1, dof1)
547 pkres.psfFit2 = (chisq2, dof2)
548
549
550 if ispsf2:
551 fdx, fdy = X2[I_dx], X2[I_dy]
552 f0 = X2[I_psf]
553
554 dx = fdx/f0
555 dy = fdy/f0
556 ispsf2 = ispsf2 and (abs(dx) < 1. and abs(dy) < 1.)
557 log.trace('isPSF2 -- checking derivatives: dx,dy = %g, %g -> %s', dx, dy, str(ispsf2))
558 if not ispsf2:
559 pkres.psfFitBigDecenter = True
560
561
562
563 if ispsf2:
564 psfimg2 = psf.computeImage(cx + dx, cy + dy)
565
566 pbb2 = psfimg2.getBBox()
567 pbb2.clip(fbb)
568
569
570
571 if not pbb2.contains(
geom.Point2I(int(cx + dx), int(cy + dy))):
572 ispsf2 = False
573 else:
574
575 px0, py0 = psfimg2.getX0(), psfimg2.getY0()
576 psfarr = psfimg2.getArray()[pbb2.getMinY()-py0:1+pbb2.getMaxY()-py0,
577 pbb2.getMinX()-px0:1+pbb2.getMaxX()-px0]
578 px0, py0 = pbb2.getMinX(), pbb2.getMinY()
579 px1, py1 = pbb2.getMaxX(), pbb2.getMaxY()
580
581
582 Ab = A[:, :NT1]
583
584 sx1, sx2, sx3, sx4 = _overlap(xlo, xhi, px0, px1)
585 sy1, sy2, sy3, sy4 = _overlap(ylo, yhi, py0, py1)
586 dpx0, dpy0 = px0 - xlo, py0 - ylo
587 psfsub = psfarr[sy3-dpy0:sy4-dpy0, sx3-dpx0:sx4-dpx0]
588 vsub = valid[sy1-ylo:sy2-ylo, sx1-xlo:sx2-xlo]
589 xx, yy = np.arange(xlo, xhi+1), np.arange(ylo, yhi+1)
590 inpsf = np.outer((yy >= py0)*(yy <= py1), (xx >= px0)*(xx <= px1))
591 Ab[inpsf[valid], I_psf] = psfsub[vsub]
592
593 Aw = Ab*w[:, np.newaxis]
594
595 Xb, rb, rankb, sb = np.linalg.lstsq(Aw, bw, rcond=-1)
596 if len(rb) > 0:
597 chisqb = rb[0]
598 else:
599 chisqb = 1e30
600 dofb = sumr - len(Xb)
601 qb = chisqb/dofb
602 ispsf2 = (qb < psfChisqCut2b)
603 q2 = qb
604 X2 = Xb
605 log.trace('shifted PSF: new chisq/dof = %g; good? %s', qb, ispsf2)
606 pkres.psfFit3 = (chisqb, dofb)
607
608
609 if (((ispsf1 and ispsf2) and (q2 < q1))
610 or (ispsf2 and not ispsf1)):
611 Xpsf = X2
612 chisq = chisq2
613 dof = dof2
614 log.debug('dof %g', dof)
615 log.trace('Keeping shifted-PSF model')
616 cx += dx
617 cy += dy
618 pkres.psfFitWithDecenter = True
619 else:
620
621 Xpsf = X1
622 chisq = chisq1
623 dof = dof1
624 log.debug('dof %g', dof)
625 log.trace('Keeping unshifted PSF model')
626
627 ispsf = (ispsf1 or ispsf2)
628
629
630 if debugPsf:
631 SW, SH = 1+xhi-xlo, 1+yhi-ylo
632 psfmod = afwImage.ImageF(SW, SH)
633 psfmod.setXY0(xlo, ylo)
634 psfderivmodm = afwImage.MaskedImageF(SW, SH)
635 psfderivmod = psfderivmodm.getImage()
636 psfderivmod.setXY0(xlo, ylo)
637 model = afwImage.ImageF(SW, SH)
638 model.setXY0(xlo, ylo)
639 for i in range(len(Xpsf)):
640 for (x, y), v in zip(ipixes, A[:, i]*Xpsf[i]):
641 ix, iy = int(x), int(y)
642 model.set(ix, iy, model.get(ix, iy) + float(v))
643 if i in [I_psf, I_dx, I_dy]:
644 psfderivmod.set(ix, iy, psfderivmod.get(ix, iy) + float(v))
645 for ii in range(NP):
646 x, y = ipixes[ii, :]
647 psfmod.set(int(x), int(y), float(A[ii, I_psf]*Xpsf[I_psf]))
649 for (x, y) in ipixes:
650 modelfp.addSpan(int(y+ylo), int(x+xlo), int(x+xlo))
651 modelfp.normalize()
652
653 pkres.psfFitDebugPsf0Img = psfimg
654 pkres.psfFitDebugPsfImg = psfmod
655 pkres.psfFitDebugPsfDerivImg = psfderivmod
656 pkres.psfFitDebugPsfModel = model
657 pkres.psfFitDebugStamp = img.Factory(img, stampbb, True)
658 pkres.psfFitDebugValidPix = valid
659 pkres.psfFitDebugVar = varimg.Factory(varimg, stampbb, True)
660 ww = np.zeros(valid.shape, np.float64)
661 ww[valid] = w
662 pkres.psfFitDebugWeight = ww
663 pkres.psfFitDebugRampWeight = rw
664
665
666 pkres.psfFitR0 = R0
667 pkres.psfFitR1 = R1
668 pkres.psfFitStampExtent = (xlo, xhi, ylo, yhi)
669 pkres.psfFitCenter = (cx, cy)
670 log.debug('saving chisq,dof %g %g', chisq, dof)
671 pkres.psfFitBest = (chisq, dof)
672 pkres.psfFitParams = Xpsf
673 pkres.psfFitFlux = Xpsf[I_psf]
674 pkres.psfFitNOthers = len(otherpeaks)
675
676 if ispsf:
677 pkres.setDeblendedAsPsf()
678
679
680
681 log.trace('Deblending as PSF; setting template to PSF model')
682
683
684 psfimg = psf.computeImage(cx, cy)
685
686 psfimg *= Xpsf[I_psf]
687 psfimg = psfimg.convertF()
688
689
691 psfbb = psfimg.getBBox()
692 fpcopy.clipTo(psfbb)
693 bb = fpcopy.getBBox()
694
695
696 psfmod = afwImage.ImageF(bb)
697 fpcopy.spans.copyImage(psfimg, psfmod)
698
699 clipFootprintToNonzeroImpl(fpcopy, psfmod)
700 pkres.setTemplate(psfmod, fpcopy)
701
702
703 pkres.setPsfTemplate(psfmod, fpcopy)
704
705 return ispsf
706
707
An integer coordinate rectangle.