lsst.shapelet  16.0-6-gf0acd13+1
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 
43 from .radialProfile import RadialProfile
44 from .multiShapeletBasis import MultiShapeletBasis
45 from .shapeletFunction import ShapeletFunction
46 
47 
49  """Register the pickled profiles in the data directory with the RadialProfile singleton registry.
50 
51  This should only be called at import time by this module; it's only a function to avoid polluting
52  the module namespace with all the local variables used here.
53  """
54  dataDir = os.path.join(os.environ["SHAPELET_DIR"], "data")
55  regex = re.compile(r"([a-z]+\d?)_K(\d+)_MR(\d+)\.pickle")
56  for filename in os.listdir(dataDir):
57  match = regex.match(filename)
58  if not match:
59  continue
60  name = match.group(1)
61  nComponents = int(match.group(2))
62  maxRadius = int(match.group(3))
63  try:
64  profile = RadialProfile.get(name)
66  warnings.warn("No C++ profile for multi-Gaussian pickle file '%s'" % filename)
67  continue
68  with open(os.path.join(dataDir, filename), 'rb') as stream:
69  if sys.version_info[0] >= 3:
70  array = pickle.load(stream, encoding='latin1')
71  else:
72  array = pickle.load(stream)
73  amplitudes = array[:nComponents]
74  amplitudes /= amplitudes.sum()
75  variances = array[nComponents:]
76  if amplitudes.shape != (nComponents,) or variances.shape != (nComponents,):
77  warnings.warn("Unknown format for multi-Gaussian pickle file '%s'" % filename)
78  continue
79  basis = MultiShapeletBasis(1)
80  for amplitude, variance in zip(amplitudes, variances):
81  radius = variance**0.5
82  matrix = numpy.array([[amplitude / ShapeletFunction.FLUX_FACTOR]], dtype=float)
83  basis.addComponent(radius, 0, matrix)
84  profile.registerBasis(basis, nComponents, maxRadius)
85 
86 
87 # We register all the profiles at module import time, to allow C++ code to access all available profiles
88 # without having to later call Python code to unpickle them.
90 
91 
92 def evaluateRadial(basis, r, sbNormalize=False, doComponents=False):
93  """Plot a single-element MultiShapeletBasis as a radial profile.
94  """
96  coefficients = numpy.ones(1, dtype=float)
97  msf = basis.makeFunction(ellipse, coefficients)
98  ev = msf.evaluate()
99  n = 1
100  if doComponents:
101  n += len(msf.getComponents())
102  z = numpy.zeros((n,) + r.shape, dtype=float)
103  for j, x in enumerate(r):
104  z[0, j] = ev(x, 0.0)
105  if doComponents:
106  for i, sf in enumerate(msf.getComponents()):
107  evc = sf.evaluate()
108  for j, x in enumerate(r):
109  z[i+1, j] = evc(x, 0.0)
110  if sbNormalize:
111  z /= ev(1.0, 0.0)
112  return z
113 
114 
115 def integrateNormalizedFluxes(maxRadius=20.0, nSteps=5000):
116  """!
117  After normalizing by surface brightness at r=1 r_e, integrate the profiles to compare
118  relative fluxes between the true profiles and their approximations.
119 
120  @param[in] maxRadius Maximum radius to integrate the profile, in units of r_e.
121  @param[in] nSteps Number of concrete points at which to evaluate the profile to
122  do the integration (we just use the trapezoidal rule).
123  """
124  radii = numpy.linspace(0.0, maxRadius, nSteps)
125  profiles = {name: RadialProfile.get(name) for name in ("exp", "lux", "dev", "luv",
126  "ser2", "ser3", "ser5")}
127  evaluated = {}
128  for name, profile in profiles.items():
129  evaluated[name] = profile.evaluate(radii)
130  basis = profile.getBasis(8)
131  evaluated["g" + name] = evaluateRadial(basis, radii, sbNormalize=True, doComponents=False)[0, :]
132  fluxes = {name: numpy.trapz(z*radii, radii) for name, z in evaluated.items()}
133  return fluxes
134 
135 
136 def plotSuite(doComponents=False):
137  """Plot all the profiles defined in this module together: true exp and dev, the SDSS softended/truncated
138  lux and luv, and the multi-Gaussian approximations to all of these.
139 
140  To plot the individual Gaussians that form the multi-Gaussian approximations, pass doComponents=True.
141 
142  Returns a tuple of (figure, axes), where 'figure' is the matplotlib figure that contains the plot,
143  and axes is a 2x4 NumPy array of matplotlib axes objects
144  """
145  from matplotlib import pyplot
146  fig = pyplot.figure(figsize=(9, 4.7))
147  axes = numpy.zeros((2, 4), dtype=object)
148  r1 = numpy.logspace(-3, 0, 1000, base=10)
149  r2 = numpy.linspace(1, 10, 1000)
150  r = [r1, r2]
151  for i in range(2):
152  for j in range(4):
153  axes[i, j] = fig.add_subplot(2, 4, i*4+j+1)
154  profiles = {name: RadialProfile.get(name) for name in ("exp", "lux", "dev", "luv")}
155  basis = {name: profiles[name].getBasis(8) for name in profiles}
156  z = numpy.zeros((2, 4), dtype=object)
157  colors = ("k", "g", "b", "r")
158  fig.subplots_adjust(wspace=0.025, hspace=0.025, bottom=0.15, left=0.1, right=0.98, top=0.92)
159  centers = [None, None]
160  for i in range(2): # 0=profile, 1=relative error
161  for j in range(0, 4, 2): # grid columns: 0=exp-like, 2=dev-like
162  bbox0 = axes[i, j].get_position()
163  bbox1 = axes[i, j+1].get_position()
164  bbox1.x0 = bbox0.x1 - 0.06
165  bbox0.x1 = bbox1.x0
166  centers[j/2] = 0.5*(bbox0.x0 + bbox1.x1)
167  axes[i, j].set_position(bbox0)
168  axes[i, j+1].set_position(bbox1)
169  for j in range(0, 2):
170  z[0, j] = [evaluateRadial(basis[k], r[j], sbNormalize=True, doComponents=doComponents)
171  for k in ("exp", "lux")]
172  z[0, j][0:0] = [profiles[k].evaluate(r[j])[numpy.newaxis, :] for k in ("exp", "lux")]
173  z[0, j+2] = [evaluateRadial(basis[k], r[j], sbNormalize=True, doComponents=doComponents)
174  for k in ("dev", "luv")]
175  z[0, j+2][0:0] = [profiles[k].evaluate(r[j])[numpy.newaxis, :] for k in ("dev", "luv")]
176  methodNames = [["loglog", "semilogy"], ["semilogx", "plot"]]
177  for j in range(0, 4): # grid columns
178  z[1, j] = [(z[0, j][0][0, :] - z[0, j][i][0, :])/z[0, j][0][0, :] for i in range(0, 4)]
179  handles = []
180  method0 = getattr(axes[0, j], methodNames[0][j%2])
181  method1 = getattr(axes[1, j], methodNames[1][j%2])
182  for k in range(4):
183  y0 = z[0, j][k]
184  handles.append(method0(r[j%2], y0[0, :], color=colors[k])[0])
185  if doComponents:
186  for l in range(1, y0.shape[0]):
187  method0(r[j%2], y0[l, :], color=colors[k], alpha=0.25)
188  method1(r[j%2], z[1, j][k], color=colors[k])
189  axes[0, j].set_xticklabels([])
190  axes[0, j].set_ylim(1E-6, 1E3)
191  axes[1, j].set_ylim(-0.2, 1.0)
192  for i, label in enumerate(("profile", "relative error")):
193  axes[i, 0].set_ylabel(label)
194  for t in axes[i, 0].get_yticklabels():
195  t.set_fontsize(11)
196  for j in range(1, 4):
197  axes[0, j].set_yticklabels([])
198  axes[1, j].set_yticklabels([])
199  xticks = [['$\\mathdefault{10^{%d}}$' % i for i in range(-3, 1)],
200  [str(i) for i in range(1, 11)]]
201  xticks[0][-1] = ""
202  xticks[1][-1] = ""
203  for j in range(0, 4):
204  axes[1, j].set_xticklabels(xticks[j%2])
205  for t in axes[1, j].get_xticklabels():
206  t.set_fontsize(11)
207  fig.legend(handles, ["exp/dev", "lux/luv", "approx exp/dev", "approx lux/luv"],
208  loc='lower center', ncol=4)
209  fig.text(centers[0], 0.95, "exponential", ha='center', weight='bold')
210  fig.text(centers[1], 0.95, "de Vaucouleur", ha='center', weight='bold')
211  return fig, axes
def evaluateRadial(basis, r, sbNormalize=False, doComponents=False)
Definition: tractor.py:92
def plotSuite(doComponents=False)
Definition: tractor.py:136
def registerRadialProfiles()
Definition: tractor.py:48
def integrateNormalizedFluxes(maxRadius=20.0, nSteps=5000)
After normalizing by surface brightness at r=1 r_e, integrate the profiles to compare relative fluxes...
Definition: tractor.py:115