LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
LSSTDataManagementBasePackage
psfex.py
Go to the documentation of this file.
1 import os
2 import re
3 import sys
4 
5 import numpy as np
6 from astropy.io import fits
7 try:
8  import matplotlib.pyplot as plt
9 except ImportError:
10  plt = None
11 
12 import lsst.afw.geom as afwGeom
13 from lsst.afw.fits import readMetadata
14 import lsst.afw.image as afwImage
15 import lsst.afw.table as afwTable
16 import lsst.afw.display.ds9 as ds9
17 from lsst.daf.base import PropertySet
18 from . import psfexLib
19 
20 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
21 
22 
23 def splitFitsCard(line):
24  """Split a FITS header, returning (key, value).
25 
26  Parameters
27  ----------
28  line: `string`
29  A string from a FITS header.
30 
31  Returns
32  -------
33  key: `string`
34  The header key.
35  value: `string`
36  The header value.
37  """
38 
39  try:
40  k, v = re.search(r"(\S+)\s*=\s*'?((?:\S+|'))", line).groups()
41  except AttributeError:
42  raise
43 
44  try:
45  v = int(v)
46  except ValueError:
47  try:
48  v = float(v)
49  except ValueError:
50  pass
51 
52  return k, v
53 
54 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
55 
56 
57 def compute_fwhmrange(fwhm, maxvar, minin, maxin, plot=dict(fwhmHistogram=False)):
58  """Compute the FWHM range associated to a series of FWHM measurements.
59  AUTHOR E. Bertin (IAP, Leiden observatory & ESO)
60  VERSION 20/03/2008
61 
62  Parameters
63  ----------
64  fwhm: iterable of `float`
65  Iterable of full width half-maxima.
66  maxvar: `float`
67  Maximum allowed FWHM variation.
68  minin: `float`
69  Minimum allowed FWHM.
70  maxin: `float`
71  Maximum allowed FWHM.
72  plot: `dict`, optional
73  Dict of plotting options.
74 
75  Returns
76  -------
77  fmin: `float`
78  FWHM mode.
79  minout: `float`
80  Lower FWHM range.
81  maxout: `float`
82  Upper FWHM range.
83  """
84 
85  nfwhm = len(fwhm)
86  fwhm.sort()
87 
88  # Find the mode
89  nw = nfwhm//4
90  if nw < 4:
91  nw = 1
92  dfmin = psfexLib.BIG
93  fmin = 0.0
94  for i in range(nfwhm - nw):
95  df = fwhm[i + nw] - fwhm[i]
96  if df < dfmin:
97  dfmin = df
98  fmin = (fwhm[i + nw] + fwhm[i])/2.0
99 
100  if nfwhm < 2:
101  fmin = fwhm[0]
102 
103  dfmin = (maxvar + 1.0)**0.3333333
104  minout = fmin/dfmin if dfmin > 0.0 else 0.0
105  if minout < minin:
106  minout = minin
107 
108  maxout = fmin*dfmin**2
109  if maxout > maxin:
110  maxout = maxin
111 
112  if plt and plot.get("fwhmHistogram"):
113  plt.clf()
114  plt.hist(fwhm, nfwhm//10 + 1, normed=1, facecolor='g', alpha=0.75)
115  plt.xlabel("FWHM")
116  plt.axvline(fmin, color='red')
117  [plt.axvline(_, color='blue') for _ in (minout, maxout)]
118 
119  input("Continue? ")
120 
121  return fmin, minout, maxout
122 
123 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
124 
125 
126 def read_samples(prefs, set, filename, frmin, frmax, ext, next, catindex, context, pcval,
127  plot=dict(showFlags=False, showRejection=False)):
128  # allocate a new set iff set is None
129  if not set:
130  set = psfexLib.Set(context)
131 
132  cmin, cmax = None, None
133  if set.getNcontext():
134  cmin = np.empty(set.getNcontext())
135  cmax = np.empty(set.getNcontext())
136  for i in range(set.getNcontext()):
137  if set.getNsample():
138  cmin[i] = set.getContextOffset(i) - set.getContextScale(i)/2.0
139  cmax[i] = cmin[i] + set.getContextScale(i)
140  else:
141  cmin[i] = psfexLib.BIG
142  cmax[i] = -psfexLib.BIG
143  #
144  # Read data
145  #
146  with fits.open(filename) as cat:
147  extCtr = -1
148  for tab in cat:
149  if tab.name == "LDAC_IMHEAD":
150  extCtr += 1
151 
152  if extCtr < ext:
153  continue
154  elif extCtr > ext:
155  break
156 
157  if tab.name == "PRIMARY":
158  pass
159  elif tab.name == "LDAC_IMHEAD":
160  hdr = tab.data[0][0] # the fits header from the original fits image
161  foundCards = 0 # how many of our desired cards have we found?
162 
163  for line in hdr:
164  try:
165  k, v = splitFitsCard(line)
166  except AttributeError:
167  continue
168 
169  if k == "SEXBKDEV":
170  backnoise2 = v**2
171  foundCards += 1
172  elif k == "SEXGAIN":
173  gain = v
174  foundCards += 1
175 
176  if foundCards == 2:
177  break
178  elif tab.name == "LDAC_OBJECTS":
179  xm = tab.data[prefs.getCenterKey(0)]
180  ym = tab.data[prefs.getCenterKey(1)]
181  fluxrad = tab.data["FLUX_RADIUS"]
182  flux = tab.data[prefs.getPhotfluxRkey()]
183  fluxerr = tab.data[prefs.getPhotfluxerrRkey()]
184  elong = tab.data["ELONGATION"]
185  flags = tab.data["FLAGS"]
186 
187  n = prefs.getPhotfluxNum() - 1
188  if n:
189  assert False, "Code to handle e.g. FLUX_APER(3) isn't yet converted"
190  if key.naxis == 1 and n < key.naxisn[0]:
191  flux += n
192  else:
193  print("Not enough apertures for %s in catalogue %s: using first aperture" % \
194  (prefs.getPhotfluxRkey(), filename), file=sys.stderr)
195 
196  n = prefs.getPhotfluxerrNum() - 1
197  if n:
198  if key.naxis == 1 and n < key.naxisn[0]:
199  fluxerr += n
200  else:
201  print("Not enough apertures for %s in catalogue %s: using first aperture" % \
202  (prefs.getPhotfluxerrRkey(), filename), file=sys.stderr)
203  #
204  # Now the VIGNET data
205  #
206  vignet = tab.data["VIGNET"]
207 
208  try:
209  vigw, vigh = vignet[0].shape
210  except ValueError:
211  raise RuntimeError("*Error*: VIGNET should be a 2D vector; saw %s" % str(vignet[0].shape))
212 
213  if set.empty():
214  set.setVigSize(vigw, vigh)
215 
216  # Try to load the set of context keys
217  pc = 0
218  contextvalp = []
219  for i, key in enumerate(context.getName()):
220  if context.getPcflag(i):
221  contextvalp.append(pcval[pc])
222  pc += 1
223  elif key[0] == ':':
224  try:
225  contextvalp.append(tab.header[key[1:]])
226  except KeyError:
227  raise RuntimeError("*Error*: %s parameter not found in the header of %s" %
228  (key[1:], filename))
229  else:
230  try:
231  contextvalp.append(tab.data[key])
232  except KeyError:
233  raise RuntimeError("*Error*: %s parameter not found in the header of %s" %
234  (key, filename))
235  set.setContextname(i, key)
236 
237  # Now examine each vector of the shipment
238  good = select_candidates(set, prefs, frmin, frmax,
239  flags, flux, fluxerr, fluxrad, elong, vignet,
240  plot=plot, title="%s[%d]" % (filename, ext + 1))
241  #
242  # Insert the good candidates into the set
243  #
244  if not vignet.dtype.isnative:
245  # without the swap setVig fails with "ValueError: 'unaligned arrays cannot be converted to C++'"
246  vignet = vignet.byteswap()
247 
248  for i in np.where(good)[0]:
249  sample = set.newSample()
250  sample.setCatindex(catindex)
251  sample.setExtindex(ext)
252 
253  sample.setVig(vignet[i])
254 
255  sample.setNorm(float(flux[i]))
256  sample.setBacknoise2(backnoise2)
257  sample.setGain(gain)
258  sample.setX(float(xm[i]))
259  sample.setY(float(ym[i]))
260  sample.setFluxrad(float(fluxrad[i]))
261 
262  for j in range(set.getNcontext()):
263  sample.setContext(j, float(contextvalp[j][i]))
264 
265  set.finiSample(sample, prefs.getProfAccuracy())
266 
267  #---- Update min and max
268  for j in range(set.getNcontext()):
269  cmin[j] = contextvalp[j][good].min()
270  cmax[j] = contextvalp[j][good].max()
271 
272  # Update the scaling
273  if set.getNsample():
274  for i in range(set.getNcontext()):
275  set.setContextScale(i, cmax[i] - cmin[i])
276  set.setContextOffset(i, (cmin[i] + cmax[i])/2.0)
277 
278  # Don't waste memory!
279  set.trimMemory()
280 
281  return set
282 
283 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
284 
285 
286 def getSexFlags(*args):
287  return {1: "flux blended",
288  2: "blended",
289  4: "saturated",
290  8: "edge",
291  16: "bad aperture",
292  32: "bad isophotal",
293  64: "memory error (deblend)",
294  128: "memory error (extract)",
295  }
296 
297 
298 def select_candidates(set, prefs, frmin, frmax,
299  flags, flux, fluxerr, rmsSize, elong, vignet,
300  plot=dict(), title=""):
301  maxbad = prefs.getBadpixNmax()
302  maxbadflag = prefs.getBadpixFlag()
303  maxelong = (prefs.getMaxellip() + 1.0)/(1.0 - prefs.getMaxellip()) if prefs.getMaxellip() < 1.0 else 100.0
304  minsn = prefs.getMinsn()
305 
306  sn = flux/np.where(fluxerr > 0, fluxerr, 1)
307  sn[fluxerr <= 0] = -psfexLib.BIG
308  #---- Apply some selection over flags, fluxes...
309  plotFlags = plot.get("showFlags") if plt else False
310  plotRejection = plot.get("showRejection") if plt else False
311 
312  bad = flags & prefs.getFlagMask() != 0
313  set.setBadFlags(int(sum(bad != 0)))
314 
315  if plotRejection:
316  selectionVectors = []
317  selectionVectors.append((bad, "flags %d" % sum(bad != 0)))
318 
319  dbad = sn < minsn
320  set.setBadSN(int(sum(dbad)))
321  bad = np.logical_or(bad, dbad)
322  if plotRejection:
323  selectionVectors.append((dbad, "S/N %d" % sum(dbad)))
324 
325  dbad = rmsSize < frmin
326  set.setBadFrmin(int(sum(dbad)))
327  bad = np.logical_or(bad, dbad)
328  if plotRejection:
329  selectionVectors.append((dbad, "frmin %d" % sum(dbad)))
330 
331  dbad = rmsSize > frmax
332  set.setBadFrmax(int(sum(dbad)))
333  bad = np.logical_or(bad, dbad)
334  if plotRejection:
335  selectionVectors.append((dbad, "frmax %d" % sum(dbad)))
336 
337  dbad = elong > maxelong
338  set.setBadElong(int(sum(dbad)))
339  bad = np.logical_or(bad, dbad)
340  if plotRejection:
341  selectionVectors.append((dbad, "elong %d" % sum(dbad)))
342 
343  #-- ... and check the integrity of the sample
344  if maxbadflag:
345  nbad = np.array([(v <= -psfexLib.BIG).sum() for v in vignet])
346  dbad = nbad > maxbad
347  set.setBadPix(int(sum(dbad)))
348  bad = np.logical_or(bad, dbad)
349  if plotRejection:
350  selectionVectors.append((dbad, "badpix %d" % sum(dbad)))
351 
352  good = np.logical_not(bad)
353  if plotFlags or plotRejection:
354  imag = -2.5*np.log10(flux)
355  plt.clf()
356 
357  alpha = 0.5
358  if plotFlags:
359  labels = getFlags()
360 
361  isSet = np.where(flags == 0x0)[0]
362  plt.plot(imag[isSet], rmsSize[isSet], 'o', alpha=alpha, label="good")
363 
364  for i in range(16):
365  mask = 1 << i
366  if mask & prefs.getFlagMask():
367  isSet = np.where(np.bitwise_and(flags, mask))[0]
368  if isSet.any():
369  plt.plot(imag[isSet], rmsSize[isSet], 'o', alpha=alpha, label=labels[mask])
370  else:
371  for bad, label in selectionVectors:
372  plt.plot(imag[bad], rmsSize[bad], 'o', alpha=alpha, label=label)
373 
374  plt.plot(imag[good], rmsSize[good], 'o', color="black", label="selected")
375  [plt.axhline(_, color='red') for _ in [frmin, frmax]]
376  plt.xlim(np.median(imag[good]) + 5*np.array([-1, 1]))
377  plt.ylim(-0.1, 2*frmax)
378  plt.legend(loc=2)
379  plt.xlabel("Instrumental Magnitude")
380  plt.ylabel("rmsSize")
381  plt.title("%s %d selected" % (title, sum(good)))
382 
383  input("Continue? ")
384 
385  return good
386 
387 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
388 
389 try:
390  _dataType
391 except NameError:
392  class _SExtractor():
393  pass
394 
395  class _LSST():
396  pass
397 
398  _dataTypes = dict(LSST=_LSST,
399  SExtractor=_SExtractor
400  )
401  _dataType = _SExtractor
402 
403 
404 def setDataType(t):
405  _dataType = _dataTypes[t]
406 
407 
408 def getFlags():
409  if _dataType == _LSST:
410  return getLsstFlags(None)
411  else:
412  return getSexFlags(None)
413 
414 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
415 
416 
417 def load_samples(prefs, context, ext=psfexLib.Prefs.ALL_EXTENSIONS, next=1, plot=dict()):
418  minsn = prefs.getMinsn()
419  maxelong = (prefs.getMaxellip() + 1.0)/(1.0 - prefs.getMaxellip()) if prefs.getMaxellip() < 1.0 else 100
420  min = prefs.getFwhmrange()[0]
421  max = prefs.getFwhmrange()[1]
422 
423  filenames = prefs.getCatalogs()
424 
425  ncat = len(filenames)
426  fwhmmin = np.empty(ncat)
427  fwhmmax = np.empty(ncat)
428 
429  if not prefs.getAutoselectFlag():
430  fwhmmin = np.zeros(ncat) + prefs.getFwhmrange()[0]
431  fwhmmax = np.zeros(ncat) + prefs.getFwhmrange()[1]
432  fwhmmode = (fwhmmin + fwhmmax)/2.0
433  else:
434  fwhms = {}
435 
436  #-- Try to estimate the most appropriate Half-light Radius range
437  #-- Get the Half-light radii
438  nobj = 0
439  for i, fileName in enumerate(filenames):
440  fwhms[i] = []
441 
442  if prefs.getVerboseType() != prefs.QUIET:
443  print("Examining Catalog #%d" % (i+1))
444 
445  #---- Read input catalog
446  backnoises = []
447  with fits.open(fileName) as cat:
448  extCtr = -1
449  for tab in cat:
450  if tab.name == "LDAC_IMHEAD":
451  extCtr += 1
452 
453  if extCtr != ext and ext != prefs.ALL_EXTENSIONS:
454  if extCtr > ext:
455  break
456  continue
457 
458  if tab.name == "PRIMARY":
459  pass
460  elif tab.name == "LDAC_IMHEAD":
461  hdr = tab.data[0][0] # the fits header from the original fits image
462  for line in hdr:
463  try:
464  k, v = splitFitsCard(line)
465  except AttributeError:
466  continue
467 
468  if k == "SEXBKDEV":
469  if v < 1/psfexLib.BIG:
470  v = 1.0
471 
472  backnoises.append(v)
473  break
474  elif tab.name == "LDAC_OBJECTS":
475  #-------- Fill the FWHM array
476  rmsSize = tab.data["FLUX_RADIUS"]
477  fmax = tab.data["FLUX_MAX"]
478  flags = tab.data["FLAGS"]
479  elong = tab.data["ELONGATION"]
480  backnoise = backnoises[-1]
481 
482  good = np.logical_and(fmax/backnoise > minsn,
483  np.logical_not(flags & prefs.getFlagMask()))
484  good = np.logical_and(good, elong < maxelong)
485  fwhm = 2.0*rmsSize
486  good = np.logical_and(good, fwhm >= min)
487  good = np.logical_and(good, fwhm < max)
488  fwhms[i] = fwhm[good]
489 
490  if prefs.getVarType() == prefs.VAR_NONE:
491  if nobj:
492  fwhms_all = np.empty(sum([len(l) for l in fwhms.values()]))
493  i = 0
494  for l in fwhms.values():
495  fwhms_all[i:len(l)] = l
496  i += len(l)
497  mode, min, max = compute_fwhmrange(fwhms_all, prefs.getMaxvar(),
498  prefs.getFwhmrange()[0], prefs.getFwhmrange()[1],
499  plot=plot)
500  else:
501  raise RuntimeError("No source with appropriate FWHM found!!")
502  mode = min = max = 2.35/(1.0 - 1.0/psfexLib.cvar.INTERPFAC)
503 
504  fwhmmin = np.zeros(ncat) + min
505  fwhmmax = np.zeros(ncat) + max
506  fwhmmode = np.zeros(ncat) + mode
507  else:
508  fwhmmode = np.empty(ncat)
509  fwhmmin = np.empty(ncat)
510  fwhmmax = np.empty(ncat)
511 
512  for i in range(ncat):
513  nobj = len(fwhms[i])
514  if (nobj):
515  fwhmmode[i], fwhmmin[i], fwhmmax[i] = \
516  compute_fwhmrange(fwhms[i], prefs.getMaxvar(),
517  prefs.getFwhmrange()[0], prefs.getFwhmrange()[1], plot=plot)
518  else:
519  raise RuntimeError("No source with appropriate FWHM found!!")
520  fwhmmode[i] = fwhmmin[i] = fwhmmax[i] = 2.35/(1.0 - 1.0/psfexLib.cvar.INTERPFAC)
521 
522  # Read the samples
523  mode = psfexLib.BIG # mode of FWHM distribution
524 
525  sets = []
526  for i, fileName in enumerate(filenames):
527  set = None
528  if ext == prefs.ALL_EXTENSIONS:
529  extensions = list(range(len(backnoises)))
530  else:
531  extensions = [ext]
532 
533  for e in extensions:
534  set = read_samples(prefs, set, fileName, fwhmmin[i]/2.0, fwhmmax[i]/2.0,
535  e, next, i, context, context.getPc(i) if context.getNpc() else None, plot=plot)
536 
537  if fwhmmode[i] < mode:
538  mode = fwhmmode[i]
539 
540  set.setFwhm(mode)
541 
542  if prefs.getVerboseType() != prefs.QUIET:
543  if set.getNsample():
544  print("%d samples loaded." % set.getNsample())
545  else:
546  raise RuntimeError("No appropriate source found!!")
547 
548  sets.append(set)
549 
550  return sets
551 
552 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
553 
554 
555 def showPsf(psf, set, ext=None, wcsData=None, trim=0, nspot=5,
556  diagnostics=False, outDir="", frame=None, title=None):
557  """Show a PSF on ds9
558  """
559 
560  if ext is not None:
561  psf = psf[ext]
562 
563  if wcsData:
564  if ext is not None:
565  wcsData = wcsData[ext]
566  wcs, naxis1, naxis2 = wcsData
567  else:
568  wcs, naxis1, naxis2 = None, None, None
569 
570  naxis = [naxis1, naxis2]
571  for i in range(2):
572  if naxis[i] is None:
573  # cmin, cmax are the range of input star positions
574  cmin, cmax = [set.getContextOffset(i) + d*set.getContextScale(i) for d in (-0.5, 0.5)]
575  naxis[i] = cmax + cmin # a decent guess
576 
577  import lsst.afw.display.utils as ds9Utils
578  if naxis[0] > naxis[1]:
579  nx, ny = int(nspot*naxis[0]/float(naxis[1]) + 0.5), nspot
580  else:
581  nx, ny = nspot, int(nspot*naxis[1]/float(naxis[0]) + 0.5)
582 
583  mos = ds9Utils.Mosaic(gutter=2, background=0.02)
584 
585  xpos, ypos = np.linspace(0, naxis[0], nx), np.linspace(0, naxis[1], ny)
586  for y in ypos:
587  for x in xpos:
588  psf.build(x, y)
589 
590  im = afwImage.ImageF(*psf.getLoc().shape)
591  im.getArray()[:] = psf.getLoc()
592  im /= float(im.getArray().max())
593  if trim:
594  if trim > im.getHeight()//2:
595  trim = im.getHeight()//2
596 
597  im = im[trim:-trim, trim:-trim]
598 
599  mos.append(im)
600 
601  mosaic = mos.makeMosaic(mode=nx)
602  if frame is not None:
603  ds9.mtv(mosaic, frame=frame, title=title)
604  #
605  # Figure out the WCS for the mosaic
606  #
607  pos = []
608  if False:
609  for x, y, i in zip((xpos[0], xpos[-1]), (ypos[0], ypos[-1]), (0, mos.nImage - 1)):
610  bbox = mos.getBBox(i)
611  mosx = bbox.getMinX() + 0.5*(bbox.getWidth() - 1)
612  mosy = bbox.getMinY() + 0.5*(bbox.getHeight() - 1)
613  pos.append([afwGeom.PointD(mosx, mosy), wcs.pixelToSky(afwGeom.PointD(x, y))])
614  else:
615  pos.append([afwGeom.PointD(0, 0), wcs.pixelToSky(afwGeom.PointD(0, 0))])
616  pos.append([afwGeom.PointD(*mosaic.getDimensions()), wcs.pixelToSky(afwGeom.PointD(naxis1, naxis2))])
617 
618  CD = []
619  for i in range(2):
620  delta = pos[1][1][i].asDegrees() - pos[0][1][i].asDegrees()
621  CD.append(delta/(pos[1][0][i] - pos[0][0][i]))
622  CD = np.array(CD)
623  CD.shape = (2, 2)
624  mosWcs = afwGeom.makeSkyWcs(crval=pos[0][0], crpix=pos[0][1], cdMatrix=CD)
625 
626  if ext is not None:
627  title = "%s-%d" % (title, ext)
628 
629  if frame is not None:
630  ds9.mtv(mosaic, frame=frame, title=title, wcs=mosWcs)
631 
632  if diagnostics:
633  outFile = "%s-mod.fits" % title
634  if outDir:
635  outFile = os.path.join(outDir, outFile)
636  mosaic.writeFits(outFile, mosWcs.getFitsMetadata())
637 
638  #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
639 
640  mos = ds9Utils.Mosaic(gutter=4, background=0.002)
641  for i in range(set.getNsample()):
642  s = set.getSample(i)
643  if ext is not None and s.getExtindex() != ext:
644  continue
645 
646  smos = ds9Utils.Mosaic(gutter=2, background=-0.003)
647  for func in [s.getVig, s.getVigResi]:
648  arr = func()
649  if func == s.getVig:
650  norm = float(arr.max()) if True else s.getNorm()
651 
652  arr /= norm
653  im = afwImage.ImageF(*arr.shape)
654  im.getArray()[:] = arr
655  smos.append(im)
656 
657  mos.append(smos.makeMosaic(mode="x"))
658 
659  mosaic = mos.makeMosaic(title=title)
660 
661  if frame is not None:
662  ds9.mtv(mosaic, title=title, frame=frame+1)
663 
664  if diagnostics:
665  outFile = "%s-psfstars.fits" % title
666  if outDir:
667  outFile = os.path.join(outDir, outFile)
668 
669  mosaic.writeFits(outFile)
670 
671 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
672 
673 
674 def getLsstFlags(tab=None):
675  flagKeys = [
676  "base_PixelFlags_flag_edge",
677  #"base_PixelFlags_flag_interpolated",
678  #"base_PixelFlags_flag_interpolatedCenter",
679  #"base_PixelFlags_flag_saturated",
680  "base_PixelFlags_flag_saturatedCenter",
681  #"base_PixelFlags_flag_cr",
682  "base_PixelFlags_flag_crCenter",
683  "base_PixelFlags_flag_bad",
684  "base_PsfFlux_flag",
685  "parent",
686  ]
687 
688  if tab is None:
689  flags = {}
690  for i, k in enumerate(flagKeys):
691  flags[1 << i] = re.sub(r"\_flag", "",
692  re.sub(r"^base\_", "", re.sub(r"^base\_PixelFlags\_flag\_", "", k)))
693  else:
694  flags = 0
695  for i, k in enumerate(flagKeys):
696  if k == "parent":
697  try:
698  isSet = tab.get("deblend_nChild") > 0
699  except KeyError:
700  isSet = 0
701  else:
702  isSet = tab.get(k)
703  flags = np.bitwise_or(flags, np.where(isSet, 1 << i, 0))
704 
705  return flags
706 
707 
708 def guessCalexp(fileName):
709  for guess in [
710  re.sub("/src", r"", fileName),
711  re.sub("(SRC([^.]+))", r"CORR\2-exp", fileName),
712  ]:
713  if guess != fileName and os.path.exists(guess):
714  return guess
715 
716  raise RuntimeError("Unable to find a calexp to go with %s" % fileName)
717 
718 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
719 
720 
721 def makeitLsst(prefs, context, saveWcs=False, plot=dict()):
722  """This is the python wrapper that reads lsst tables
723  """
724  # Create an array of PSFs (one PSF for each extension)
725  if prefs.getVerboseType() != prefs.QUIET:
726  print("----- %d input catalogues:" % prefs.getNcat())
727 
728  if saveWcs: # only needed for making plots
729  wcssList = []
730 
731  fields = psfexLib.vectorField()
732  for cat in prefs.getCatalogs():
733  field = psfexLib.Field(cat)
734  wcss = []
735  wcssList.append(wcss)
736  with fits.open(cat):
737  # Hack: I want the WCS so I'll guess where the calexp is to be found
738  calexpFile = guessCalexp(cat)
739  md = readMetadata(calexpFile)
740  wcs = afwGeom.makeSkyWcs(md)
741 
742  if not wcs:
743  cdMatrix = np.array([1.0, 0.0, 0.0, 1.0])
744  cdMatrix.shape = (2, 2)
745  wcs = afwGeom.makeSkyWcs(crpix=afwGeom.PointD(0, 0),
746  crval=afwGeom.SpherePoint(0.0, 0.0, afwGeom.degrees),
747  cdMatrix=cdMatrix)
748 
749  naxis1, naxis2 = md.getScalar("NAXIS1"), md.getScalar("NAXIS2")
750  # Find how many rows there are in the catalogue
751  md = readMetadata(cat)
752 
753  field.addExt(wcs, naxis1, naxis2, md.getScalar("NAXIS2"))
754  if saveWcs:
755  wcss.append((wcs, naxis1, naxis2))
756 
757  field.finalize()
758  fields.append(field)
759 
760  fields[0].getNext() # number of extensions
761 
762  prefs.getPsfStep()
763 
764  sets = psfexLib.vectorSet()
765  for set in load_samplesLsst(prefs, context, plot=plot):
766  sets.append(set)
767 
768  psfexLib.makeit(fields, sets)
769 
770  ret = [[f.getPsfs() for f in fields], sets]
771  if saveWcs:
772  ret.append(wcssList)
773 
774  return ret
775 
776 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
777 
778 
779 def read_samplesLsst(prefs, set, filename, frmin, frmax, ext, next, catindex, context, pcval,
780  plot=dict(showFlags=False, showRejection=False)):
781  # allocate a new set iff set is None
782  if not set:
783  set = psfexLib.Set(context)
784 
785  cmin, cmax = None, None
786  if set.getNcontext():
787  cmin = np.empty(set.getNcontext())
788  cmax = np.empty(set.getNcontext())
789  for i in range(set.getNcontext()):
790  if set.getNsample():
791  cmin[i] = set.getContextOffset(i) - set.getContextScale(i)/2.0
792  cmax[i] = cmin[i] + set.getContextScale(i)
793  else:
794  cmin[i] = psfexLib.BIG
795  cmax[i] = -psfexLib.BIG
796  #
797  # Read data
798  #
799  tab = afwTable.SourceCatalog.readFits(filename)
800 
801  centroid = tab.getCentroidDefinition()
802  xm = tab.get("%s.x" % centroid)
803  ym = tab.get("%s.y" % centroid)
804 
805  shape = tab.getShapeDefinition()
806  ixx = tab.get("%s.xx" % shape)
807  iyy = tab.get("%s.yy" % shape)
808 
809  rmsSize = np.sqrt(0.5*(ixx + iyy))
810  elong = 0.5*(ixx - iyy)/(ixx + iyy)
811 
812  flux = tab.get(prefs.getPhotfluxRkey())
813  fluxErr = tab.get(prefs.getPhotfluxerrRkey())
814  flags = getLsstFlags(tab)
815 
816  #
817  # Now the VIGNET data
818  #
819  vigw, vigh = 35, 35 # [prefs.getPsfsize()[i] for i in range(2)]
820  if set.empty():
821  set.setVigSize(vigw, vigh)
822 
823  vignet = np.empty(nobj*vigw*vigh, "float32").reshape(nobj, vigw, vigh)
824 
825  # Hack: I want the WCS so I'll guess where the calexp is to be found
826  calexpFile = guessCalexp(filename)
827  mi = afwImage.MaskedImageF(calexpFile)
828  backnoise2 = np.median(mi.getVariance().getArray())
829  gain = 1.0
830 
831  edgeBit = [k for k, v in getFlags().items() if v == "edge"][0]
832 
833  for i, xc, yc in zip(range(nobj), xm, ym):
834  try:
835  x, y = int(xc), int(yc)
836  except ValueError:
837  flags[i] |= edgeBit # mark star as bad
838 
839  try:
840  pstamp = mi[x - vigw//2:x + vigw//2 + 1, y - vigh//2:y + vigh//2 + 1]
841  vignet[i] = pstamp.getImage().getArray().transpose()
842  except Exception:
843  flags[i] |= edgeBit # mark star as bad
844 
845  # Try to load the set of context keys
846  pc = 0
847  contextvalp = []
848  for i, key in enumerate(context.getName()):
849  if context.getPcflag(i):
850  contextvalp.append(pcval[pc])
851  pc += 1
852  elif key[0] == ':':
853  try:
854  contextvalp.append(tab.header[key[1:]])
855  except KeyError:
856  raise RuntimeError("*Error*: %s parameter not found in the header of %s" %
857  (key[1:], filename))
858  else:
859  try:
860  contextvalp.append(tab.get(key))
861  except KeyError:
862  raise RuntimeError("*Error*: %s parameter not found in the header of %s" %
863  (key, filename))
864  set.setContextname(i, key)
865 
866  # Now examine each vector of the shipment
867  good = select_candidates(set, prefs, frmin, frmax,
868  flags, flux, fluxErr, rmsSize, elong, vignet,
869  plot=plot, title="%s[%d]" % (filename, ext + 1) if next > 1 else filename)
870  #
871  # Insert the good candidates into the set
872  #
873  if not vignet.dtype.isnative:
874  # without the swap setVig fails with "ValueError: 'unaligned arrays cannot be converted to C++'"
875  vignet = vignet.byteswap()
876 
877  for i in np.where(good)[0]:
878  sample = set.newSample()
879  sample.setCatindex(catindex)
880  sample.setExtindex(ext)
881 
882  sample.setVig(vignet[i])
883  if False:
884  pstamp = afwImage.ImageF(*vignet[i].shape)
885  pstamp.getArray()[:] = sample.getVig()
886 
887  ds9.mtv(pstamp)
888 
889  sample.setNorm(float(flux[i]))
890  sample.setBacknoise2(backnoise2)
891  sample.setGain(gain)
892  sample.setX(float(xm[i]))
893  sample.setY(float(ym[i]))
894  sample.setFluxrad(float(rmsSize[i]))
895 
896  for j in range(set.getNcontext()):
897  sample.setContext(j, float(contextvalp[j][i]))
898 
899  set.finiSample(sample, prefs.getProfAccuracy())
900 
901  #---- Update min and max
902  for j in range(set.getNcontext()):
903  cmin[j] = contextvalp[j][good].min()
904  cmax[j] = contextvalp[j][good].max()
905 
906  # Update the scaling
907  if set.getNsample():
908  for i in range(set.getNcontext()):
909  set.setContextScale(i, cmax[i] - cmin[i])
910  set.setContextOffset(i, (cmin[i] + cmax[i])/2.0)
911 
912  # Don't waste memory!
913  set.trimMemory()
914 
915  return set
916 
917 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
918 
919 
920 def load_samplesLsst(prefs, context, ext=psfexLib.Prefs.ALL_EXTENSIONS, next=1, plot=dict()):
921  minsn = prefs.getMinsn()
922  maxelong = (prefs.getMaxellip() + 1.0)/(1.0 - prefs.getMaxellip()) if prefs.getMaxellip() < 1.0 else 100
923  min = prefs.getFwhmrange()[0]
924  max = prefs.getFwhmrange()[1]
925 
926  filenames = prefs.getCatalogs()
927 
928  ncat = len(filenames)
929  fwhmmin = np.empty(ncat)
930  fwhmmax = np.empty(ncat)
931 
932  if not prefs.getAutoselectFlag():
933  fwhmmin = np.zeros(ncat) + prefs.getFwhmrange()[0]
934  fwhmmax = np.zeros(ncat) + prefs.getFwhmrange()[1]
935  fwhmmode = (fwhmmin + fwhmmax)/2.0
936  else:
937  fwhms = {}
938 
939  #-- Try to estimate the most appropriate Half-light Radius range
940  #-- Get the Half-light radii
941  nobj = 0
942  for i, fileName in enumerate(filenames):
943  fwhms[i] = []
944 
945  if prefs.getVerboseType() != prefs.QUIET:
946  print("Examining Catalog #%d" % (i+1))
947 
948  #---- Read input catalog
949  tab = afwTable.SourceCatalog.readFits(fileName)
950 
951  #-------- Fill the FWHM array
952  shape = tab.getShapeDefinition()
953  ixx = tab.get("%s.xx" % shape)
954  iyy = tab.get("%s.yy" % shape)
955 
956  rmsSize = np.sqrt(0.5*(ixx + iyy))
957  elong = 0.5*(ixx - iyy)/(ixx + iyy)
958 
959  flux = tab.get(prefs.getPhotfluxRkey())
960  fluxErr = tab.get(prefs.getPhotfluxerrRkey())
961 
962  flags = getLsstFlags(tab)
963 
964  good = np.logical_and(flux/fluxErr > minsn,
965  np.logical_not(flags & prefs.getFlagMask()))
966  good = np.logical_and(good, elong < maxelong)
967  fwhm = 2.0*rmsSize
968  good = np.logical_and(good, fwhm >= min)
969  good = np.logical_and(good, fwhm < max)
970  fwhms[i] = fwhm[good]
971 
972  if prefs.getVarType() == prefs.VAR_NONE:
973  if nobj:
974  fwhms_all = np.empty(sum([len(l) for l in fwhms.values()]))
975  i = 0
976  for l in fwhms.values():
977  fwhms_all[i:len(l)] = l
978  i += len(l)
979  mode, min, max = compute_fwhmrange(fwhms_all, prefs.getMaxvar(),
980  prefs.getFwhmrange()[0], prefs.getFwhmrange()[1],
981  plot=plot)
982  else:
983  raise RuntimeError("No source with appropriate FWHM found!!")
984  mode = min = max = 2.35/(1.0 - 1.0/psfexLib.cvar.INTERPFAC)
985 
986  fwhmmin = np.zeros(ncat) + min
987  fwhmmax = np.zeros(ncat) + max
988  fwhmmode = np.zeros(ncat) + mode
989  else:
990  fwhmmode = np.empty(ncat)
991  fwhmmin = np.empty(ncat)
992  fwhmmax = np.empty(ncat)
993 
994  for i in range(ncat):
995  nobj = len(fwhms[i])
996  if (nobj):
997  fwhmmode[i], fwhmmin[i], fwhmmax[i] = \
998  compute_fwhmrange(fwhms[i], prefs.getMaxvar(),
999  prefs.getFwhmrange()[0], prefs.getFwhmrange()[1], plot=plot)
1000  else:
1001  raise RuntimeError("No source with appropriate FWHM found!!")
1002  fwhmmode[i] = fwhmmin[i] = fwhmmax[i] = 2.35/(1.0 - 1.0/psfexLib.cvar.INTERPFAC)
1003 
1004  # Read the samples
1005  mode = psfexLib.BIG # mode of FWHM distribution
1006 
1007  sets = []
1008  for i, fileName in enumerate(filenames):
1009  set = None
1010  for ext in range(next):
1011  set = read_samplesLsst(prefs, set, fileName, fwhmmin[i]/2.0, fwhmmax[i]/2.0,
1012  ext, next, i, context,
1013  context.getPc(i) if context.getNpc() else None, plot=plot)
1014 
1015  if fwhmmode[i] < mode:
1016  mode = fwhmmode[i]
1017 
1018  set.setFwhm(mode)
1019 
1020  if prefs.getVerboseType() != prefs.QUIET:
1021  if set.getNsample():
1022  print("%d samples loaded." % set.getNsample())
1023  else:
1024  raise RuntimeError("No appropriate source found!!")
1025 
1026  sets.append(set)
1027 
1028  return sets
1029 
1030 
1031 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
1032 
1033 def makeit(prefs, context, saveWcs=False, plot=dict()):
1034  """This is the python wrapper for the original psfex that reads SExtractor outputs
1035  """
1036  # Create an array of PSFs (one PSF for each extension)
1037  if prefs.getVerboseType() != prefs.QUIET:
1038  print("----- %d input catalogues:" % prefs.getNcat())
1039 
1040  if saveWcs: # only needed for making plots
1041  wcssList = []
1042 
1043  fields = psfexLib.vectorField()
1044  for cat in prefs.getCatalogs():
1045  field = psfexLib.Field(cat)
1046  wcss = []
1047  wcssList.append(wcss)
1048  with fits.open(cat) as pf:
1049  for hdu in pf:
1050  if hdu.name == "PRIMARY":
1051  pass
1052  elif hdu.name == "LDAC_IMHEAD":
1053  hdr = hdu.data[0][0] # the fits header from the original fits image
1054  md = PropertySet()
1055  for line in hdr:
1056  try:
1057  md.set(*splitFitsCard(line))
1058  except AttributeError:
1059  continue
1060 
1061  if not md.exists("CRPIX1"): # no WCS; try WCSA
1062  for k in md.names():
1063  if re.search(r"A$", k):
1064  md.set(k[:-1], md.getScalar(k))
1065  wcs = afwGeom.makeSkyWcs(md)
1066  naxis1, naxis2 = md.getScalar("NAXIS1"), md.getScalar("NAXIS2")
1067  elif hdu.name == "LDAC_OBJECTS":
1068  nobj = len(hdu.data)
1069 
1070  assert wcs, "LDAC_OBJECTS comes after LDAC_IMHEAD"
1071  field.addExt(wcs, naxis1, naxis2, nobj)
1072  if saveWcs:
1073  wcss.append((wcs, naxis1, naxis2))
1074  wcs = None
1075 
1076  field.finalize()
1077  fields.append(field)
1078 
1079  sets = psfexLib.vectorSet()
1080  for set in load_samples(prefs, context, plot=plot):
1081  sets.append(set)
1082 
1083  psfexLib.makeit(fields, sets)
1084 
1085  ret = [[f.getPsfs() for f in fields], sets]
1086  if saveWcs:
1087  ret.append(wcssList)
1088 
1089  return ret
def select_candidates(set, prefs, frmin, frmax, flags, flux, fluxerr, rmsSize, elong, vignet, plot=dict(), title="")
Definition: psfex.py:300
def read_samples(prefs, set, filename, frmin, frmax, ext, next, catindex, context, pcval, plot=dict(showFlags=False, showRejection=False))
Definition: psfex.py:127
def makeit(prefs, context, saveWcs=False, plot=dict())
Definition: psfex.py:1033
def showPsf(psf, set, ext=None, wcsData=None, trim=0, nspot=5, diagnostics=False, outDir="", frame=None, title=None)
Definition: psfex.py:556
def makeitLsst(prefs, context, saveWcs=False, plot=dict())
Definition: psfex.py:721
int min
def compute_fwhmrange(fwhm, maxvar, minin, maxin, plot=dict(fwhmHistogram=False))
Definition: psfex.py:57
def read_samplesLsst(prefs, set, filename, frmin, frmax, ext, next, catindex, context, pcval, plot=dict(showFlags=False, showRejection=False))
Definition: psfex.py:780
int max
Point in an unspecified spherical coordinate system.
Definition: SpherePoint.h:57
std::shared_ptr< SkyWcs > makeSkyWcs(TransformPoint2ToPoint2 const &pixelsToFieldAngle, lsst::geom::Angle const &orientation, bool flipX, lsst::geom::SpherePoint const &boresight, std::string const &projection="TAN")
Construct a FITS SkyWcs from camera geometry.
Definition: SkyWcs.cc:486
def load_samples(prefs, context, ext=psfexLib.Prefs.ALL_EXTENSIONS, next=1, plot=dict())
Definition: psfex.py:417
Class for storing generic metadata.
Definition: PropertySet.h:68
std::vector< SchemaItem< Flag > > * items
daf::base::PropertyList * list
Definition: fits.cc:833
def load_samplesLsst(prefs, context, ext=psfexLib.Prefs.ALL_EXTENSIONS, next=1, plot=dict())
Definition: psfex.py:920