lsst.shapelet  14.0-1-ge6e5c2d+40
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 # We register all the profiles at module import time, to allow C++ code to access all available profiles
86 # without having to later call Python code to unpickle them.
88 
89 
90 def evaluateRadial(basis, r, sbNormalize=False, doComponents=False):
91  """Plot a single-element MultiShapeletBasis as a radial profile.
92  """
94  coefficients = numpy.ones(1, dtype=float)
95  msf = basis.makeFunction(ellipse, coefficients)
96  ev = msf.evaluate()
97  n = 1
98  if doComponents:
99  n += len(msf.getComponents())
100  z = numpy.zeros((n,) + r.shape, dtype=float)
101  for j, x in enumerate(r):
102  z[0, j] = ev(x, 0.0)
103  if doComponents:
104  for i, sf in enumerate(msf.getComponents()):
105  evc = sf.evaluate()
106  for j, x in enumerate(r):
107  z[i+1, j] = evc(x, 0.0)
108  if sbNormalize:
109  z /= ev(1.0, 0.0)
110  return z
111 
112 
113 def integrateNormalizedFluxes(maxRadius=20.0, nSteps=5000):
114  """!
115  After normalizing by surface brightness at r=1 r_e, integrate the profiles to compare
116  relative fluxes between the true profiles and their approximations.
117 
118  @param[in] maxRadius Maximum radius to integrate the profile, in units of r_e.
119  @param[in] nSteps Number of concrete points at which to evaluate the profile to
120  do the integration (we just use the trapezoidal rule).
121  """
122  radii = numpy.linspace(0.0, maxRadius, nSteps)
123  profiles = {name: RadialProfile.get(name) for name in ("exp", "lux", "dev", "luv",
124  "ser2", "ser3", "ser5")}
125  evaluated = {}
126  for name, profile in profiles.items():
127  evaluated[name] = profile.evaluate(radii)
128  basis = profile.getBasis(8)
129  evaluated["g" + name] = evaluateRadial(basis, radii, sbNormalize=True, doComponents=False)[0, :]
130  fluxes = {name: numpy.trapz(z*radii, radii) for name, z in evaluated.items()}
131  return fluxes
132 
133 
134 def plotSuite(doComponents=False):
135  """Plot all the profiles defined in this module together: true exp and dev, the SDSS softended/truncated
136  lux and luv, and the multi-Gaussian approximations to all of these.
137 
138  To plot the individual Gaussians that form the multi-Gaussian approximations, pass doComponents=True.
139 
140  Returns a tuple of (figure, axes), where 'figure' is the matplotlib figure that contains the plot,
141  and axes is a 2x4 NumPy array of matplotlib axes objects
142  """
143  from matplotlib import pyplot
144  fig = pyplot.figure(figsize=(9, 4.7))
145  axes = numpy.zeros((2, 4), dtype=object)
146  r1 = numpy.logspace(-3, 0, 1000, base=10)
147  r2 = numpy.linspace(1, 10, 1000)
148  r = [r1, r2]
149  for i in range(2):
150  for j in range(4):
151  axes[i, j] = fig.add_subplot(2, 4, i*4+j+1)
152  profiles = {name: RadialProfile.get(name) for name in ("exp", "lux", "dev", "luv")}
153  basis = {name: profiles[name].getBasis(8) for name in profiles}
154  z = numpy.zeros((2, 4), dtype=object)
155  colors = ("k", "g", "b", "r")
156  fig.subplots_adjust(wspace=0.025, hspace=0.025, bottom=0.15, left=0.1, right=0.98, top=0.92)
157  centers = [None, None]
158  for i in range(2): # 0=profile, 1=relative error
159  for j in range(0, 4, 2): # grid columns: 0=exp-like, 2=dev-like
160  bbox0 = axes[i, j].get_position()
161  bbox1 = axes[i, j+1].get_position()
162  bbox1.x0 = bbox0.x1 - 0.06
163  bbox0.x1 = bbox1.x0
164  centers[j/2] = 0.5*(bbox0.x0 + bbox1.x1)
165  axes[i, j].set_position(bbox0)
166  axes[i, j+1].set_position(bbox1)
167  for j in range(0, 2):
168  z[0, j] = [evaluateRadial(basis[k], r[j], sbNormalize=True, doComponents=doComponents)
169  for k in ("exp", "lux")]
170  z[0, j][0:0] = [profiles[k].evaluate(r[j])[numpy.newaxis, :] for k in ("exp", "lux")]
171  z[0, j+2] = [evaluateRadial(basis[k], r[j], sbNormalize=True, doComponents=doComponents)
172  for k in ("dev", "luv")]
173  z[0, j+2][0:0] = [profiles[k].evaluate(r[j])[numpy.newaxis, :] for k in ("dev", "luv")]
174  methodNames = [["loglog", "semilogy"], ["semilogx", "plot"]]
175  for j in range(0, 4): # grid columns
176  z[1, j] = [(z[0, j][0][0, :] - z[0, j][i][0, :])/z[0, j][0][0, :] for i in range(0, 4)]
177  handles = []
178  method0 = getattr(axes[0, j], methodNames[0][j%2])
179  method1 = getattr(axes[1, j], methodNames[1][j%2])
180  for k in range(4):
181  y0 = z[0, j][k]
182  handles.append(method0(r[j%2], y0[0, :], color=colors[k])[0])
183  if doComponents:
184  for l in range(1, y0.shape[0]):
185  method0(r[j%2], y0[l, :], color=colors[k], alpha=0.25)
186  method1(r[j%2], z[1, j][k], color=colors[k])
187  axes[0, j].set_xticklabels([])
188  axes[0, j].set_ylim(1E-6, 1E3)
189  axes[1, j].set_ylim(-0.2, 1.0)
190  for i, label in enumerate(("profile", "relative error")):
191  axes[i, 0].set_ylabel(label)
192  for t in axes[i, 0].get_yticklabels():
193  t.set_fontsize(11)
194  for j in range(1, 4):
195  axes[0, j].set_yticklabels([])
196  axes[1, j].set_yticklabels([])
197  xticks = [['$\\mathdefault{10^{%d}}$' % i for i in range(-3, 1)],
198  [str(i) for i in range(1, 11)]]
199  xticks[0][-1] = ""
200  xticks[1][-1] = ""
201  for j in range(0, 4):
202  axes[1, j].set_xticklabels(xticks[j%2])
203  for t in axes[1, j].get_xticklabels():
204  t.set_fontsize(11)
205  fig.legend(handles, ["exp/dev", "lux/luv", "approx exp/dev", "approx lux/luv"],
206  loc='lower center', ncol=4)
207  fig.text(centers[0], 0.95, "exponential", ha='center', weight='bold')
208  fig.text(centers[1], 0.95, "de Vaucouleur", ha='center', weight='bold')
209  return fig, axes
def evaluateRadial(basis, r, sbNormalize=False, doComponents=False)
Definition: tractor.py:90
def plotSuite(doComponents=False)
Definition: tractor.py:134
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:113