lsst.shapelet  13.0-3-g20b541e+9
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends
tractor.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2013 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 """
23 Code to load multi-Gaussian approximations to profiles from "The Tractor"
24 into a lsst.shapelet.MultiShapeletBasis.
25 
26 Please see the README file in the data directory of the lsst.shapelet
27 package for more information.
28 """
29 from future import standard_library
30 standard_library.install_aliases() # noqa
31 from builtins import zip
32 from builtins import str
33 from builtins import range
34 
35 import numpy
36 import os
37 import re
38 import sys
39 import warnings
40 import pickle
41 
42 import lsst.pex.exceptions
43 from .radialProfile import RadialProfile
44 from .multiShapeletBasis import MultiShapeletBasis
45 from .shapeletFunction import ShapeletFunction
46 
48  """Register the pickled profiles in the data directory with the RadialProfile singleton registry.
49 
50  This should only be called at import time by this module; it's only a function to avoid polluting
51  the module namespace with all the local variables used here.
52  """
53  dataDir = os.path.join(os.environ["SHAPELET_DIR"], "data")
54  regex = re.compile(r"([a-z]+\d?)_K(\d+)_MR(\d+)\.pickle")
55  for filename in os.listdir(dataDir):
56  match = regex.match(filename)
57  if not match:
58  continue
59  name = match.group(1)
60  nComponents = int(match.group(2))
61  maxRadius = int(match.group(3))
62  try:
63  profile = RadialProfile.get(name)
64  except lsst.pex.exceptions.Exception:
65  warnings.warn("No C++ profile for multi-Gaussian pickle file '%s'" % filename)
66  continue
67  with open(os.path.join(dataDir, filename), 'rb') as stream:
68  if sys.version_info[0] >= 3:
69  array = pickle.load(stream, encoding='latin1')
70  else:
71  array = pickle.load(stream)
72  amplitudes = array[:nComponents]
73  amplitudes /= amplitudes.sum()
74  variances = array[nComponents:]
75  if amplitudes.shape != (nComponents,) or variances.shape != (nComponents,):
76  warnings.warn("Unknown format for multi-Gaussian pickle file '%s'" % filename)
77  continue
78  basis = MultiShapeletBasis(1)
79  for amplitude, variance in zip(amplitudes, variances):
80  radius = variance**0.5
81  matrix = numpy.array([[amplitude / ShapeletFunction.FLUX_FACTOR]], dtype=float)
82  basis.addComponent(radius, 0, matrix)
83  profile.registerBasis(basis, nComponents, maxRadius)
84 # We register all the profiles at module import time, to allow C++ code to access all available profiles
85 # without having to later call Python code to unpickle them.
87 
88 
89 def evaluateRadial(basis, r, sbNormalize=False, doComponents=False):
90  """Plot a single-element MultiShapeletBasis as a radial profile.
91  """
92  ellipse = lsst.afw.geom.ellipses.Ellipse(lsst.afw.geom.ellipses.Axes())
93  coefficients = numpy.ones(1, dtype=float)
94  msf = basis.makeFunction(ellipse, coefficients)
95  ev = msf.evaluate()
96  n = 1
97  if doComponents:
98  n += len(msf.getComponents())
99  z = numpy.zeros((n,) + r.shape, dtype=float)
100  for j, x in enumerate(r):
101  z[0, j] = ev(x, 0.0)
102  if doComponents:
103  for i, sf in enumerate(msf.getComponents()):
104  evc = sf.evaluate()
105  for j, x in enumerate(r):
106  z[i+1, j] = evc(x, 0.0)
107  if sbNormalize:
108  z /= ev(1.0, 0.0)
109  return z
110 
111 
112 def integrateNormalizedFluxes(maxRadius=20.0, nSteps=5000):
113  """!
114  After normalizing by surface brightness at r=1 r_e, integrate the profiles to compare
115  relative fluxes between the true profiles and their approximations.
116 
117  @param[in] maxRadius Maximum radius to integrate the profile, in units of r_e.
118  @param[in] nSteps Number of concrete points at which to evaluate the profile to
119  do the integration (we just use the trapezoidal rule).
120  """
121  radii = numpy.linspace(0.0, maxRadius, nSteps)
122  profiles = {name: RadialProfile.get(name) for name in ("exp", "lux", "dev", "luv",
123  "ser2", "ser3", "ser5")}
124  evaluated = {}
125  for name, profile in profiles.items():
126  evaluated[name] = profile.evaluate(radii)
127  basis = profile.getBasis(8)
128  evaluated["g" + name] = evaluateRadial(basis, radii, sbNormalize=True, doComponents=False)[0, :]
129  fluxes = {name: numpy.trapz(z*radii, radii) for name, z in evaluated.items()}
130  return fluxes
131 
132 
133 def plotSuite(doComponents=False):
134  """Plot all the profiles defined in this module together: true exp and dev, the SDSS softended/truncated
135  lux and luv, and the multi-Gaussian approximations to all of these.
136 
137  To plot the individual Gaussians that form the multi-Gaussian approximations, pass doComponents=True.
138 
139  Returns a tuple of (figure, axes), where 'figure' is the matplotlib figure that contains the plot,
140  and axes is a 2x4 NumPy array of matplotlib axes objects
141  """
142  from matplotlib import pyplot
143  fig = pyplot.figure(figsize=(9, 4.7))
144  axes = numpy.zeros((2, 4), dtype=object)
145  r1 = numpy.logspace(-3, 0, 1000, base=10)
146  r2 = numpy.linspace(1, 10, 1000)
147  r = [r1, r2]
148  for i in range(2):
149  for j in range(4):
150  axes[i, j] = fig.add_subplot(2, 4, i*4+j+1)
151  profiles = {name: RadialProfile.get(name) for name in ("exp", "lux", "dev", "luv")}
152  basis = {name: profiles[name].getBasis(8) for name in profiles}
153  z = numpy.zeros((2, 4), dtype=object)
154  colors = ("k", "g", "b", "r")
155  fig.subplots_adjust(wspace=0.025, hspace=0.025, bottom=0.15, left=0.1, right=0.98, top=0.92)
156  centers = [None, None]
157  for i in range(2): # 0=profile, 1=relative error
158  for j in range(0, 4, 2): # grid columns: 0=exp-like, 2=dev-like
159  bbox0 = axes[i, j].get_position()
160  bbox1 = axes[i, j+1].get_position()
161  bbox1.x0 = bbox0.x1 - 0.06
162  bbox0.x1 = bbox1.x0
163  centers[j/2] = 0.5*(bbox0.x0 + bbox1.x1)
164  axes[i, j].set_position(bbox0)
165  axes[i, j+1].set_position(bbox1)
166  for j in range(0, 2):
167  z[0, j] = [evaluateRadial(basis[k], r[j], sbNormalize=True, doComponents=doComponents)
168  for k in ("exp", "lux")]
169  z[0, j][0:0] = [profiles[k].evaluate(r[j])[numpy.newaxis, :] for k in ("exp", "lux")]
170  z[0, j+2] = [evaluateRadial(basis[k], r[j], sbNormalize=True, doComponents=doComponents)
171  for k in ("dev", "luv")]
172  z[0, j+2][0:0] = [profiles[k].evaluate(r[j])[numpy.newaxis, :] for k in ("dev", "luv")]
173  methodNames = [["loglog", "semilogy"], ["semilogx", "plot"]]
174  for j in range(0, 4): # grid columns
175  y[1, j] = [(y[0, j][0][0, :] - y[0, j][i][0, :])/y[0, j][0][0, :] for i in range(0, 4)]
176  handles = []
177  method0 = getattr(axes[0, j], methodNames[0][j%2])
178  method1 = getattr(axes[1, j], methodNames[1][j%2])
179  for k in range(4):
180  y0 = y[0, j][k]
181  handles.append(method0(r[j%2], y0[0, :], color=colors[k])[0])
182  if doComponents:
183  for l in range(1, y0.shape[0]):
184  method0(r[j%2], y0[l, :], color=colors[k], alpha=0.25)
185  method1(r[j%2], y[1, j][k], color=colors[k])
186  axes[0, j].set_xticklabels([])
187  axes[0, j].set_ylim(1E-6, 1E3)
188  axes[1, j].set_ylim(-0.2, 1.0)
189  for i, label in enumerate(("profile", "relative error")):
190  axes[i, 0].set_ylabel(label)
191  for t in axes[i, 0].get_yticklabels():
192  t.set_fontsize(11)
193  for j in range(1, 4):
194  axes[0, j].set_yticklabels([])
195  axes[1, j].set_yticklabels([])
196  xticks = [['$\\mathdefault{10^{%d}}$' % i for i in range(-3, 1)],
197  [str(i) for i in range(1, 11)]]
198  xticks[0][-1] = ""
199  xticks[1][-1] = ""
200  for j in range(0, 4):
201  axes[1, j].set_xticklabels(xticks[j%2])
202  for t in axes[1, j].get_xticklabels():
203  t.set_fontsize(11)
204  fig.legend(handles, ["exp/dev", "lux/luv", "approx exp/dev", "approx lux/luv"],
205  loc='lower center', ncol=4)
206  fig.text(centers[0], 0.95, "exponential", ha='center', weight='bold')
207  fig.text(centers[1], 0.95, "de Vaucouleur", ha='center', weight='bold')
208  return fig, axes
def integrateNormalizedFluxes
After normalizing by surface brightness at r=1 r_e, integrate the profiles to compare relative fluxes...
Definition: tractor.py:112